├── CONTRIBUTING.md
├── .prettierignore
├── babel.config.ts
├── src
├── splash.ts
├── creator
│ ├── components
│ │ ├── DetailsBlock
│ │ │ ├── index.ts
│ │ │ └── DetailsForm.tsx
│ │ ├── PackageJsonBlock
│ │ │ ├── index.tsx
│ │ │ ├── packagejson.css
│ │ │ └── CardPackageJson.tsx
│ │ ├── GithubBlock
│ │ │ ├── index.ts
│ │ │ ├── GithubSection.tsx
│ │ │ └── GithubForm.tsx
│ │ ├── FeaturesBlock
│ │ │ ├── index.ts
│ │ │ ├── FeaturesList.tsx
│ │ │ └── FeatureSwitch.tsx
│ │ ├── InstallationBlock
│ │ │ ├── index.tsx
│ │ │ └── TerminalOutputInstallation.tsx
│ │ ├── PackageCharts
│ │ │ ├── index.ts
│ │ │ ├── Treemap.tsx
│ │ │ ├── PackagesSize.tsx
│ │ │ └── ChartSize.tsx
│ │ ├── ScriptBlock
│ │ │ ├── index.ts
│ │ │ ├── ListScripts.tsx
│ │ │ ├── ScriptItem.tsx
│ │ │ └── ScriptSection.tsx
│ │ ├── ArchitectureBlock
│ │ │ ├── index.ts
│ │ │ ├── TreeFolder.tsx
│ │ │ └── CreateFolder.tsx
│ │ ├── ReadmeBlock
│ │ │ ├── index.tsx
│ │ │ ├── ReadmePreview.tsx
│ │ │ ├── ReadmeSection.tsx
│ │ │ ├── ReadmeEdit.tsx
│ │ │ ├── MarkdownWrapper.tsx
│ │ │ ├── ReadmeHeader.tsx
│ │ │ └── markdown.css
│ │ ├── pages
│ │ │ ├── DocumentationPage.tsx
│ │ │ ├── CommandPage.tsx
│ │ │ ├── DetailsPage.tsx
│ │ │ ├── PackagesPage.tsx
│ │ │ ├── FeaturesPage.tsx
│ │ │ ├── ArchitecturePage.tsx
│ │ │ └── SuccessPage.tsx
│ │ ├── Buttons
│ │ │ ├── index.tsx
│ │ │ ├── ButtonSaveReadme.tsx
│ │ │ ├── ButtonCreation.tsx
│ │ │ ├── ButtonRemoveScript.tsx
│ │ │ ├── ButtonEditFilename.tsx
│ │ │ ├── ButtonRemoveFile.tsx
│ │ │ ├── ButtonRemovePackage.tsx
│ │ │ ├── ButtonGithubLogin.tsx
│ │ │ └── ButtonAddPackage.tsx
│ │ ├── Contexts
│ │ │ ├── LoadingPackageProvider.tsx
│ │ │ ├── PackageJsonProvider.tsx
│ │ │ ├── dependenciesProvider.tsx
│ │ │ └── GithubProvider.tsx
│ │ ├── PackageManagerBlock
│ │ │ ├── index.tsx
│ │ │ ├── PackagesManager.tsx
│ │ │ ├── ItemPackageTooltip.tsx
│ │ │ ├── CardDependencies.tsx
│ │ │ ├── ListPackagesSelected.tsx
│ │ │ ├── ItemPackage.tsx
│ │ │ ├── ListPackagesFound.tsx
│ │ │ ├── ItemPackageFound.tsx
│ │ │ ├── ListPackages.tsx
│ │ │ └── SearchPackages.tsx
│ │ ├── LayoutCreator.tsx
│ │ └── StepControlButtons.tsx
│ ├── index.ts
│ ├── helpers
│ │ ├── gitServicesOptions.ts
│ │ ├── initialStructure.ts
│ │ ├── toast.ts
│ │ ├── initialState.ts
│ │ ├── steps.ts
│ │ ├── initialPackageJson.ts
│ │ ├── authGithub.ts
│ │ ├── featuresLists.ts
│ │ └── types.ts
│ ├── reducers
│ │ ├── structureReducer.ts
│ │ └── dependenciesReducer.ts
│ ├── CreatorMenuSelection.tsx
│ ├── Creator.tsx
│ └── CreatorVite.tsx
├── manager
│ ├── index.ts
│ ├── components
│ │ ├── Terminal
│ │ │ ├── index.ts
│ │ │ └── TerminalOutput.tsx
│ │ ├── ComponentGenBlock
│ │ │ ├── index.ts
│ │ │ ├── ComponentPreview.tsx
│ │ │ ├── ListComponentOptions.tsx
│ │ │ ├── ComponentSwitch.tsx
│ │ │ └── ComponentMode.tsx
│ │ ├── DependenciesBlock
│ │ │ ├── index.ts
│ │ │ ├── DependenciesList.tsx
│ │ │ ├── ListDependenciesFound.tsx
│ │ │ ├── DependencyItemFound.tsx
│ │ │ ├── DependenciesItem.tsx
│ │ │ └── DependenciesSearch.tsx
│ │ ├── pages
│ │ │ ├── TasksPage.tsx
│ │ │ ├── DependenciesPage.tsx
│ │ │ └── ComponentGeneratorPage.tsx
│ │ ├── TasksBlock
│ │ │ ├── TasksList.tsx
│ │ │ ├── TaskStatut.tsx
│ │ │ ├── TasksItem.tsx
│ │ │ └── TasksDevelopmentPane.tsx
│ │ └── HeaderManager.tsx
│ ├── helpers
│ │ ├── initialStructure.ts
│ │ └── types.ts
│ ├── ManagerMenuSelection.tsx
│ └── reducers
│ │ └── taskReducer.tsx
├── assets
│ ├── icons
│ │ ├── mac
│ │ │ └── icon.icns
│ │ ├── png
│ │ │ ├── 16x16.png
│ │ │ ├── 24x24.png
│ │ │ ├── 32x32.png
│ │ │ ├── 48x48.png
│ │ │ ├── 64x64.png
│ │ │ ├── 128x128.png
│ │ │ ├── 256x256.png
│ │ │ ├── 512x512.png
│ │ │ └── 1024x1024.png
│ │ └── win
│ │ │ └── icon.ico
│ ├── logo_starter
│ │ ├── gatsby.png
│ │ ├── vite.svg
│ │ ├── remix.svg
│ │ └── nextjs.svg
│ └── waves.svg
├── analytics
│ ├── electron-store.service.ts
│ └── mixpanel.service.ts
├── utils
│ ├── pause.ts
│ ├── listDepsSize.ts
│ ├── color.ts
│ ├── findStartScript.ts
│ ├── writeFileAtTop.ts
│ ├── findStarter.ts
│ ├── runCmd.ts
│ ├── formatDeps.ts
│ ├── readSrcFolder.ts
│ ├── promisifyFs.ts
│ ├── validateInput.ts
│ ├── generateTreeMapWithD3.ts
│ ├── killProcess.tsx
│ └── createTemplateComponent.ts
├── index.html
├── hooks
│ └── useModal.ts
├── common
│ ├── Typo.tsx
│ ├── layout.css
│ ├── SplashScreen
│ │ ├── splash.css
│ │ └── Splash.tsx
│ ├── Card.tsx
│ ├── markdown.css
│ ├── card.css
│ ├── Layout.tsx
│ ├── MarkdownWrapper.tsx
│ ├── ScoreNpmPophover.tsx
│ ├── Input.tsx
│ ├── Bar.tsx
│ ├── bar.css
│ ├── sidenav.css
│ └── Button.tsx
├── splash.html
├── hooks.ts
├── index.css
├── store.ts
├── slices
│ ├── projectSrcSlice.ts
│ ├── projectSlice.ts
│ ├── dependenciesSlice.ts
│ └── taskSlice.ts
├── preload.ts
├── __test__
│ ├── helpersElectron.ts
│ ├── e2e
│ │ └── main.test.ts
│ └── integration
│ │ ├── feature.test.tsx
│ │ └── details.test.tsx
├── renderer.ts
└── services
│ ├── package.service.ts
│ └── github.services.ts
├── postcss.config.js
├── .prettierrc.json
├── jest.config.js
├── webpack.main.config.js
├── webpack.renderer.config.js
├── tsconfig.json
├── webpack.plugins.js
├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── workflows
│ ├── build.yml
│ └── release.yml
├── .eslintrc.json
├── webpack.rules.js
├── LICENSE.md
├── tailwind.config.js
├── .gitignore
└── CODE_OF_CONDUCT.md
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 | coverage
--------------------------------------------------------------------------------
/babel.config.ts:
--------------------------------------------------------------------------------
1 | module.exports = {presets: ['@babel/preset-env']}
--------------------------------------------------------------------------------
/src/splash.ts:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import './common/SplashScreen/Splash';
3 |
--------------------------------------------------------------------------------
/src/creator/components/DetailsBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { DetailsForm } from './DetailsForm';
2 |
--------------------------------------------------------------------------------
/src/creator/index.ts:
--------------------------------------------------------------------------------
1 | import Creator from './Creator';
2 |
3 | export default Creator;
4 |
--------------------------------------------------------------------------------
/src/manager/index.ts:
--------------------------------------------------------------------------------
1 | import Manager from './Manager';
2 |
3 | export default Manager;
4 |
--------------------------------------------------------------------------------
/src/manager/components/Terminal/index.ts:
--------------------------------------------------------------------------------
1 | export { TerminalOutput } from './TerminalOutput';
2 |
--------------------------------------------------------------------------------
/src/creator/components/PackageJsonBlock/index.tsx:
--------------------------------------------------------------------------------
1 | export { CardPackageJson } from './CardPackageJson';
2 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | }
6 | }
--------------------------------------------------------------------------------
/src/assets/icons/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/mac/icon.icns
--------------------------------------------------------------------------------
/src/assets/icons/png/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/16x16.png
--------------------------------------------------------------------------------
/src/assets/icons/png/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/24x24.png
--------------------------------------------------------------------------------
/src/assets/icons/png/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/32x32.png
--------------------------------------------------------------------------------
/src/assets/icons/png/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/48x48.png
--------------------------------------------------------------------------------
/src/assets/icons/png/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/64x64.png
--------------------------------------------------------------------------------
/src/assets/icons/win/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/win/icon.ico
--------------------------------------------------------------------------------
/src/assets/icons/png/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/128x128.png
--------------------------------------------------------------------------------
/src/assets/icons/png/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/256x256.png
--------------------------------------------------------------------------------
/src/assets/icons/png/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/512x512.png
--------------------------------------------------------------------------------
/src/assets/icons/png/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/icons/png/1024x1024.png
--------------------------------------------------------------------------------
/src/assets/logo_starter/gatsby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Leopold-V/Reactirator/HEAD/src/assets/logo_starter/gatsby.png
--------------------------------------------------------------------------------
/src/analytics/electron-store.service.ts:
--------------------------------------------------------------------------------
1 | import ElectronStore from 'electron-store';
2 |
3 | export default new ElectronStore();
4 |
--------------------------------------------------------------------------------
/src/creator/components/GithubBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { GithubSection } from './GithubSection';
2 | export { GithubForm } from './GithubForm';
3 |
--------------------------------------------------------------------------------
/src/creator/components/FeaturesBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { FeaturesList } from './FeaturesList';
2 | export { FeatureSwitch } from './FeatureSwitch';
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "printWidth": 100,
6 | "singleQuote": true
7 | }
--------------------------------------------------------------------------------
/src/utils/pause.ts:
--------------------------------------------------------------------------------
1 | export const pause = (time: number) => {
2 | return new Promise(function (resolve) {
3 | setTimeout(resolve, time);
4 | });
5 | };
6 |
--------------------------------------------------------------------------------
/src/creator/components/InstallationBlock/index.tsx:
--------------------------------------------------------------------------------
1 | export { ModalInstallation } from './ModalInstallation';
2 | export { TerminalOutputInstallation } from './TerminalOutputInstallation';
3 |
--------------------------------------------------------------------------------
/src/creator/components/PackageCharts/index.ts:
--------------------------------------------------------------------------------
1 | export { ChartSize } from './ChartSize';
2 | //export { Treemap } from './Treemap';
3 | export { PackagesSizeMemoized } from './PackagesSize';
4 |
--------------------------------------------------------------------------------
/src/creator/components/ScriptBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { ScriptSection } from './ScriptSection';
2 | export { ListScripts } from './ListScripts';
3 | export { ScriptItem } from './ScriptItem';
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: "ts-jest",
3 | transform: {
4 | '^.+\\.(ts|tsx)?$': 'ts-jest',
5 | "^.+\\.(js|jsx)$": "babel-jest",
6 | },
7 | testTimeout: 20000,
8 | };
--------------------------------------------------------------------------------
/src/creator/components/ArchitectureBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { TreeFolder } from './TreeFolder';
2 | export { CreateComponent } from './CreateComponent';
3 | export { CreateFolder } from './CreateFolder';
4 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Reactirator
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/utils/listDepsSize.ts:
--------------------------------------------------------------------------------
1 | const listDepsSize = (listDeps: { name: string; size: number }[]): number => {
2 | return Math.floor(Object.values(listDeps).reduce((a, b) => a + b.size, 0) / 1000);
3 | };
4 |
5 | export default listDepsSize;
6 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/index.tsx:
--------------------------------------------------------------------------------
1 | export { ReadmeSection } from './ReadmeSection';
2 | export { ReadmeHeader } from './ReadmeHeader';
3 | export { ReadmeEdit } from './ReadmeEdit';
4 | export { ReadmePreview } from './ReadmePreview';
5 |
--------------------------------------------------------------------------------
/src/utils/color.ts:
--------------------------------------------------------------------------------
1 | export const getRandomColor = () => {
2 | const letters = '0123456789ABCDEF';
3 | let color = '#';
4 | for (let i = 0; i < 6; i++) {
5 | color += letters[Math.floor(Math.random() * 16)];
6 | }
7 | return color;
8 | };
9 |
--------------------------------------------------------------------------------
/src/creator/helpers/gitServicesOptions.ts:
--------------------------------------------------------------------------------
1 | export const Constants = {
2 | AUTH_SCOPE: ['repo'],
3 |
4 | DEFAULT_AUTH_OPTIONS: {
5 | hostname: 'github.com',
6 | clientId: process.env.CLIENT_ID,
7 | clientSecret: process.env.CLIENT_SECRET,
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/src/hooks/useModal.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export const useModal = () => {
4 | const [show, setShow] = useState(false);
5 |
6 | const toggleModal = (): void => {
7 | setShow((show) => !show);
8 | };
9 |
10 | return [show, toggleModal] as const;
11 | };
12 |
--------------------------------------------------------------------------------
/src/common/Typo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Title = ({ title, className }: { title: string; className?: string }) => {
4 | return (
5 |
6 | {title}
7 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/src/manager/components/ComponentGenBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { ComponentSwitch } from './ComponentSwitch';
2 | export { FormComponent } from './FormComponent';
3 | export { ListComponentOptions } from './ListComponentOptions';
4 | export { SelectComponentMode } from './ComponentMode';
5 | export { ComponentPreview } from './ComponentPreview';
6 |
--------------------------------------------------------------------------------
/src/common/layout.css:
--------------------------------------------------------------------------------
1 | #layout::-webkit-scrollbar {
2 | width: 11px;
3 | }
4 |
5 | #layout::-webkit-scrollbar-track {
6 | background: rgb(59, 59, 59);
7 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
8 | margin-top: 2rem;
9 | }
10 |
11 | #layout::-webkit-scrollbar-thumb {
12 | background-color: whitesmoke;
13 | border-radius: 6px;
14 | }
15 |
--------------------------------------------------------------------------------
/src/splash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Splash screen
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/ReadmePreview.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MarkdownWrapper } from './MarkdownWrapper';
3 |
4 | export const ReadmePreview = ({ readme }: { readme: string }) => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/utils/findStartScript.ts:
--------------------------------------------------------------------------------
1 | const findStartScript = (starterName: string) => {
2 | if (starterName === 'CRA' || starterName === 'gatsby') {
3 | return 'start';
4 | }
5 | if (starterName === 'next' || starterName === 'remix' || starterName === 'vite') {
6 | return 'dev';
7 | }
8 | return null;
9 | };
10 |
11 | export default findStartScript;
12 |
--------------------------------------------------------------------------------
/src/utils/writeFileAtTop.ts:
--------------------------------------------------------------------------------
1 | import { promisifyReadFs, promisifyWriteFs, promisifyAppendFs } from './promisifyFs';
2 |
3 | export const writeFileAtTop = async (fullpath: string, dataToWrite: string): Promise => {
4 | const data = await promisifyReadFs(fullpath);
5 | await promisifyWriteFs(fullpath, dataToWrite);
6 | await promisifyAppendFs(fullpath, data);
7 | };
8 |
--------------------------------------------------------------------------------
/src/common/SplashScreen/splash.css:
--------------------------------------------------------------------------------
1 | #splash_screen {
2 | background: white;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | flex-direction: column;
7 | min-height: 100vh;
8 | width: 100vw;
9 | }
10 |
11 | #splash_title {
12 | font-size: 3rem;
13 | font-weight: bolder;
14 | color: darkblue;
15 | padding: 2rem 0;
16 | }
17 |
--------------------------------------------------------------------------------
/src/creator/helpers/initialStructure.ts:
--------------------------------------------------------------------------------
1 | const initialStructure = [
2 | {
3 | id: '1',
4 | name: 'src',
5 | ancestor: '',
6 | isFolder: true,
7 | path: '\\src',
8 | },
9 | {
10 | id: '2',
11 | name: 'App',
12 | ancestor: '1',
13 | isFolder: false,
14 | path: '\\src\\App',
15 | },
16 | ];
17 |
18 | export default initialStructure;
19 |
--------------------------------------------------------------------------------
/src/manager/helpers/initialStructure.ts:
--------------------------------------------------------------------------------
1 | const initialStructure = [
2 | {
3 | id: '1',
4 | name: 'src',
5 | ancestor: '',
6 | isFolder: true,
7 | path: '\\src',
8 | },
9 | {
10 | id: '2',
11 | name: 'App',
12 | ancestor: '1',
13 | isFolder: false,
14 | path: '\\src\\App',
15 | },
16 | ];
17 |
18 | export default initialStructure;
19 |
--------------------------------------------------------------------------------
/src/creator/components/PackageJsonBlock/packagejson.css:
--------------------------------------------------------------------------------
1 | #packagejson::-webkit-scrollbar {
2 | width: 11px;
3 | }
4 |
5 | #packagejson::-webkit-scrollbar-track {
6 | background: rgb(45, 55, 72);
7 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
8 | }
9 |
10 | #packagejson::-webkit-scrollbar-thumb {
11 | background-color: rgb(255, 240, 193);
12 | border-radius: 6px;
13 | }
14 |
--------------------------------------------------------------------------------
/src/hooks.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
3 | import type { RootState, AppDispatch } from './store';
4 |
5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
6 | export const useAppDispatch = () => useDispatch();
7 | export const useAppSelector: TypedUseSelectorHook = useSelector;
8 |
--------------------------------------------------------------------------------
/src/common/Card.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import './card.css';
3 |
4 | type cardProps = { children: ReactNode; large?: boolean; width?: string; height?: string };
5 |
6 | export const Card = ({ children, large, width, height }: cardProps) => {
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * This is the main entry point for your application, it's the first file
4 | * that runs in the main process.
5 | */
6 | entry: './src/index.ts',
7 | // Put your normal webpack config below here
8 | module: {
9 | rules: require('./webpack.rules'),
10 | },
11 | resolve: {
12 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json']
13 | },
14 | };
--------------------------------------------------------------------------------
/src/manager/components/ComponentGenBlock/ComponentPreview.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MarkdownWrapper } from '../../../common/MarkdownWrapper';
3 |
4 | export const ComponentPreview = ({ componentCode }: { componentCode: string }) => {
5 | return (
6 | <>
7 | Component Preview:
8 |
9 | >
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/src/creator/components/pages/DocumentationPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ReadmeSection } from '../ReadmeBlock';
3 |
4 | export const DocumentationPage = ({
5 | readme,
6 | setReadme,
7 | }: {
8 | readme: string;
9 | setReadme: (readme: string) => void;
10 | }) => {
11 | return (
12 |
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/common/SplashScreen/Splash.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import './splash.css';
4 |
5 | const Splash = () => {
6 | return (
7 | <>
8 | Reactirator
9 |
10 | >
11 | );
12 | };
13 |
14 | function render() {
15 | ReactDOM.render( , document.querySelector('#splash_screen'));
16 | }
17 |
18 | render();
19 |
--------------------------------------------------------------------------------
/src/creator/components/ScriptBlock/ListScripts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScriptItem } from './ScriptItem';
3 |
4 | export const ListScripts = ({ scripts }: { scripts: Record }) => {
5 | return (
6 |
7 | {Object.entries(scripts).map((script, i) => {
8 | return ;
9 | })}
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/src/common/markdown.css:
--------------------------------------------------------------------------------
1 | .markdown {
2 | @apply break-words text-sm py-0 my-0 overflow-y-auto h-64;
3 | }
4 |
5 | .markdown code {
6 | @apply font-mono inline rounded px-1;
7 | }
8 |
9 | .markdown pre {
10 | @apply rounded;
11 | }
12 |
13 | .markdown pre code {
14 | @apply block overflow-visible rounded-none;
15 | }
16 |
17 | /* Override pygments style background color. */
18 | .markdown .highlight pre {
19 | @apply bg-gray-200 border-gray-500 !important;
20 | }
21 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/index.ts:
--------------------------------------------------------------------------------
1 | export { DependenciesItem } from './DependenciesItem';
2 | export { DependenciesList } from './DependenciesList';
3 | export { DependencySelectedCard } from './DependencySelectedCard';
4 | export { DependenciesSearch } from './DependenciesSearch';
5 | export { ListDependenciesFound } from './ListDependenciesFound';
6 | export { DependencyItemFound } from './DependencyItemFound';
7 | export { DependencyModal } from './DependencyModal';
8 |
--------------------------------------------------------------------------------
/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | const rules = require('./webpack.rules');
2 | const plugins = require('./webpack.plugins');
3 |
4 | rules.push({
5 | test: /\.css$/,
6 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }, 'postcss-loader'],
7 | });
8 |
9 | module.exports = {
10 | module: {
11 | rules,
12 | },
13 | target: "electron-renderer",
14 | plugins: plugins,
15 | resolve: {
16 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'],
17 |
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/src/creator/components/pages/CommandPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card } from '../../../common/Card';
3 | import { ScriptSection } from '../ScriptBlock';
4 |
5 | export const CommandPage = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/manager/components/pages/TasksPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TasksList } from '../TasksBlock/TasksList';
3 | import { TasksDevelopmentPane } from '../TasksBlock/TasksDevelopmentPane';
4 |
5 | export const TasksPage = () => {
6 | return (
7 | <>
8 | Tasks:
9 |
10 |
11 |
12 |
13 | >
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/src/common/card.css:
--------------------------------------------------------------------------------
1 | .card {
2 | background: white;
3 | border: 1px solid #eaeaea;
4 | border-radius: 5px;
5 | text-align: left;
6 | padding: 1rem 2rem;
7 | flex: 1 1;
8 | display: flex;
9 | flex-direction: column;
10 | transition: box-shadow 0.2s ease, border 0.2s ease;
11 | }
12 |
13 | .card.lg {
14 | padding: 3rem;
15 | }
16 |
17 | .card:hover {
18 | transition: box-shadow 0.2s ease;
19 | box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
20 | border: 1px solid transparent;
21 | }
22 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/index.tsx:
--------------------------------------------------------------------------------
1 | export { ButtonAddPackage } from './ButtonAddPackage';
2 | export { ButtonCreation } from './ButtonCreation';
3 | export { ButtonRemovePackage } from './ButtonRemovePackage';
4 | export { ButtonRemoveScript } from './ButtonRemoveScript';
5 | export { ButtonSaveReadme } from './ButtonSaveReadme';
6 | export { ButtonGithubLogin } from './ButtonGithubLogin';
7 | export { ButtonRemoveFile } from './ButtonRemoveFile';
8 | export { ButtonEditFilename } from './ButtonEditFilename';
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "commonjs",
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "noImplicitAny": true,
8 | "sourceMap": true,
9 | "baseUrl": ".",
10 | "outDir": "dist",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "paths": {
14 | "*": ["node_modules/*"]
15 | },
16 | "jsx": "react"
17 | },
18 | "include": [
19 | "src/**/*"
20 | , "build_msi.ts" ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/creator/components/ScriptBlock/ScriptItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ButtonRemoveScript } from '../Buttons';
3 |
4 | export const ScriptItem = ({ script }: { script: any[] }) => {
5 | return (
6 |
7 | {script[0]}: {script[1]}
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/findStarter.ts:
--------------------------------------------------------------------------------
1 | const findStarter = (packageJson: any) => {
2 | if (packageJson.dependencies['react-scripts']) {
3 | return 'CRA';
4 | }
5 | if (packageJson.dependencies.next) {
6 | return 'next';
7 | }
8 | if (packageJson.dependencies.gatsby) {
9 | return 'gatsby';
10 | }
11 | if (packageJson.dependencies['remix'] || packageJson.dependencies['@remix-run/react']) {
12 | return 'remix';
13 | }
14 | if (packageJson?.devDependencies.vite) {
15 | return 'vite';
16 | }
17 | return '';
18 | };
19 |
20 | export default findStarter;
21 |
--------------------------------------------------------------------------------
/webpack.plugins.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const Dotenv = require('dotenv-webpack');
3 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
4 | const CopyWebpackPlugin = require('copy-webpack-plugin');
5 | const assets = [ 'assets' ]; // asset directories
6 |
7 | module.exports = [
8 | new ForkTsCheckerWebpackPlugin(),
9 | new Dotenv(),
10 | new CopyWebpackPlugin({
11 | patterns: assets.map(asset => ({
12 | from: path.resolve(__dirname, 'src', asset), to: path.resolve(__dirname, '.webpack/renderer', asset)
13 | }))
14 | }),
15 | ];
16 |
--------------------------------------------------------------------------------
/src/creator/components/Contexts/LoadingPackageProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, ReactNode, useContext, useState } from 'react';
2 |
3 | const LoadingContext = createContext(null);
4 |
5 | export const LoadingPackageProvider = ({ children }: { children: ReactNode }) => {
6 | const [loading, setLoading] = useState(false);
7 | return (
8 | {children}
9 | );
10 | };
11 |
12 | export const useLoading = () => {
13 | const { loading, setLoading } = useContext(LoadingContext);
14 |
15 | return { loading, setLoading };
16 | };
17 |
--------------------------------------------------------------------------------
/src/creator/components/PackageJsonBlock/CardPackageJson.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
3 | //import './packagejson.css';
4 |
5 | export const CardPackageJson = () => {
6 | const { packageJson } = usePackageJson();
7 |
8 | return (
9 |
13 |
{JSON.stringify(packageJson, null, 2)}
14 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/src/creator/components/pages/DetailsPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { formInputType } from '../../helpers/types';
4 |
5 | import { GithubSection } from '../GithubBlock';
6 | import { DetailsForm } from '../DetailsBlock';
7 |
8 | export const DetailsPage = ({
9 | input,
10 | setInput,
11 | }: {
12 | input: formInputType;
13 | setInput: (input: formInputType) => void;
14 | }) => {
15 | return (
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/index.tsx:
--------------------------------------------------------------------------------
1 | export { ItemPackage } from './ItemPackage';
2 | export { ItemPackageFound } from './ItemPackageFound';
3 | export { ItemPackageTooltip } from './ItemPackageTooltip';
4 | export { ListPackages } from './ListPackages';
5 | export { ListPackagesSelected } from './ListPackagesSelected';
6 | export { ListPackagesFound } from './ListPackagesFound';
7 | export { SearchPackages } from './SearchPackages';
8 | export { CardDependencies } from './CardDependencies';
9 | export { PackagesManager } from './PackagesManager';
10 | export { DependencyModalCreator } from './DependencyModalCreator';
11 |
--------------------------------------------------------------------------------
/src/creator/components/pages/PackagesPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { useDependencies } from '../Contexts/dependenciesProvider';
4 |
5 | import { Title } from '../../../common/Typo';
6 | import { PackagesManager } from '../PackageManagerBlock';
7 |
8 | export const PackagesPage = () => {
9 | const { listPackages, dispatch } = useDependencies();
10 |
11 | return (
12 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/manager/components/TasksBlock/TasksList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useAppSelector } from '../../../hooks';
3 | import { TasksItem } from './TasksItem';
4 |
5 | export const TasksList = () => {
6 | const scripts = useAppSelector((state) => state.tasks.tasks);
7 | const startScript = useAppSelector((state) => state.project.scriptDev);
8 |
9 | return (
10 |
11 | {Object.keys(scripts)
12 | .filter((ele) => ele !== startScript)
13 | .map((ele, i) => (
14 |
15 | ))}
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/src/common/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { SideNav } from './SideNav';
3 | import './layout.css';
4 | import { Toaster } from 'react-hot-toast';
5 |
6 | export const Layout = ({ children }: { children: ReactNode }) => {
7 | return (
8 |
9 |
10 |
{children}
11 |
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/analytics/mixpanel.service.ts:
--------------------------------------------------------------------------------
1 | import mixpanel from 'mixpanel-browser';
2 | import { nanoid } from 'nanoid';
3 | import electronStore from './electron-store.service';
4 |
5 | type MixpanelEvent = 'app-launch' | 'new-project' | 'project-open';
6 |
7 | const API_KEY = '4423465541cae31b055ca3980d5f8663';
8 |
9 | mixpanel.init(API_KEY);
10 |
11 | export const mixpanelTracker = (event: MixpanelEvent, data?: any) => {
12 | let userId = electronStore.get('uid');
13 | if (!userId) {
14 | userId = nanoid(12);
15 | electronStore.set('uid', userId);
16 | }
17 | mixpanel.identify(userId as string);
18 | data === null ? mixpanel.track(event) : mixpanel.track(event, { ...data });
19 | };
20 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .input {
6 | @apply block text-gray-700 text-center bg-gray-100 border border-gray-300 rounded-md py-2 px-3 placeholder-gray-500 focus:outline-none focus:bg-gray-50 focus:text-gray-900 focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm;
7 | }
8 |
9 | /* dark:placeholder-gray-400 dark:text-gray-50 dark:border-gray-600 dark:bg-gray-600 dark:focus:ring-1
10 | dark:focus:ring-indigo-500 dark:focus:bg-gray-900 dark:focus:border-transparent; */
11 |
12 | body {
13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
14 | }
15 |
--------------------------------------------------------------------------------
/src/manager/components/pages/DependenciesPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { DependenciesList, DependenciesSearch, DependencySelectedCard } from '../DependenciesBlock';
4 |
5 | export const DependenciesPage = () => {
6 | return (
7 |
8 |
9 |
Dependencies:
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/utils/runCmd.ts:
--------------------------------------------------------------------------------
1 | import child_process from 'child_process';
2 |
3 | export const runCmd = (cmd: string): Promise => {
4 | return new Promise((resolve, reject) => {
5 | const installProcess = child_process.exec(cmd, (error: Error, data: string) => {
6 | if (error) {
7 | reject(error);
8 | }
9 | resolve(data);
10 | });
11 | installProcess.stdout.on('data', (data: string) => {
12 | console.log(data);
13 | });
14 | installProcess.stderr.on('data', (data: string) => {
15 | console.log(data);
16 | });
17 | installProcess.on('error', (error: Error) => {
18 | console.error(`error: ${error.message}`);
19 | });
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/src/utils/formatDeps.ts:
--------------------------------------------------------------------------------
1 | import { depType } from '../manager/helpers/types';
2 |
3 | export const formatDeps = (dependencies: Record, isDevDep: boolean) => {
4 | const newDependencies: Record = {};
5 | Object.entries(dependencies).forEach((ele) => {
6 | if (ele[1][0] === '^') {
7 | newDependencies[ele[0]] = {
8 | name: ele[0],
9 | version: ele[1].slice(1),
10 | status: 'Idle',
11 | isDevDep: isDevDep,
12 | };
13 | } else {
14 | newDependencies[ele[0]] = {
15 | name: ele[0],
16 | version: ele[1],
17 | status: 'Idle',
18 | isDevDep: isDevDep,
19 | };
20 | }
21 | });
22 | return newDependencies;
23 | };
24 |
--------------------------------------------------------------------------------
/src/creator/helpers/toast.ts:
--------------------------------------------------------------------------------
1 | export const toastInstallStyle = {
2 | style: {
3 | margin: '16px',
4 | borderRadius: '10px',
5 | background: '#333',
6 | color: '#fff',
7 | },
8 | loading: {
9 | duration: 2000,
10 | },
11 | success: {
12 | duration: 8000,
13 | icon: '✅',
14 | },
15 | error: {
16 | duration: 8000,
17 | icon: '❌',
18 | },
19 | };
20 |
21 | export const toastInstallMsg = {
22 | loading: 'Installation start !',
23 | success: () => `Successfully installed !`,
24 | error: () => `An error happened`,
25 | };
26 |
27 | export const toastValidationStyle = {
28 | icon: '❌',
29 | style: {
30 | margin: '16px',
31 | borderRadius: '10px',
32 | background: '#333',
33 | color: '#fff',
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. Windows 10]
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/src/utils/readSrcFolder.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import { nanoid } from 'nanoid';
3 | import { FileStructureType } from '../manager/helpers/types';
4 |
5 | const readSrcFolder = async (path: string): Promise => {
6 | const projectSrc: FileStructureType[] = [];
7 | fs.readdir(path, function (err, files) {
8 | if (err) {
9 | return console.log('Unable to scan directory: ' + err);
10 | }
11 | files.forEach(function (file) {
12 | projectSrc.push({
13 | id: nanoid(6),
14 | name: file,
15 | ancestor: '',
16 | isFolder: false,
17 | path: `${path}/${file}`,
18 | });
19 | console.log(file);
20 | });
21 | });
22 | return projectSrc;
23 | };
24 |
25 | export default readSrcFolder;
26 |
--------------------------------------------------------------------------------
/src/creator/components/PackageCharts/Treemap.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | import React, { useEffect } from 'react';
3 | import { generateTreeMapWithD3 } from '../../utils/generateTreeMapWithD3';
4 | import { depStateType } from '../../helpers/types';
5 |
6 | export const Treemap = ({ listPackages }: { listPackages: depStateType }) => {
7 | useEffect(() => {
8 | generateTreeMapWithD3(listPackages);
9 | }, [listPackages]);
10 |
11 | return (
12 |
13 |
14 |
Treemap (TODO)
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export const TreemapMemoized = React.memo(Treemap);
22 | */
23 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import dependenciesSlice from './slices/dependenciesSlice';
3 | import projectReducer from './slices/projectSlice';
4 | import projectSrcSlice from './slices/projectSrcSlice';
5 | import taskSlice from './slices/taskSlice';
6 |
7 | export const store = configureStore({
8 | reducer: {
9 | project: projectReducer,
10 | tasks: taskSlice,
11 | dependencies: dependenciesSlice,
12 | projectSrc: projectSrcSlice,
13 | },
14 | });
15 |
16 | // Infer the `RootState` and `AppDispatch` types from the store itself
17 | export type RootState = ReturnType;
18 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
19 | export type AppDispatch = typeof store.dispatch;
20 |
--------------------------------------------------------------------------------
/src/creator/reducers/structureReducer.ts:
--------------------------------------------------------------------------------
1 | import { FileStructureType } from '../helpers/types';
2 |
3 | const structureReducer = (state: any, action: any) => {
4 | switch (action.type) {
5 | case 'ADD': {
6 | state.push(action.payload);
7 | return [...state];
8 | }
9 | case 'REMOVE': {
10 | const newState = state.filter(
11 | (ele: FileStructureType) =>
12 | ele.id !== action.payload.id && ele.ancestor !== action.payload.id
13 | );
14 | return [...newState];
15 | }
16 | case 'EDIT': {
17 | const index = state.findIndex((ele: FileStructureType) => ele.id === action.payload.id);
18 | state[index].name = action.payload.newName;
19 | return [...state];
20 | }
21 | }
22 | };
23 |
24 | export default structureReducer;
25 |
--------------------------------------------------------------------------------
/src/slices/projectSrcSlice.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3 | import initialStructure from '../manager/helpers/initialStructure';
4 | import { FileStructureType, projectSrcStateType } from '../manager/helpers/types';
5 |
6 | const initialState: projectSrcStateType = {
7 | projectSrc: initialStructure,
8 | };
9 |
10 | export const projectSrcSlice = createSlice({
11 | name: 'projectSrc',
12 | initialState,
13 | reducers: {
14 | initProjectSrc: (state: projectSrcStateType, action: PayloadAction) => {
15 | state.projectSrc = action.payload;
16 | },
17 | },
18 | });
19 |
20 | export const { initProjectSrc } = projectSrcSlice.actions;
21 |
22 | export default projectSrcSlice.reducer;
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/eslint-recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:import/errors",
12 | "plugin:import/warnings"
13 | ],
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "warnOnUnsupportedTypeScriptVersion": false
17 | },
18 | "rules": {
19 | "@typescript-eslint/explicit-module-boundary-types": "off",
20 | "@typescript-eslint/ban-ts-comment": "off",
21 | "@typescript-eslint/no-explicit-any": "off"
22 | },
23 | "settings": {
24 | "import/resolver": {
25 | "node": {
26 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/preload.ts:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import fetchNode from 'node-fetch';
3 |
4 | // Because of CORS issues with the npm registry
5 | // we are using preload and exposed this function as a workaround to do server-to-server requests.
6 | window.fetchWithNode = async (url) => {
7 | const rep = await fetchNode(url, {
8 | headers: {
9 | 'X-Requested-With': 'XMLHttpRequest',
10 | },
11 | });
12 | const res = await rep.json();
13 | return res;
14 | };
15 |
16 | window.fetchPostWithNode = async (url, data) => {
17 | const rep = await fetchNode(url, {
18 | headers: {
19 | Accept: 'application/json',
20 | 'Content-Type': 'application/json',
21 | 'X-Requested-With': 'XMLHttpRequest',
22 | },
23 | method: 'POST',
24 | body: JSON.stringify(data),
25 | });
26 | const res = await rep.json();
27 | return res;
28 | };
29 |
--------------------------------------------------------------------------------
/src/creator/components/Contexts/PackageJsonProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useContext } from 'react';
2 | import { actionJsonType } from '../../helpers/types';
3 |
4 | type PackageContextType = {
5 | packageJson: any;
6 | dispatchJson: (object: actionJsonType) => void;
7 | } | null;
8 |
9 | export const PackageContext = React.createContext(null);
10 |
11 | export const PackageJsonProvider = ({
12 | children,
13 | packageJson,
14 | dispatchJson,
15 | }: {
16 | children: ReactNode;
17 | packageJson: any;
18 | dispatchJson: (object: actionJsonType) => void;
19 | }) => {
20 | return (
21 |
22 | {children}
23 |
24 | );
25 | };
26 |
27 | export const usePackageJson = () => {
28 | return useContext(PackageContext);
29 | };
30 |
--------------------------------------------------------------------------------
/src/creator/helpers/initialState.ts:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | appname: '',
3 | description: '',
4 | version: '0.1.0',
5 | typescript: false,
6 | prettier: false,
7 | flow: false,
8 | tailwind: false,
9 | bootstrap: false,
10 | reactbootstrap: false,
11 | materialui: false,
12 | styledcomponents: false,
13 | normalize: false,
14 | reactrouter: false,
15 | proptypes: false,
16 | sourcemapexplorer: false,
17 | storybook: false,
18 | };
19 |
20 | export const initialStateVite = {
21 | appname: '',
22 | description: '',
23 | version: '0.1.0',
24 | typescript: false,
25 | prettier: false,
26 | flow: false,
27 | tailwind: false,
28 | bootstrap: false,
29 | materialui: false,
30 | reactbootstrap: false,
31 | normalize: false,
32 | reactrouter: false,
33 | proptypes: false,
34 | sourcemapexplorer: false,
35 | };
36 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonSaveReadme.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const ButtonSaveReadme = ({ hasChanged }: { hasChanged: boolean }) => {
4 | if (hasChanged) {
5 | return (
6 |
10 | Save
11 |
12 | );
13 | } else {
14 | return (
15 |
20 | Save
21 |
22 | );
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/webpack.rules.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // Add support for native node modules
3 | {
4 | test: /\.node$/,
5 | use: 'node-loader',
6 | },
7 | {
8 | test: /\.(m?js|node)$/,
9 | parser: { amd: false },
10 | use: {
11 | loader: '@vercel/webpack-asset-relocator-loader',
12 | options: {
13 | outputAssetBase: 'native_modules',
14 | },
15 | },
16 | },
17 | {
18 | test: /\.tsx?$/,
19 | exclude: /(node_modules|\.webpack)/,
20 | use: {
21 | loader: 'ts-loader',
22 | options: {
23 | transpileOnly: true
24 | }
25 | }
26 | },
27 | {
28 | test: /\.(png|jpe?g|gif|ico|icns)$/i,
29 | use: [
30 | {
31 | loader: 'file-loader',
32 | options: {
33 | name: 'img/[name].[ext]',
34 | publicPath: '../.'
35 | }
36 | },
37 | ],
38 | },
39 | ];
40 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonCreation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import toast from 'react-hot-toast';
3 | import { ipcRenderer } from 'electron';
4 |
5 | import { toastValidationStyle } from '../../helpers/toast';
6 | import { validateProjectName } from '../../../utils/validateInput';
7 | import { formInputType } from '../../helpers/types';
8 | import { Button } from '../../../common/Button';
9 |
10 | export const ButtonCreation = ({ input }: { input: formInputType }) => {
11 | const handleSubmit = async (e: React.MouseEvent): Promise => {
12 | e.preventDefault();
13 | if (!validateProjectName(input.appname)) {
14 | toast('The project name is invalid !', toastValidationStyle);
15 | } else {
16 | ipcRenderer.send('open-directory', input);
17 | }
18 | };
19 |
20 | return Install ;
21 | };
22 |
--------------------------------------------------------------------------------
/src/creator/components/pages/FeaturesPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { formInputType, starterType } from '../../helpers/types';
4 |
5 | import { Title } from '../../../common/Typo';
6 | import { FeaturesList } from '../FeaturesBlock';
7 | import { featuresListVite, featuresListCRA } from '../../helpers/featuresLists';
8 |
9 | export const FeaturesPage = ({
10 | input,
11 | setInput,
12 | starter,
13 | }: {
14 | input: formInputType;
15 | setInput: (input: formInputType) => void;
16 | starter: starterType;
17 | }) => {
18 | return (
19 |
20 |
21 |
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/creator/components/GithubBlock/GithubSection.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { useGithub } from '../Contexts/GithubProvider';
4 | import { GithubForm } from './GithubForm';
5 | import { ButtonGithubLogin } from '../Buttons';
6 | import { Title } from '../../../common/Typo';
7 |
8 | export const GithubSection = () => {
9 | const [loading, setLoading] = useState(false);
10 | const { github } = useGithub();
11 |
12 | if (loading)
13 | return (
14 |
15 |
Authentication...
16 |
17 | );
18 | return (
19 |
20 |
21 | {!github.token ? : }
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/creator/components/pages/ArchitecturePage.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | import { structureStateType } from '../../helpers/types';
3 |
4 | import { CreateComponent, CreateFolder, TreeFolder } from '../ArchitectureBlock';
5 |
6 | export const ArchitecturePage = ({
7 | structure,
8 | dispatch,
9 | }: {
10 | structure: structureStateType;
11 | dispatch: Dispatch;
12 | }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/PackagesManager.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 |
3 | import { actionPackageType, depStateType } from '../../helpers/types';
4 | import { LoadingPackageProvider } from '../Contexts/LoadingPackageProvider';
5 |
6 | import { SearchPackages } from './SearchPackages';
7 | import { ListPackages } from './ListPackages';
8 |
9 | export const PackagesManager = ({
10 | listPackages,
11 | dispatchPackages,
12 | }: {
13 | listPackages: depStateType;
14 | dispatchPackages: Dispatch;
15 | }) => {
16 | return (
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/__test__/helpersElectron.ts:
--------------------------------------------------------------------------------
1 | import { findLatestBuild, parseElectronApp } from 'electron-playwright-helpers';
2 | import { Page, _electron as electron } from 'playwright';
3 | import { pause } from '../utils/pause';
4 |
5 | export const startApp = async () => {
6 | const latestBuild = findLatestBuild();
7 | const appInfo = parseElectronApp(latestBuild);
8 |
9 | const electronApp = await electron.launch({
10 | args: [appInfo.main],
11 | executablePath: appInfo.executable,
12 | });
13 |
14 | await electronApp.firstWindow();
15 |
16 | while (electronApp.windows().length === 2) {
17 | await pause(100);
18 | }
19 |
20 | const windows = electronApp.windows();
21 |
22 | if (windows.length !== 1) {
23 | throw new Error('too many windows open');
24 | }
25 |
26 | const appWindow: Page = windows[0];
27 | appWindow.on('console', console.log);
28 |
29 | return { appWindow, appInfo, electronApp };
30 | };
31 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ItemPackageTooltip.tsx:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import React from 'react';
3 | import { packageFoundType } from '../../helpers/types';
4 |
5 | export const ItemPackageTooltip = ({
6 | data,
7 | mouse,
8 | isShown,
9 | }: {
10 | data: packageFoundType;
11 | mouse: any;
12 | isShown: boolean;
13 | }) => {
14 | if (!isShown) return null;
15 | return (
16 |
20 |
21 | {data.name}@{data.version}
22 |
23 |
{data.description}
24 |
Npm score: {data.score.toPrecision(3)}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/ReadmeSection.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ReadmeEdit } from './ReadmeEdit';
3 | import { ReadmeHeader } from './ReadmeHeader';
4 | import { ReadmePreview } from './ReadmePreview';
5 |
6 | export const ReadmeSection = ({
7 | readme,
8 | setReadme,
9 | }: {
10 | readme: string;
11 | setReadme: (input: any) => void;
12 | }) => {
13 | const [tab, setTab] = useState('Edit');
14 |
15 | return (
16 |
20 |
21 |
22 | {tab === 'Edit' ? (
23 |
24 | ) : (
25 |
26 | )}
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/creator/helpers/steps.ts:
--------------------------------------------------------------------------------
1 | export const stepsCRA = [
2 | { index: 0, name: 'Overview', href: '/creator', status: 'current' },
3 | { index: 1, name: 'Features', href: '/creator/features', status: 'upcoming' },
4 | { index: 2, name: 'Packages', href: '/creator/packages', status: 'upcoming' },
5 | { index: 3, name: 'Components', href: '/creator/components', status: 'upcoming' },
6 | { index: 4, name: 'Installation', href: '/creator/installation', status: 'upcoming' },
7 | ];
8 |
9 | export const stepsVite = [
10 | { index: 0, name: 'Overview', href: '/creatorVite', status: 'current' },
11 | { index: 1, name: 'Features', href: '/creatorVite/features', status: 'upcoming' },
12 | { index: 2, name: 'Packages', href: '/creatorVite/packages', status: 'upcoming' },
13 | { index: 3, name: 'Components', href: '/creatorVite/components', status: 'upcoming' },
14 | { index: 4, name: 'Installation', href: '/creatorVite/installation', status: 'upcoming' },
15 | ];
16 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/CardDependencies.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ClipLoader from 'react-spinners/ClipLoader';
3 |
4 | import { useLoading } from '../Contexts/LoadingPackageProvider';
5 | import { Card } from '../../../common/Card';
6 |
7 | export const CardDependencies = ({
8 | children,
9 | title,
10 | listPackages,
11 | }: {
12 | children: React.ReactNode;
13 | title: string;
14 | listPackages: { name: string; size: number }[];
15 | }) => {
16 | const { loading } = useLoading();
17 |
18 | return (
19 |
20 |
21 |
22 | {title} ({listPackages.length}) :
23 |
24 |
25 | {loading ? : children}
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/creator/components/Contexts/dependenciesProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, ReactNode, useContext, useReducer } from 'react';
2 | import { depStateType } from '../../helpers/types';
3 | import dependenciesReducer from '../../reducers/dependenciesReducer';
4 |
5 | const dependenciesContext = createContext(null);
6 |
7 | const initialDeps: depStateType = {
8 | dependencies: [],
9 | devDependencies: [],
10 | };
11 |
12 | export const DependenciesProvider = ({ children }: { children: ReactNode }) => {
13 | const [listPackages, dispatch] = useReducer(
14 | dependenciesReducer,
15 | JSON.parse(JSON.stringify(initialDeps))
16 | );
17 |
18 | return (
19 |
20 | {children}
21 |
22 | );
23 | };
24 |
25 | export const useDependencies = () => {
26 | const { listPackages, dispatch } = useContext(dependenciesContext);
27 |
28 | return { listPackages, dispatch };
29 | };
30 |
--------------------------------------------------------------------------------
/src/manager/ManagerMenuSelection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { FolderOpenIcon } from '@heroicons/react/outline';
4 |
5 | export const ManagerMenuSelection = () => {
6 | return (
7 |
8 |
9 |
Open a project.
10 |
Pick a React project and start working.
11 |
12 |
16 | Open project
17 |
18 |
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/ReadmeEdit.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent } from 'react';
2 |
3 | export const ReadmeEdit = ({
4 | readme,
5 | setReadme,
6 | }: {
7 | readme: string;
8 | setReadme: (input: any) => void;
9 | }) => {
10 | const handleChange = (e: ChangeEvent): void => {
11 | setReadme(e.target.value);
12 | };
13 |
14 | return (
15 |
16 |
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/creator/components/Contexts/GithubProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext, useContext } from 'react';
2 | import { ReactNode } from 'react-markdown';
3 |
4 | export type GithubStateType = {
5 | token: string;
6 | reponame: string;
7 | visibility: 'public' | 'private';
8 | };
9 |
10 | type GithubContextType = {
11 | github: GithubStateType;
12 | setGithub: (github: GithubStateType) => void;
13 | };
14 |
15 | const initialState: GithubStateType = {
16 | token: '',
17 | reponame: '',
18 | visibility: 'public',
19 | };
20 |
21 | const githubContext = createContext(null);
22 |
23 | export const GithubProvider = ({ children }: { children: ReactNode }) => {
24 | const [github, setGithub] = useState(initialState);
25 |
26 | return {children} ;
27 | };
28 |
29 | export const useGithub = () => {
30 | const { github, setGithub } = useContext(githubContext);
31 | return { github, setGithub };
32 | };
33 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonRemoveScript.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
3 |
4 | export const ButtonRemoveScript = ({ name }: { name: string }) => {
5 | const { packageJson, dispatchJson } = usePackageJson();
6 |
7 | const handleClick = () => {
8 | const newScripts = { ...packageJson.scripts };
9 | delete newScripts[name];
10 | dispatchJson({ type: 'CHANGE_SCRIPTS', payload: { scripts: { ...newScripts } } });
11 | };
12 |
13 | return (
14 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/creator/components/FeaturesBlock/FeaturesList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { featureType, formInputType } from '../../helpers/types';
4 | import { FeatureSwitch } from './';
5 |
6 | export const FeaturesList = ({
7 | input,
8 | setInput,
9 | listFeatures,
10 | }: {
11 | input: formInputType;
12 | setInput: (input: formInputType) => void;
13 | listFeatures: featureType[];
14 | }) => {
15 | return (
16 |
17 | {listFeatures.map((feature) => (
18 |
25 | {feature.title}
26 | {feature.description}
27 |
28 | ))}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonEditFilename.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react';
2 |
3 | export const ButtonEditFilename = ({ setIsEdit }: { setIsEdit: (isEdit: boolean) => void }) => {
4 | const editNameItem = (e: MouseEvent) => {
5 | e.stopPropagation();
6 | setIsEdit(true);
7 | };
8 |
9 | return (
10 |
11 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/DependenciesList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { useAppSelector } from '../../../hooks';
4 |
5 | import { DependenciesItem } from './DependenciesItem';
6 |
7 | export const DependenciesList = () => {
8 | const dependencies = useAppSelector((state) => state.dependencies);
9 |
10 | return (
11 |
12 | {Object.entries(dependencies.dependencies).map((ele) => (
13 |
20 | ))}
21 | {Object.entries(dependencies.devDependencies).map((ele) => (
22 |
29 | ))}
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import { shell } from 'electron';
2 | // eslint-disable-next-line @typescript-eslint/no-var-requires
3 | const remote = require('@electron/remote');
4 | import './index.css';
5 | import './App';
6 |
7 | const win = remote.getCurrentWindow();
8 |
9 | document.onreadystatechange = () => {
10 | if (document.readyState == 'complete') {
11 | handleWindowControls();
12 | addExternalLink();
13 | }
14 | };
15 |
16 | window.onbeforeunload = () => {
17 | win.removeAllListeners();
18 | };
19 |
20 | function addExternalLink() {
21 | document.getElementById('button_git').addEventListener('click', () => {
22 | shell.openExternal('https://github.com/Leopold-V/Reactirator.git');
23 | });
24 | document.getElementById('open_react').addEventListener('click', () => {
25 | shell.openExternal('https://reactjs.org');
26 | });
27 | }
28 |
29 | function handleWindowControls() {
30 | document.getElementById('min-button').addEventListener('click', () => {
31 | win.minimize();
32 | });
33 |
34 | document.getElementById('close-button').addEventListener('click', () => {
35 | win.close();
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 leopold-v
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/creator/components/ArchitectureBlock/TreeFolder.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | import { Title } from '../../../common/Typo';
3 | import { FileStructureType, structureStateType } from '../../helpers/types';
4 | import { TreeItem } from './TreeItem';
5 |
6 | export const TreeFolder = ({
7 | structure,
8 | dispatchStructure,
9 | }: {
10 | structure: structureStateType;
11 | dispatchStructure: Dispatch;
12 | }) => {
13 | const rootItem = structure.filter((ele: FileStructureType) => ele.ancestor === '');
14 |
15 | return (
16 |
17 |
18 |
19 | {rootItem.map((ele) => (
20 |
29 | ))}
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/creator/components/PackageCharts/PackagesSize.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import listDepsSize from '../../../utils/listDepsSize';
3 | import { depStateType } from '../../helpers/types';
4 | import { ChartSize } from '.';
5 |
6 | export const PackagesSize = ({
7 | listPackages,
8 | baseSize,
9 | }: {
10 | listPackages: depStateType;
11 | baseSize: number;
12 | }) => {
13 | const [depsSize, setDepsSize] = useState(0);
14 | const [devDepsSize, setDevDepsSize] = useState(0);
15 |
16 | useEffect(() => {
17 | setDepsSize(listDepsSize(listPackages.dependencies));
18 | setDevDepsSize(listDepsSize(listPackages.devDependencies));
19 | }, [listPackages]);
20 |
21 | return (
22 |
23 |
Install size (kb) :
24 |
25 |
26 | );
27 | };
28 |
29 | export const PackagesSizeMemoized = React.memo(PackagesSize);
30 |
--------------------------------------------------------------------------------
/src/manager/components/TasksBlock/TaskStatut.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BadgeCheckIcon, CogIcon, ExclamationIcon } from '@heroicons/react/outline';
3 | import { taskStateType } from '../../helpers/types';
4 |
5 | const displayStateIcon = (taskState: string) => {
6 | switch (taskState) {
7 | case 'Success':
8 | return ;
9 | case 'Error':
10 | return ;
11 | case 'Pending':
12 | return ;
13 | }
14 | };
15 |
16 | const statusText: Record = {
17 | Success: 'Success',
18 | Error: 'Error',
19 | Pending: 'Pending...',
20 | };
21 |
22 | export const TaskStatut = ({ taskState }: { taskState: taskStateType }) => {
23 | if (taskState === 'Idle') {
24 | return Idle
;
25 | } else {
26 | return (
27 |
28 | {displayStateIcon(taskState)}
29 | {statusText[taskState]}
30 |
31 | );
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/ListDependenciesFound.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 |
3 | import { dependencyFoundType } from '../../../manager/helpers/types';
4 | import { useModal } from '../../../hooks/useModal';
5 |
6 | import { DependencyItemFound } from './DependencyItemFound';
7 | import { DependencyModal } from './DependencyModal';
8 |
9 | export const ListDependenciesFound = ({ results }: { results: dependencyFoundType[] }) => {
10 | const [open, toggleModal] = useModal();
11 | const [depData, setDepData] = useState(null);
12 | const ref = useRef(null);
13 |
14 | return (
15 | <>
16 |
20 | {results.map((ele: dependencyFoundType) => (
21 |
27 | ))}
28 |
29 |
30 | >
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/creator/components/PackageCharts/ChartSize.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Pie } from 'react-chartjs-2';
3 |
4 | export const ChartSize = ({
5 | depsSize,
6 | devDepsSize,
7 | baseSize,
8 | }: {
9 | depsSize: number;
10 | devDepsSize: number;
11 | baseSize: number;
12 | }) => {
13 | const data = {
14 | labels: ['Base', 'Dependencies', 'Dev dependencies'],
15 | datasets: [
16 | {
17 | label: 'Install size',
18 | data: [baseSize, depsSize, devDepsSize],
19 | backgroundColor: [
20 | 'rgba(54, 162, 235, 1)',
21 | 'rgba(75, 192, 192, 1)',
22 | 'rgba(255, 206, 86, 1)',
23 | ],
24 | borderColor: [
25 | 'rgba(54, 162, 235, 0.2)',
26 | 'rgba(75, 192, 192, 0.2)',
27 | 'rgba(255, 206, 86, 0.2)',
28 | ],
29 | borderWidth: 1,
30 | },
31 | ],
32 | };
33 |
34 | return (
35 |
36 | {
37 | // @ts-ignore
38 |
44 | }
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/manager/components/Terminal/TerminalOutput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { XTerm } from 'xterm-for-react';
3 | import { FitAddon } from 'xterm-addon-fit';
4 | import { useAppSelector } from '../../../hooks';
5 |
6 | type terminalOutputProps = {
7 | taskName: string;
8 | inModal?: boolean;
9 | };
10 |
11 | export const TerminalOutput = React.memo(({ taskName, inModal = false }) => {
12 | const xtermRef = useRef(null);
13 | const logs = useAppSelector((state) => state.tasks.tasks[taskName].logs);
14 |
15 | const fitAddon = new FitAddon();
16 |
17 | useEffect(() => {
18 | xtermRef.current.terminal.setOption('fontSize', 14);
19 | xtermRef.current.terminal.setOption('convertEol', true);
20 | xtermRef.current.terminal.setOption('theme', { background: '#2e3748' });
21 | fitAddon.fit();
22 | }, []);
23 |
24 | useEffect(() => {
25 | xtermRef.current.terminal.clear();
26 | xtermRef.current.terminal.writeln(logs);
27 | }, [logs]);
28 |
29 | return (
30 |
35 | );
36 | });
37 |
--------------------------------------------------------------------------------
/src/manager/reducers/taskReducer.tsx:
--------------------------------------------------------------------------------
1 | import { actionTaskType } from '../helpers/types';
2 |
3 | const taskReducer = (state: any, { type, payload }: actionTaskType) => {
4 | console.log(type);
5 | switch (type) {
6 | case 'SWITCH':
7 | state.enabled = !state.enabled;
8 | return { ...state };
9 | case 'IDLE':
10 | state.taskState = 'Idle';
11 | state.enabled = false;
12 | state.isKill = false;
13 | return { ...state };
14 | case 'PENDING':
15 | state.taskState = 'Pending';
16 | state.enabled = true;
17 | return { ...state };
18 | case 'FINISH':
19 | state.taskState = 'Success';
20 | state.enabled = false;
21 | if (state.isKill) {
22 | state.taskState = 'Error';
23 | }
24 | state.isKill = false;
25 | return { ...state };
26 | case 'STOP':
27 | state.taskState = 'Error';
28 | state.enabled = false;
29 | state.isKill = true;
30 | return { ...state };
31 | case 'ERROR':
32 | state.taskState = 'Error';
33 | state.enabled = false;
34 | state.isKill = false;
35 | return { ...state };
36 | default:
37 | throw new Error();
38 | }
39 | };
40 |
41 | export default taskReducer;
42 |
--------------------------------------------------------------------------------
/src/creator/components/InstallationBlock/TerminalOutputInstallation.tsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import React, { useEffect, useRef, useState } from 'react';
3 | import { XTerm } from 'xterm-for-react';
4 | import { FitAddon } from 'xterm-addon-fit';
5 | import { Hook, Unhook } from 'console-feed';
6 |
7 | export const TerminalOutputInstallation = () => {
8 | const xtermRef = useRef(null);
9 |
10 | const fitAddon = new FitAddon();
11 | const [logs, setLogs] = useState('');
12 |
13 | useEffect(() => {
14 | Hook(
15 | window.console,
16 | (log) => {
17 | setLogs(log.data[0] + '\n'); // if (logs) => logs + log.data[0] then all logs are displayed with each new log.
18 | },
19 | false
20 | );
21 | return () => Unhook(window.console);
22 | }, []);
23 |
24 | useEffect(() => {
25 | xtermRef.current.terminal.setOption('fontSize', 14);
26 | xtermRef.current.terminal.setOption('convertEol', true);
27 | xtermRef.current.terminal.setOption('theme', { background: '#2e3748' });
28 | fitAddon.fit();
29 | }, []);
30 |
31 | useEffect(() => {
32 | xtermRef.current.terminal.writeln(logs);
33 | }, [logs]);
34 |
35 | return ;
36 | };
37 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/DependencyItemFound.tsx:
--------------------------------------------------------------------------------
1 | import { CheckIcon } from '@heroicons/react/outline';
2 | import React from 'react';
3 | import { useAppSelector } from '../../../hooks';
4 | import { dependencyFoundType } from '../../../manager/helpers/types';
5 |
6 | export const DependencyItemFound = ({
7 | dep,
8 | setDepData,
9 | toggleModal,
10 | }: {
11 | dep: dependencyFoundType;
12 | setDepData: (dep: dependencyFoundType) => void;
13 | toggleModal: () => void;
14 | }) => {
15 | const dependencies = useAppSelector((state) => state.dependencies);
16 |
17 | const handleOpen = async () => {
18 | setDepData(dep);
19 | toggleModal();
20 | };
21 |
22 | return (
23 |
28 | {dep.name}
29 |
30 | {(dependencies.dependencies[dep.name] || dependencies.dependencies[dep.name]) && (
31 |
32 | )}
33 |
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/utils/promisifyFs.ts:
--------------------------------------------------------------------------------
1 | //@ts-nocheck
2 | import fs from 'fs';
3 |
4 | export const promisifyReadFs = (fullpath: string): Promise => {
5 | return new Promise((resolve, reject) => {
6 | fs.readFile(`${fullpath}`, 'utf8', (err: Error, data: string) => {
7 | if (err) {
8 | reject(err);
9 | }
10 | resolve(data);
11 | });
12 | });
13 | };
14 |
15 | export const promisifyWriteFs = (fullpath: string, dataToWrite: string): Promise => {
16 | return new Promise((resolve, reject) => {
17 | fs.writeFile(`${fullpath}`, dataToWrite, (err: Error, data: string) => {
18 | if (err) {
19 | reject(err);
20 | }
21 | resolve(data);
22 | });
23 | });
24 | };
25 |
26 | export const promisifyAppendFs = (fullpath: string, dataToWrite: string): Promise => {
27 | return new Promise((resolve, reject) => {
28 | fs.appendFile(`${fullpath}`, dataToWrite, (err: Error, data: string) => {
29 | if (err) {
30 | reject(err);
31 | }
32 | resolve(data);
33 | });
34 | });
35 | };
36 |
37 | export const fileExist = (path: string): Promise => {
38 | return new Promise((resolve) => {
39 | if (fs.existsSync(path)) {
40 | resolve(true);
41 | } else resolve(false);
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonRemoveFile.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react';
2 | import { Dispatch } from 'react';
3 |
4 | export const ButtonRemoveFile = ({
5 | id,
6 | dispatchStructure,
7 | }: {
8 | id: string;
9 | dispatchStructure: Dispatch;
10 | }) => {
11 | const removeItem = (e: MouseEvent) => {
12 | e.stopPropagation();
13 | dispatchStructure({ type: 'REMOVE', payload: { id: id } });
14 | };
15 |
16 | return (
17 |
18 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ListPackagesSelected.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | import { Droppable } from 'react-beautiful-dnd';
3 |
4 | import { actionPackageType } from '../../helpers/types';
5 | import { ItemPackage } from '.';
6 |
7 | export const ListPackagesSelected = ({
8 | type,
9 | listPackages,
10 | dispatchPackages,
11 | }: {
12 | type: 'dependencies' | 'devDependencies';
13 | listPackages: { name: string; size: number }[];
14 | dispatchPackages: Dispatch;
15 | }) => {
16 | return (
17 |
18 | {(provided) => (
19 |
24 | {listPackages.length === 0 && (
25 | Empty list
26 | )}
27 | {listPackages.map((ele, index) => (
28 |
35 | ))}
36 | {provided.placeholder}
37 |
38 | )}
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/MarkdownWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | //@ts-ignore
3 | import ReactMarkdown from 'react-markdown';
4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
5 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
6 | import gfm from 'remark-gfm';
7 | import './markdown.css';
8 |
9 | const components = {
10 | code({
11 | node,
12 | inline,
13 | className,
14 | children,
15 | ...props
16 | }: {
17 | node: any;
18 | inline: any;
19 | className: string;
20 | children: ReactNode;
21 | }) {
22 | const match = /language-(\w+)/.exec(className || '');
23 |
24 | return !inline && match ? (
25 |
32 | ) : (
33 |
34 | {children}
35 |
36 | );
37 | },
38 | };
39 |
40 | export const MarkdownWrapper = ({ content }: { content: string }) => {
41 | return (
42 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/src/common/MarkdownWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import ReactMarkdown from 'react-markdown';
3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4 | import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
5 | import gfm from 'remark-gfm';
6 | // eslint-disable-next-line import/no-unresolved
7 | import './markdown.css';
8 |
9 | const components = {
10 | code({
11 | node,
12 | inline,
13 | className,
14 | children,
15 | ...props
16 | }: {
17 | node: any;
18 | inline: any;
19 | className: string;
20 | children: ReactNode;
21 | }) {
22 | const match = /language-(\w+)/.exec(className || '');
23 | return !inline && match ? (
24 |
31 | ) : (
32 |
33 | {children}
34 |
35 | );
36 | },
37 | };
38 |
39 | export const MarkdownWrapper = ({ content }: { content: string }) => {
40 | return (
41 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/ReadmeHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEvent } from 'react';
2 |
3 | export const ReadmeHeader = ({ tab, setTab }: { tab: string; setTab: (tab: string) => void }) => {
4 | const handleTab = (e: MouseEvent) => {
5 | const target = e.target as HTMLLIElement;
6 | setTab(target.innerText);
7 | };
8 |
9 | return (
10 |
11 |
Readme.md
12 |
13 |
20 | Edit
21 |
22 |
29 | Preview
30 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/creator/helpers/initialPackageJson.ts:
--------------------------------------------------------------------------------
1 | export const initialPackageJsonCRA = {
2 | name: '',
3 | version: '0.1.0',
4 | description: '',
5 | private: true,
6 | dependencies: {
7 | '@testing-library/jest-dom': '',
8 | '@testing-library/react': '',
9 | '@testing-library/user-event': '',
10 | react: '',
11 | 'react-dom': '',
12 | 'react-scripts': '',
13 | 'web-vitals': '',
14 | },
15 | scripts: {
16 | start: 'react-scripts start',
17 | build: 'react-scripts build',
18 | test: 'react-scripts test',
19 | eject: 'react-scripts eject',
20 | },
21 | eslintConfig: {
22 | extends: ['react-app', 'react-app/jest'],
23 | },
24 | browserslist: {
25 | production: ['>0.2%', 'not dead', 'not op_mini all'],
26 | development: ['last 1 chrome version', 'last 1 firefox version', 'last 1 safari version'],
27 | },
28 | devDependencies: {},
29 | };
30 |
31 | export const initialPackageJsonVite = {
32 | name: '',
33 | private: true,
34 | version: '0.1.0',
35 | type: 'module',
36 | scripts: {
37 | dev: 'vite',
38 | build: 'tsc && vite build',
39 | preview: 'vite preview',
40 | },
41 | dependencies: {
42 | react: '',
43 | 'react-dom': '',
44 | },
45 | devDependencies: {
46 | '@types/react': '',
47 | '@types/react-dom': '',
48 | '@vitejs/plugin-react': '',
49 | typescript: '',
50 | vite: '',
51 | },
52 | };
53 |
--------------------------------------------------------------------------------
/src/slices/projectSlice.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3 | import { projectStateType } from '../manager/helpers/types';
4 |
5 | const initialState: projectStateType = {
6 | projectName: '',
7 | projectPath: '',
8 | starter: '',
9 | scriptDev: '',
10 | isTypescript: false,
11 | loading: true,
12 | };
13 |
14 | export const projectSlice = createSlice({
15 | name: 'project',
16 | initialState,
17 | reducers: {
18 | resetProject: (state) => {
19 | state.loading = initialState.loading;
20 | state.projectName = initialState.projectName;
21 | state.projectPath = initialState.projectPath;
22 | state.starter = initialState.starter;
23 | state.scriptDev = initialState.scriptDev;
24 | state.isTypescript = initialState.isTypescript;
25 | },
26 | initProject: (state: projectStateType, action: PayloadAction) => {
27 | state.loading = false;
28 | state.projectName = action.payload.projectName;
29 | state.projectPath = action.payload.projectPath;
30 | state.starter = action.payload.starter;
31 | state.scriptDev = action.payload.scriptDev;
32 | state.projectPath = action.payload.projectPath;
33 | state.isTypescript = action.payload.isTypescript;
34 | },
35 | },
36 | });
37 |
38 | export const { resetProject, initProject } = projectSlice.actions;
39 |
40 | export default projectSlice.reducer;
41 |
--------------------------------------------------------------------------------
/src/utils/validateInput.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-control-regex */
2 |
3 | export const validateProjectName = (input: string): boolean => {
4 | const filenameReserved = /[<>:"/\\|?*\u0000-\u001F]/g;
5 | const filenameReservedWindowsNames = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
6 | const rg1 = /^[^\\/:*?"<>|]+$/; // forbidden characters \ / : * ? " < > |
7 | const rg2 = /^\./; // cannot start with dot (.)
8 | const rg3 = /[A-Z]/; // cannot have uppercase letter
9 | if (!input || input.length > 255) {
10 | return false;
11 | }
12 | if (filenameReserved.test(input) || filenameReservedWindowsNames.test(input)) {
13 | return false;
14 | }
15 | if ((rg1.test(input) && rg2.test(input)) || rg3.test(input)) {
16 | return false;
17 | }
18 | return true;
19 | };
20 |
21 | export const validateFileName = (input: string): boolean => {
22 | const filenameReserved = /[<>:"/\\|?*\u0000-\u001F]/g;
23 | const filenameReservedWindowsNames = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
24 | const rg1 = /^[^\\/:*?"<>|]+$/; // forbidden characters \ / : * ? " < > |
25 | const rg2 = /[.]/; // cannot have dot (.)
26 | const rg3 = /\s/g; // cannot have space
27 | if (!input || input.length > 255) {
28 | return false;
29 | }
30 | if (filenameReserved.test(input) || filenameReservedWindowsNames.test(input)) {
31 | return false;
32 | }
33 | if ((rg1.test(input) && rg2.test(input)) || rg3.test(input)) {
34 | return false;
35 | }
36 | return true;
37 | };
38 |
--------------------------------------------------------------------------------
/src/common/ScoreNpmPophover.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type scoreNpmPophoverProps = {
4 | scoreDetail: {
5 | quality: number;
6 | popularity: number;
7 | maintenance: number;
8 | };
9 | open: boolean;
10 | };
11 |
12 | export const ScoreNpmPophover = ({ scoreDetail, open }: scoreNpmPophoverProps) => {
13 | if (!open) return null;
14 | return (
15 |
16 |
17 |
18 |
19 |
Score:
20 |
21 |
22 | Quality: {' '}
23 | {scoreDetail.quality.toFixed(3)}
24 |
25 |
26 | Popularity: {' '}
27 | {scoreDetail.popularity.toFixed(3)}
28 |
29 |
30 | Maintenance: {' '}
31 | {scoreDetail.maintenance.toFixed(3)}
32 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ItemPackage.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | import { Draggable } from 'react-beautiful-dnd';
3 | import { actionPackageType } from '../../helpers/types';
4 | import { ButtonRemovePackage } from '../Buttons';
5 |
6 | export const ItemPackage = ({
7 | type,
8 | packageName,
9 | index,
10 | dispatchPackages,
11 | }: {
12 | type: 'dependencies' | 'devDependencies';
13 | packageName: string;
14 | index: number;
15 | dispatchPackages: Dispatch;
16 | }) => {
17 | return (
18 |
19 | {(provided, snapshot) => {
20 | console.log(snapshot.isDragging);
21 | return (
22 |
29 |
34 | {packageName}
35 |
36 | );
37 | }}
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | branches: [main]
7 |
8 | jobs:
9 | build_on_linux:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@main
14 | with:
15 | node-version: 14
16 | - name: install dependencies
17 | run: npm install
18 | - name: Running Prettier Code Formatter
19 | run: npm run format:check --if-present
20 | - name: build
21 | run: npm run make
22 |
23 | build_on_mac:
24 | runs-on: macos-latest
25 | steps:
26 | - uses: actions/checkout@v2
27 | - uses: actions/setup-node@main
28 | with:
29 | node-version: 14
30 | - name: install dependencies
31 | run: npm install
32 | - name: Running Prettier Code Formatter
33 | run: npm run format:check --if-present
34 | - name: build
35 | run: npm run make
36 |
37 | build_on_win:
38 | runs-on: windows-latest
39 | steps:
40 | - name: Set git to use LF
41 | run: |
42 | git config --global core.autocrlf false
43 | git config --global core.eol lf
44 | - uses: actions/checkout@v2
45 | - uses: actions/setup-node@main
46 | with:
47 | node-version: 14
48 | - name: install dependencies
49 | run: npm install
50 | - name: Running Prettier Code Formatter
51 | run: npm run format:check --if-present
52 | - name: build
53 | run: npm run make
54 |
--------------------------------------------------------------------------------
/src/services/package.service.ts:
--------------------------------------------------------------------------------
1 | const API_URL = 'https://api.npms.io/v2/search?q=';
2 | const API_URL2 = 'https://api.npms.io/v2/package/';
3 | const REGISTRY_URL = 'https://registry.npmjs.org';
4 |
5 | export const searchPackages = async (packageName: string, size = 30): Promise => {
6 | const rep = await fetch(`${API_URL}${packageName}&size=${size}`, {
7 | headers: {
8 | 'X-Requested-With': 'XMLHttpRequest',
9 | },
10 | });
11 | const res = await rep.json();
12 | return res.results;
13 | };
14 |
15 | export const searchOnePackage = async (packageName: string): Promise => {
16 | const formatedPackageName = packageName.replace('/', '%2F'); // npm.io doesn't support scope.
17 | const rep = await fetch(`${API_URL2}${formatedPackageName}`, {
18 | headers: {
19 | 'X-Requested-With': 'XMLHttpRequest',
20 | },
21 | });
22 | const res = await rep.json();
23 | return res;
24 | };
25 |
26 | export const searchPackageInRegistry = async (name: string, version: string): Promise => {
27 | //@ts-ignore
28 | const res = await fetchWithNode(`${REGISTRY_URL}/${name}/${version}`);
29 | return res;
30 | };
31 |
32 | export const getSizeOfPackagesList = async (listPkg: any[]) => {
33 | const listSize: number[] = [];
34 | for (const ele of listPkg) {
35 | await searchPackageInRegistry(ele.package.name, ele.package.version).then((result) => {
36 | listSize.push(result.dist.unpackedSize);
37 | });
38 | }
39 | const totalSize = listSize.reduce((a, b) => a + b, 0);
40 | return totalSize;
41 | };
42 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ListPackagesFound.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, useRef, useState } from 'react';
2 | import { useModal } from '../../../hooks/useModal';
3 | import { dependencyFoundType } from '../../../manager/helpers/types';
4 |
5 | import { DependencyModalCreator } from './';
6 |
7 | import { actionPackageType, listPackageType } from '../../helpers/types';
8 |
9 | import { ItemPackageFound } from './ItemPackageFound';
10 |
11 | export const ListPackagesFound = ({
12 | results,
13 | dispatchPackages,
14 | }: {
15 | results: listPackageType;
16 | dispatchPackages: Dispatch;
17 | }) => {
18 | const ref = useRef(null);
19 |
20 | const [open, toggleModal] = useModal();
21 | const [depData, setDepData] = useState(null);
22 |
23 | return (
24 | <>
25 |
29 | {results.map((ele) => (
30 |
38 | ))}
39 |
40 |
46 | >
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/utils/generateTreeMapWithD3.ts:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | import * as d3 from 'd3';
4 | import { depStateType } from '../helpers/types';
5 | import { getRandomColor } from './color';
6 |
7 | export const generateTreeMapWithD3 = async (listPackages: depStateType) => {
8 | const margin = { top: 10, right: 10, bottom: 10, left: 10 };
9 | const width = 400 - margin.left - margin.right;
10 | const height = 240 - margin.top - margin.bottom;
11 | const listColor = [...listPackages.dependencies.map(() => getRandomColor())];
12 |
13 | const svgElement = d3
14 | .select('#treemap')
15 | .append('svg')
16 | .attr('width', width + margin.left + margin.right)
17 | .attr('height', height + margin.top + margin.bottom)
18 | .append('g')
19 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
20 | };
21 |
22 | // const extendPackagesList = (listPackages: depStateType) => {
23 |
24 | // for (let i = 0; i < listPackages.dependencies.length; i ++) {
25 | // for (const child of listPackages.dependencies[i].dependencies) {
26 | // console.log(child);
27 | // }
28 | // /*for (let j = 0; Object.keys(listPackages.dependencies[i].dependencies).length; j++) {
29 | // console.log(Object.keys(listPackages.dependencies[i].dependencies)[j]);
30 | // }*/
31 | // }
32 |
33 | // /*const newList = listPackages.dependencies.map((ele) => Object.entries(ele.dependencies).map(
34 | // async (child) => ({name: child[0], size: await calculatePackageSize(child[0], child[1])})));*/
35 | // }
36 |
--------------------------------------------------------------------------------
/src/assets/logo_starter/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/assets/waves.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/killProcess.tsx:
--------------------------------------------------------------------------------
1 | import { spawnSync } from 'child_process';
2 | import os from 'os';
3 | import psTree from 'ps-tree';
4 |
5 | // This function has been copied from:
6 | // https://github.com/joshwcomeau/guppy/blob/master/src/services/kill-process-id.service.js
7 | // All credit to his creator.
8 |
9 | // TODO:
10 | // Convert to ES6 async/await.
11 | const isWin = /^win/.test(os.platform());
12 |
13 | export const killProcess = (doomedProcessId: any): Promise => {
14 | return new Promise((resolve, reject) => {
15 | if (isWin) {
16 | resolve(spawnSync('taskkill', ['/pid', doomedProcessId, '/f', '/t']));
17 | } else {
18 | psTree(doomedProcessId, (err: Error, children: psTree.PS[]) => {
19 | if (err) {
20 | console.error('Could not gather process children:', err);
21 | return reject(err.message);
22 | }
23 | const childrenPIDs = children.map((child: any) => child.PID);
24 | resolve(spawnSync('kill', ['-9', doomedProcessId, ...childrenPIDs]));
25 | });
26 | }
27 | });
28 | };
29 |
30 | export const killAllProcess = async (listProcess: { pid: number; taskName: string }[]) => {
31 | console.log('kill all running process');
32 | const newListProcess = [...listProcess];
33 | if (listProcess.length > 0) {
34 | try {
35 | listProcess.forEach(async (ele) => {
36 | await killProcess(ele.pid);
37 | newListProcess.filter((ele2) => (ele2.pid = ele.pid));
38 | });
39 | } catch (error) {
40 | console.log(error.message);
41 | }
42 | }
43 | return newListProcess;
44 | };
45 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonRemovePackage.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 |
3 | import { actionPackageType } from '../../helpers/types';
4 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
5 |
6 | export const ButtonRemovePackage = ({
7 | type,
8 | packageName,
9 | dispatchPackages,
10 | }: {
11 | type: 'dependencies' | 'devDependencies';
12 | packageName: string;
13 | dispatchPackages: Dispatch;
14 | }) => {
15 | const { dispatchJson } = usePackageJson();
16 |
17 | const removePackages = (e: React.MouseEvent): void => {
18 | dispatchPackages({
19 | type: 'REMOVE',
20 | payload: { destination: type, name: e.currentTarget.dataset.name },
21 | });
22 | dispatchJson({
23 | type: 'REMOVE',
24 | payload: { category: type, name: e.currentTarget.dataset.name },
25 | });
26 | };
27 |
28 | return (
29 |
34 |
40 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['./src/**/*.{js,jsx,ts,tsx}'],
3 | darkMode: 'class', // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | animation: {
7 | 'arrow-bounce': 'abounce 1s infinite;',
8 | 'spin-slow': 'spin 3s linear infinite',
9 | 'animate-ping': 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
10 | },
11 | keyframes: {
12 | abounce: {
13 | '0%, 100%': {
14 | transform: 'translateX(+15%)',
15 | animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)',
16 | },
17 | '50%': {
18 | transform: 'translateX(0)',
19 | animationTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
20 | },
21 | },
22 | ping: {
23 | '75%, 100%': {
24 | transform: 'scale(2)',
25 | opacity: '0',
26 | },
27 | },
28 | },
29 | inset: {
30 | 23: '5.5rem',
31 | },
32 | minHeight: {
33 | small: '2rem',
34 | medium: '14rem',
35 | big: '33rem',
36 | },
37 | maxHeight: {
38 | small: '2rem',
39 | medium: '14rem',
40 | big: '33rem',
41 | },
42 | height: {
43 | deps: '26rem',
44 | big: '33rem',
45 | },
46 | maxWidth: {
47 | small: '2rem',
48 | medium: '14rem',
49 | big: '33rem',
50 | },
51 | colors: {
52 | primary: '#181b33',
53 | blueGray: '#2e3748',
54 | },
55 | zIndex: {
56 | '-10': '-10',
57 | },
58 | },
59 | },
60 | plugins: [require('@tailwindcss/forms')],
61 | };
62 |
--------------------------------------------------------------------------------
/src/manager/components/ComponentGenBlock/ListComponentOptions.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { formCompType } from '../../../manager/helpers/types';
4 | import { ComponentSwitch } from './ComponentSwitch';
5 |
6 | // TODO
7 | // Create list component with a list object of all options
8 |
9 | export const ListComponentOptions = ({
10 | input,
11 | setInput,
12 | }: {
13 | input: formCompType;
14 | setInput: (input: formCompType) => void;
15 | }) => {
16 | return (
17 |
18 |
19 | Function component
20 | Lorem dolor sit amet consectetur adipisicing elit.
21 |
22 |
23 | Styled component
24 | Lorem ipsum dolor sit amet adipisicing elit.
25 |
26 |
27 | Typescript
28 |
29 | Lorem ipsum dolor sit amet consectetur adipisicing elit.
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | const componentOptionsList = [
37 | {
38 | name: '',
39 | input: '',
40 | },
41 | ];
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | package-lock.json
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 | .DS_Store
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # TypeScript cache
45 | *.tsbuildinfo
46 |
47 | # Optional npm cache directory
48 | .npm
49 |
50 | # Optional eslint cache
51 | .eslintcache
52 |
53 | # Optional REPL history
54 | .node_repl_history
55 |
56 | # Output of 'npm pack'
57 | *.tgz
58 |
59 | # Yarn Integrity file
60 | .yarn-integrity
61 |
62 | # dotenv environment variables file
63 | .env
64 | .env.test
65 |
66 | # parcel-bundler cache (https://parceljs.org/)
67 | .cache
68 |
69 | # next.js build output
70 | .next
71 |
72 | # nuxt.js build output
73 | .nuxt
74 |
75 | # vuepress build output
76 | .vuepress/dist
77 |
78 | # Serverless directories
79 | .serverless/
80 |
81 | # FuseBox cache
82 | .fusebox/
83 |
84 | # DynamoDB Local files
85 | .dynamodb/
86 |
87 | # Webpack
88 | .webpack/
89 |
90 | # Electron-Forge
91 | out/
92 |
93 | # Msi installer
94 | windows_installer
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ItemPackageFound.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | import { actionPackageType, packageFoundType } from '../../helpers/types';
3 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
4 | import { dependencyFoundType } from '../../../manager/helpers/types';
5 |
6 | type propsType = {
7 | packageData: packageFoundType;
8 | dispatchPackages: Dispatch;
9 | setDepData: (dep: dependencyFoundType) => void;
10 | toggleModal: () => void;
11 | dep: dependencyFoundType;
12 | };
13 |
14 | export const ItemPackageFound = (props: propsType) => {
15 | const { packageJson } = usePackageJson();
16 |
17 | const { packageData, dep, setDepData, toggleModal } = { ...props };
18 |
19 | const handleOpen = async () => {
20 | setDepData(dep);
21 | toggleModal();
22 | };
23 |
24 | return (
25 |
30 | {Object.keys(packageJson.dependencies).includes(packageData.name) ||
31 | Object.keys(packageJson.devDependencies).includes(packageData.name) ? (
32 |
33 | {packageData.name}
34 |
35 | ) : (
36 |
40 | {packageData.name}
41 |
42 | )}
43 |
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/DependenciesItem.tsx:
--------------------------------------------------------------------------------
1 | import { CogIcon } from '@heroicons/react/outline';
2 | import React from 'react';
3 |
4 | import { depStatusType } from '../../../manager/helpers/types';
5 | import { useAppDispatch, useAppSelector } from '../../../hooks';
6 | import { selectDep } from '../../../slices/dependenciesSlice';
7 |
8 | export const DependenciesItem = ({
9 | depName,
10 | depVersion,
11 | isDevDep,
12 | status,
13 | }: {
14 | depName: string;
15 | depVersion: string;
16 | isDevDep: boolean;
17 | status: depStatusType;
18 | }) => {
19 | const depSelectedName = useAppSelector((state) => state.dependencies.depSelected.depName);
20 | const dispatch = useAppDispatch();
21 |
22 | const handleClick = () => {
23 | dispatch(selectDep({ depName: depName, depVersion: depVersion, isDevDep: isDevDep }));
24 | };
25 |
26 | return (
27 |
28 |
36 |
37 | {depName}
38 |
39 | {status === 'Pending' ? (
40 |
41 | ) : (
42 | {depVersion}
43 | )}
44 |
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonGithubLogin.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { getToken } from '../../../services/github.services';
3 | import { authGitHub } from '../../helpers/authGithub';
4 | import { useGithub } from '../Contexts/GithubProvider';
5 |
6 | export const ButtonGithubLogin = ({ setLoading }: { setLoading: (loading: boolean) => void }) => {
7 | const { github, setGithub } = useGithub();
8 |
9 | const handleAuthentication = async () => {
10 | setLoading(true);
11 | try {
12 | const { authCode } = await authGitHub();
13 | const { newToken } = await getToken(authCode);
14 | setGithub({ ...github, token: newToken });
15 | } catch (error) {
16 | console.log(error);
17 | } finally {
18 | setLoading(false);
19 | }
20 | };
21 |
22 | return (
23 |
28 | Login
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/creator/CreatorMenuSelection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PlusIcon } from '@heroicons/react/solid';
3 |
4 | import { useModal } from '../../src/hooks/useModal';
5 |
6 | import { CreatorMenuModal } from './CreatorMenuModal';
7 |
8 | export const CreatorMenuSelection = () => {
9 | const [open, toggleModal] = useModal();
10 |
11 | return (
12 |
13 |
20 |
27 |
28 |
Create a project.
29 |
Get started with a fresh new React project.
30 |
31 |
toggleModal()}
34 | >
35 |
36 | New Project
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/__test__/e2e/main.test.ts:
--------------------------------------------------------------------------------
1 | import { ElectronApplication, Page } from 'playwright';
2 | import { test, expect } from '@playwright/test';
3 | import { startApp } from '../helpersElectron';
4 |
5 | let electronApp: ElectronApplication;
6 | let appWindow: Page;
7 |
8 | test.beforeEach(async () => {
9 | const startAppResponse = await startApp();
10 | electronApp = startAppResponse.electronApp;
11 | appWindow = startAppResponse.appWindow;
12 | });
13 |
14 | test.afterEach(async () => {
15 | await electronApp.close();
16 | });
17 |
18 | test.describe('app renders correctly', () => {
19 | test('display the correct window', async () => {
20 | const title = await appWindow.title();
21 | expect(title).toBe('Reactirator');
22 | });
23 |
24 | test('renders the menu', async () => {
25 | await expect(appWindow.locator('text=New Project')).toBeVisible();
26 | await expect(appWindow.locator('text=Open project')).toBeVisible();
27 | });
28 | });
29 |
30 | test.describe('creator part functions correctly', () => {
31 | test('display the creator part', async () => {
32 | const buttonCreator = appWindow.locator('text=New Project');
33 | buttonCreator.click();
34 | await appWindow.waitForSelector('text=Creation process');
35 | expect(appWindow.locator('text=Creation process')).toBeVisible();
36 | });
37 | });
38 |
39 | /*
40 | test.describe('manager part functions correctly', () => {
41 | test('display the manager part', async () => {
42 | const buttonManager = appWindow.locator('text=Open project');
43 | buttonManager.click();
44 | await appWindow.waitForSelector('text=Tasks:');
45 | const titlePage = appWindow.locator('text=Tasks:');
46 | expect(titlePage).toBeVisible();
47 | })
48 | });
49 | */
50 |
--------------------------------------------------------------------------------
/src/creator/reducers/dependenciesReducer.ts:
--------------------------------------------------------------------------------
1 | import { actionPackageType, depStateType } from '../helpers/types';
2 |
3 | const dependenciesReducer = (state: depStateType, { type, payload }: actionPackageType) => {
4 | switch (type) {
5 | case 'ADD':
6 | if (payload.destination === 'dependencies') {
7 | state['dependencies'].push({
8 | name: payload.name,
9 | size: payload.size,
10 | version: payload.version,
11 | dependencies: payload.dependencies,
12 | });
13 | return { ...state };
14 | } else {
15 | state['devDependencies'].push({
16 | name: payload.name,
17 | size: payload.size,
18 | version: payload.version,
19 | dependencies: payload.dependencies,
20 | });
21 | return { ...state };
22 | }
23 | case 'REMOVE':
24 | if (payload.destination === 'dependencies') {
25 | const newDeps = state['dependencies'].filter((ele: any) => ele.name !== payload.name);
26 | return { ...state, dependencies: newDeps };
27 | } else {
28 | const newDeps = state['devDependencies'].filter((ele: any) => ele.name !== payload.name);
29 | return { ...state, devDependencies: newDeps };
30 | }
31 | case 'CHANGE_TYPE': {
32 | // @ts-ignore
33 | const dep = state[payload.source].find((ele) => ele.name === payload.name);
34 | // @ts-ignore
35 | const newDeps = state[payload.source].filter((ele) => ele.name !== payload.name);
36 | // @ts-ignore
37 | state[payload.destination].push(dep);
38 | return { ...state, [payload.source]: newDeps };
39 | }
40 | default:
41 | throw new Error();
42 | }
43 | };
44 |
45 | export default dependenciesReducer;
46 |
--------------------------------------------------------------------------------
/src/manager/components/TasksBlock/TasksItem.tsx:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import React, { useEffect } from 'react';
3 | import { useAppSelector } from '../../../hooks';
4 | import { useModal } from '../../../hooks/useModal';
5 |
6 | import { Card } from '../../../common/Card';
7 | import { TaskSwitch } from './TaskSwitch';
8 | import { TaskModal } from './TaskModal';
9 | import { TaskStatut } from './TaskStatut';
10 |
11 | export const TasksItem = ({ taskName }: { taskName: string }) => {
12 | const [open, toggleModal] = useModal();
13 | const task = useAppSelector((state) => state.tasks.tasks[taskName]);
14 |
15 | useEffect(() => {
16 | if (!task.enabled && task.taskState === 'Pending') {
17 | ipcRenderer.send('kill-process', { taskName: taskName });
18 | }
19 | }, [task.enabled]);
20 |
21 | return (
22 |
23 |
24 |
25 | {taskName}
26 |
27 |
28 |
29 |
30 |
31 |
35 | Open logs
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/src/utils/createTemplateComponent.ts:
--------------------------------------------------------------------------------
1 | const createTemplateComponent = (mode: string, name: string) => {
2 | switch (mode) {
3 | case 'rfc':
4 | return `import React from 'react';
5 |
6 | export default function ${name}() {
7 | return (
8 |
9 |
10 |
11 | )
12 | };`;
13 |
14 | case 'rcc':
15 | return `import React, { Component } from 'react';
16 |
17 | export default class ${name} extends Component {
18 | render() {
19 | return (
20 |
21 |
22 |
23 | )
24 | }
25 | };`;
26 |
27 | case 'rfce':
28 | return `import React from 'react';
29 |
30 | function ${name}() {
31 | return
32 | };
33 |
34 | export default ${name};`;
35 |
36 | case 'rafc':
37 | return `import React from 'react';
38 |
39 | export const ${name} = () => {
40 | return (
41 |
42 |
43 |
44 | )
45 | };`;
46 |
47 | case 'rafce':
48 | return `import React from 'react';
49 |
50 | const ${name} = () => {
51 | return
52 | };
53 |
54 | export default ${name};`;
55 |
56 | case 'rafcp':
57 | return `import React from 'react';
58 | import PropTypes from 'prop-types'
59 |
60 | const ${name} = props => {
61 | return (
62 |
63 | )
64 | }
65 |
66 | ${name}.propTypes = {}
67 |
68 | export default ${name}`;
69 |
70 | case 'rmc':
71 | return `import React, { memo } from 'react';
72 |
73 | const ${name} = memo(() => {
74 | return (
75 |
76 | )
77 | });
78 |
79 | export default ${name};`;
80 |
81 | default:
82 | console.log('Error unknown component case');
83 | }
84 | };
85 |
86 | export default createTemplateComponent;
87 |
--------------------------------------------------------------------------------
/src/common/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEventHandler } from 'react';
2 |
3 | type inputProps = {
4 | className?: string;
5 | value?: string;
6 | name?: string;
7 | id?: string;
8 | placeholder?: string;
9 | type?: string;
10 | onChange?: ChangeEventHandler;
11 | };
12 |
13 | type textAreaProps = {
14 | className?: string;
15 | value?: string;
16 | name?: string;
17 | id?: string;
18 | placeholder?: string;
19 | onChange?: ChangeEventHandler;
20 | };
21 |
22 | export const Input = React.forwardRef(
23 | ({ className, value, name, id, placeholder, type, onChange }, ref) => {
24 | return (
25 |
37 | );
38 | }
39 | );
40 |
41 | export const TextArea = React.forwardRef(
42 | ({ className, value, name, id, placeholder, onChange }, ref) => {
43 | return (
44 |
55 | );
56 | }
57 | );
58 |
--------------------------------------------------------------------------------
/src/manager/components/pages/ComponentGeneratorPage.tsx:
--------------------------------------------------------------------------------
1 | import { ipcRenderer } from 'electron';
2 | import React, { useEffect, useState } from 'react';
3 |
4 | import { formCompType } from '../../helpers/types';
5 |
6 | import { ButtonOutline } from '../../../common/Button';
7 | import { FormComponent } from '../ComponentGenBlock';
8 |
9 | const initialInput: formCompType = {
10 | functionComponent: false,
11 | };
12 |
13 | export const ComponentGeneratorPage = () => {
14 | const [location, setLocation] = useState('');
15 | const [input, setInput] = useState(initialInput);
16 | const selectLocation = () => {
17 | ipcRenderer.send('open-directory', 'component');
18 | };
19 |
20 | useEffect(() => {
21 | ipcRenderer.on(
22 | 'open-dialog-directory-selected-component',
23 | async (event: Electron.IpcRendererEvent, arg) => {
24 | const [filepath] = arg;
25 | setLocation(filepath[0]);
26 | }
27 | );
28 | return () => {
29 | ipcRenderer.removeAllListeners('open-dialog-directory-selected-component');
30 | };
31 | }, []);
32 |
33 | return (
34 |
35 |
Component generator
36 |
37 |
Select folder
38 | {location && (
39 |
40 | Location:{' '}
41 |
42 | {location}
43 |
44 |
45 | )}
46 |
47 |
48 |
49 | );
50 | };
51 |
--------------------------------------------------------------------------------
/src/manager/components/HeaderManager.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { useAppSelector } from '../../hooks';
4 |
5 | export const HeaderManager = ({
6 | projectName,
7 | taskState,
8 | }: {
9 | projectName: string;
10 | taskState: string;
11 | }) => {
12 | const starterName = useAppSelector((state) => state.project.starter);
13 |
14 | const pathToLogo = (starterName: string) => {
15 | if (starterName === 'CRA') {
16 | return '../assets/logo_starter/cra.svg';
17 | }
18 | if (starterName === 'next') {
19 | return '../assets/logo_starter/nextjs.svg';
20 | }
21 | if (starterName === 'gatsby') {
22 | return '../assets/logo_starter/gatsby.png';
23 | }
24 | if (starterName === 'remix') {
25 | return '../assets/logo_starter/remix.svg';
26 | }
27 | if (starterName === 'vite') {
28 | return '../assets/logo_starter/vite.svg';
29 | }
30 | };
31 |
32 | return (
33 |
34 | {starterName !== '' && (
35 |
36 | )}
37 |
{projectName}
38 |
39 |
40 |
45 |
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/creator/Creator.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useReducer } from 'react';
2 | import { Route, useRouteMatch } from 'react-router-dom';
3 |
4 | import { formInputType } from './helpers/types';
5 | import { initialState } from './helpers/initialState';
6 | import initialStructure from './helpers/initialStructure';
7 | import structureReducer from './reducers/structureReducer';
8 |
9 | import { PackagesPage } from './components/pages/PackagesPage';
10 | import { ArchitecturePage } from './components/pages/ArchitecturePage';
11 | import { DetailsPage } from './components/pages/DetailsPage';
12 | import { FeaturesPage } from './components/pages/FeaturesPage';
13 | import { LayoutCreator } from './components/LayoutCreator';
14 | import { InstallationPage } from './components/pages/InstallationPage';
15 |
16 | const Creator = () => {
17 | const [input, setInput] = useState(initialState);
18 | const [structure, dispatch] = useReducer(
19 | structureReducer,
20 | JSON.parse(JSON.stringify(initialStructure))
21 | );
22 |
23 | const { path } = useRouteMatch();
24 |
25 | return (
26 |
27 | } />
28 | }
32 | />
33 | } />
34 | }
38 | />
39 | }
43 | />
44 |
45 | );
46 | };
47 |
48 | export default Creator;
49 |
--------------------------------------------------------------------------------
/src/creator/CreatorVite.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useReducer } from 'react';
2 | import { Route, useRouteMatch } from 'react-router-dom';
3 |
4 | import { initialStateVite } from './helpers/initialState';
5 | import initialStructure from './helpers/initialStructure';
6 | import structureReducer from './reducers/structureReducer';
7 |
8 | import { PackagesPage } from './components/pages/PackagesPage';
9 | import { ArchitecturePage } from './components/pages/ArchitecturePage';
10 | import { DetailsPage } from './components/pages/DetailsPage';
11 | import { FeaturesPage } from './components/pages/FeaturesPage';
12 | import { LayoutCreator } from './components/LayoutCreator';
13 | import { InstallationPage } from './components/pages/InstallationPage';
14 | import { formInputType } from './helpers/types';
15 |
16 | const CreatorVite = () => {
17 | const [input, setInput] = useState(initialStateVite);
18 | const [structure, dispatch] = useReducer(
19 | structureReducer,
20 | JSON.parse(JSON.stringify(initialStructure))
21 | );
22 |
23 | const { path } = useRouteMatch();
24 |
25 | return (
26 |
27 | } />
28 | }
32 | />
33 | } />
34 | }
38 | />
39 | }
43 | />
44 |
45 | );
46 | };
47 |
48 | export default CreatorVite;
49 |
--------------------------------------------------------------------------------
/src/creator/components/LayoutCreator.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react';
2 | import { Toaster } from 'react-hot-toast';
3 | import { ArrowLeftIcon } from '@heroicons/react/outline';
4 |
5 | import { StepBar } from './StepBar';
6 | import { StepControlButtons } from './StepControlButtons';
7 | import { Link } from 'react-router-dom';
8 | import { starterType } from '../helpers/types';
9 |
10 | export const LayoutCreator = ({
11 | children,
12 | starter,
13 | }: {
14 | children: ReactNode;
15 | starter: starterType;
16 | }) => {
17 | return (
18 |
19 |
20 |
24 |
25 | Menu
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Creation process
33 |
34 |
35 |
36 |
37 |
{children}
38 |
39 |
40 |
41 |
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/manager/components/ComponentGenBlock/ComponentSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { Switch } from '@headlessui/react';
2 | import React, { ReactNode } from 'react';
3 | import { formCompType } from '../../helpers/types';
4 |
5 | export const ComponentSwitch = ({
6 | children,
7 | name,
8 | setInput,
9 | input,
10 | }: {
11 | children: ReactNode;
12 | name: string;
13 | setInput: (input: formCompType) => void;
14 | input: any;
15 | }) => {
16 | const handleChange = async () => {
17 | setInput({ ...input, [name]: !input[name] });
18 | };
19 |
20 | return (
21 |
22 |
23 |
24 | {children}
25 |
26 |
31 | Enable
32 |
36 |
43 |
48 |
49 |
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/manager/helpers/types.ts:
--------------------------------------------------------------------------------
1 | export type taskStateType = 'Idle' | 'Pending' | 'Success' | 'Error';
2 |
3 | export type actionTaskType = {
4 | type: 'IDLE' | 'PENDING' | 'FINISH' | 'ERROR' | 'STOP' | 'SWITCH';
5 | payload?: {
6 | enabled: boolean;
7 | taskSate: taskStateType;
8 | isKill: boolean;
9 | };
10 | };
11 |
12 | export type taskType = {
13 | enabled: boolean;
14 | taskState: taskStateType;
15 | isKill: boolean;
16 | logs: string;
17 | };
18 |
19 | export type projectStateType = {
20 | projectName: string;
21 | projectPath: string;
22 | starter: string;
23 | scriptDev: string;
24 | isTypescript: boolean;
25 | loading?: boolean;
26 | };
27 |
28 | export type tasksStateType = {
29 | tasks: Record;
30 | };
31 |
32 | export type FileStructureType = {
33 | id: string;
34 | name: string;
35 | ancestor: string;
36 | isFolder: boolean;
37 | mode?: string;
38 | path: string;
39 | };
40 |
41 | export type projectSrcStateType = {
42 | projectSrc: FileStructureType[];
43 | };
44 |
45 | // TODO:
46 | // Rename to "name" and "version"
47 | export type depSelectType = {
48 | depName: string;
49 | depVersion: string;
50 | isDevDep: boolean;
51 | };
52 |
53 | export type depType = {
54 | name: string;
55 | version: string;
56 | status: depStatusType;
57 | isDevDep: boolean;
58 | };
59 |
60 | export type dependenciesStateType = {
61 | dependencies: Record;
62 | devDependencies: Record;
63 | depSelected: depSelectType;
64 | };
65 |
66 | export type dependencyFoundType = {
67 | name: string;
68 | version: string;
69 | description: string;
70 | score: number;
71 | scoreDetail?: {
72 | quality: number;
73 | popularity: number;
74 | maintenance: number;
75 | };
76 | links?: {
77 | npm: string;
78 | repository: string;
79 | };
80 | };
81 |
82 | export type depStatusType = 'Idle' | 'Pending';
83 |
84 | export type formCompType = {
85 | functionComponent: boolean;
86 | };
87 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | release:
4 | types: [created]
5 | jobs:
6 | publish_on_linux:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-node@master
11 | with:
12 | node-version: 14
13 | - name: install dependencies
14 | run: npm install
15 | - name: Running Prettier Code Formatter
16 | run: npm run format:check --if-present
17 | - name: publish
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20 | CLIENT_ID: ${{ secrets.CLIENT_ID }}
21 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
22 | run: npm run publish
23 |
24 | publish_on_mac:
25 | runs-on: macos-latest
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: actions/setup-node@master
29 | with:
30 | node-version: 14
31 | - name: install dependencies
32 | run: npm install
33 | - name: Running Prettier Code Formatter
34 | run: npm run format:check --if-present
35 | - name: publish
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 | CLIENT_ID: ${{ secrets.CLIENT_ID }}
39 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
40 | run: npm run publish
41 |
42 | publish_on_win:
43 | runs-on: windows-latest
44 | steps:
45 | - name: Set git to use LF
46 | run: |
47 | git config --global core.autocrlf false
48 | git config --global core.eol lf
49 | - uses: actions/checkout@v2
50 | - uses: actions/setup-node@master
51 | with:
52 | node-version: 14
53 | - name: install dependencies
54 | run: npm install
55 | - name: Running Prettier Code Formatter
56 | run: npm run format:check --if-present
57 | - name: publish
58 | env:
59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60 | CLIENT_ID: ${{ secrets.CLIENT_ID }}
61 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
62 | run: npm run publish
63 |
--------------------------------------------------------------------------------
/src/creator/components/ScriptBlock/ScriptSection.tsx:
--------------------------------------------------------------------------------
1 | import React, { FormEvent, useState } from 'react';
2 |
3 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
4 |
5 | import { Card } from '../../../common/Card';
6 | import { Button } from '../../../common/Button';
7 | import { Input } from '../../../common/Input';
8 | import { ListScripts } from './ListScripts';
9 |
10 | export const ScriptSection = () => {
11 | const { packageJson, dispatchJson } = usePackageJson();
12 | const [input, setInput] = useState({ name: '', cmd: '' });
13 |
14 | const handleChange = (e: React.ChangeEvent): void => {
15 | setInput((input) => ({ ...input, [e.target.name]: e.target.value }));
16 | };
17 |
18 | const handleAdd = (e: FormEvent) => {
19 | e.preventDefault();
20 | if (input.name && input.cmd) {
21 | dispatchJson({
22 | type: 'CHANGE_SCRIPTS',
23 | payload: { scripts: { ...packageJson.scripts, [input.name]: input.cmd } },
24 | });
25 | setInput({ name: '', cmd: '' });
26 | }
27 | };
28 |
29 | return (
30 |
31 | Scripts edit
32 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/__test__/integration/feature.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | import React, { useReducer, useState } from 'react';
5 | import { cleanup, render, screen, fireEvent, waitFor } from '@testing-library/react';
6 | import '@testing-library/jest-dom/extend-expect';
7 |
8 | import * as packageService from '../../services/package.service';
9 | import initialPackageJson from '../../creator/helpers/initialPackageJson';
10 | import initialState from '../../creator/helpers/initialState';
11 |
12 | import { FeaturesPage } from '../../creator/components/pages/FeaturesPage';
13 | import jsonPackageReducer from '../../creator/reducers/jsonPackageReducer';
14 | import { PackageJsonProvider } from '../../creator/components/Contexts/PackageJsonProvider';
15 | import { CardPackageJson } from '../../creator/components/PackageJsonBlock';
16 |
17 | afterEach(cleanup);
18 |
19 | describe('Add feature to the project', () => {
20 | it('should render the feature page', () => {
21 | const tree = render( );
22 | expect(tree).toMatchSnapshot();
23 | });
24 |
25 | it('should activate typescript feature', async () => {
26 | jest.spyOn(packageService, 'searchOnePackage').mockResolvedValue({
27 | collected: {
28 | metadata: {
29 | version: '1.0.0',
30 | },
31 | },
32 | });
33 | render( );
34 | fireEvent.click(screen.getByText('Bootstrap'));
35 | await waitFor(() => screen.queryByText('bootstrap'));
36 | expect(screen.getByTestId('packagejson')).toHaveTextContent('"bootstrap": "^1.0.0"');
37 | });
38 | });
39 |
40 | const FakeFeaturePage = () => {
41 | const [input, setInput] = useState(initialState);
42 | const [packageJson, dispatchJson] = useReducer(
43 | jsonPackageReducer,
44 | JSON.parse(JSON.stringify(initialPackageJson))
45 | );
46 | return (
47 |
48 |
49 |
50 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/src/creator/components/GithubBlock/GithubForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent } from 'react';
2 | import { Input } from '../../../common/Input';
3 | import { useGithub } from '../Contexts/GithubProvider';
4 |
5 | export const GithubForm = () => {
6 | const { github, setGithub } = useGithub();
7 |
8 | const handleChange = (e: ChangeEvent) => {
9 | setGithub({ ...github, reponame: e.target.value });
10 | };
11 |
12 | const handleChangeCheckbox = (e: ChangeEvent) => {
13 | // @ts-ignore
14 | setGithub({ ...github, visibility: e.target.value });
15 | };
16 |
17 | return (
18 |
19 |
20 |
21 | Name
22 |
23 |
32 |
33 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/manager/components/TasksBlock/TasksDevelopmentPane.tsx:
--------------------------------------------------------------------------------
1 | // import { shell } from 'electron';
2 | import React from 'react';
3 | import launchEditor from 'react-dev-utils/launchEditor';
4 |
5 | import { useAppSelector } from '../../../hooks';
6 |
7 | import { Card } from '../../../common/Card';
8 | import { TerminalOutput } from '../Terminal';
9 | import { TaskMainSwitch } from './TaskSwitch';
10 |
11 | export const TasksDevelopmentPane = () => {
12 | const startScript = useAppSelector((state) => state.project.scriptDev);
13 | const projectPath = useAppSelector((state) => state.project.projectPath);
14 | const redirectToEditor = () => {
15 | launchEditor(projectPath, 1, 1);
16 | };
17 |
18 | return (
19 |
20 |
21 |
22 |
Development server
23 |
24 |
25 |
26 |
31 |
39 |
44 |
45 | Open in editor
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/creator/helpers/authGithub.ts:
--------------------------------------------------------------------------------
1 | import { AuthOptions } from './types';
2 | import { Constants } from './gitServicesOptions';
3 | import { BrowserWindow } from '@electron/remote';
4 |
5 | export const authGitHub = (
6 | authOptions = Constants.DEFAULT_AUTH_OPTIONS
7 | ): Promise<{
8 | authCode: string;
9 | authOptions: AuthOptions;
10 | }> => {
11 | return new Promise((resolve, reject) => {
12 | const authWindow = new BrowserWindow({
13 | width: 800,
14 | height: 600,
15 | show: true,
16 | });
17 |
18 | const githubUrl = `https://github.com/login/oauth/authorize?client_id=${authOptions.clientId}&scope=${Constants.AUTH_SCOPE}`;
19 |
20 | const session = authWindow.webContents.session;
21 | session.clearStorageData();
22 |
23 | authWindow.loadURL(githubUrl);
24 |
25 | const handleCallback = async (url: string) => {
26 | const raw_code = /code=([^&]*)/.exec(url) || null;
27 | const authCode = raw_code && raw_code.length > 1 ? raw_code[1] : null;
28 | const error = /\?error=(.+)$/.exec(url);
29 | if (authCode || error) {
30 | authWindow.destroy();
31 | }
32 | if (authCode) {
33 | resolve({ authCode, authOptions });
34 | } else if (error) {
35 | reject(
36 | "Oops! Something went wrong and we couldn't " +
37 | 'log you in using Github. Please try again.'
38 | );
39 | }
40 | };
41 |
42 | authWindow.on('close', () => {
43 | reject('Cancel authentication window.');
44 | authWindow.destroy();
45 | });
46 |
47 | authWindow.webContents.on(
48 | 'did-fail-load',
49 | (event: any, errorCode: any, errorDescription: any, validatedURL: any) => {
50 | if (validatedURL.includes(authOptions.hostname)) {
51 | authWindow.destroy();
52 | reject(`Invalid Hostname. Could not load https://${authOptions.hostname}/.`);
53 | }
54 | }
55 | );
56 |
57 | authWindow.webContents.on('will-navigate', async (event: any, url: string) => {
58 | event.preventDefault();
59 | await handleCallback(url);
60 | });
61 | });
62 | };
63 |
--------------------------------------------------------------------------------
/src/creator/components/Buttons/ButtonAddPackage.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 |
3 | import { searchPackageInRegistry } from '../../../services/package.service';
4 | import { actionPackageType, packageFoundType } from '../../helpers/types';
5 | import { useLoading } from '../Contexts/LoadingPackageProvider';
6 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
7 |
8 | export const ButtonAddPackage = ({
9 | packageData,
10 | dispatchPackages,
11 | }: {
12 | packageData: packageFoundType;
13 | dispatchPackages: Dispatch;
14 | }) => {
15 | const { loading, setLoading } = useLoading();
16 | const { dispatchJson } = usePackageJson();
17 |
18 | const addPackages = async (e: React.MouseEvent): Promise => {
19 | const target = e.target as HTMLElement;
20 | setLoading(true);
21 | try {
22 | const packageRegistryInfo = await searchPackageInRegistry(
23 | target.dataset.name,
24 | target.dataset.version
25 | );
26 | dispatchPackages({
27 | type: 'ADD',
28 | payload: {
29 | destination: 'dependencies',
30 | name: target.dataset.name,
31 | size: packageRegistryInfo.dist.unpackedSize,
32 | version: target.dataset.version,
33 | dependencies: packageRegistryInfo.dependencies,
34 | },
35 | });
36 | dispatchJson({
37 | type: 'ADD',
38 | payload: {
39 | category: 'dependencies',
40 | name: target.dataset.name,
41 | version: target.dataset.version,
42 | },
43 | });
44 | setLoading(false);
45 | } catch (error) {
46 | console.log(error);
47 | setLoading(false);
48 | }
49 | };
50 |
51 | return (
52 |
60 | {packageData.name}
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/services/github.services.ts:
--------------------------------------------------------------------------------
1 | import { AuthOptions } from '../creator/helpers/types';
2 | import { Constants } from '../creator/helpers/gitServicesOptions';
3 | import { GithubStateType } from '../creator/components/Contexts/GithubProvider';
4 |
5 | export const createGithubRepo = async (github: GithubStateType) => {
6 | try {
7 | const isRepoPrivate = github.visibility === 'private' ? true : false;
8 | await fetch('https://api.github.com/user/repos', {
9 | method: 'POST',
10 | headers: {
11 | Authorization: 'Token ' + github.token,
12 | Accept: 'application/vnd.github.v3+json',
13 | 'Content-Type': 'application/x-www-form-urlencoded',
14 | },
15 | body: JSON.stringify({ name: github.reponame, private: isRepoPrivate }),
16 | });
17 | } catch (error) {
18 | console.log(error);
19 | }
20 | };
21 |
22 | /*
23 | export const pushToGithubRepo = async (github: GithubStateType, filename: string, filecontent: string) => {
24 | try {
25 | await fetch(`https://api.github.com/repos/leopold-v/${github.reponame}/contents/${filename}`, {
26 | method: 'PUT',
27 | headers: {
28 | 'Authorization': 'Token ' + github.token,
29 | "Accept": "application/vnd.github.v3+json",
30 | "Content-Type": "application/x-www-form-urlencoded"
31 | },
32 | body: JSON.stringify({message: 'update', content: btoa(filecontent)})
33 | })
34 | } catch (error) {
35 | console.log(error);
36 | }
37 | }
38 | */
39 |
40 | export const getToken = async (
41 | authCode: string,
42 | authOptions: AuthOptions = Constants.DEFAULT_AUTH_OPTIONS
43 | ): Promise => {
44 | const url = `https://github.com/login/oauth/access_token`;
45 | const data = {
46 | client_id: authOptions.clientId,
47 | client_secret: authOptions.clientSecret,
48 | code: authCode,
49 | };
50 | try {
51 | //@ts-ignore
52 | const response = await fetchPostWithNode(url, data);
53 | console.log(response);
54 | return {
55 | hostname: authOptions.hostname,
56 | newToken: response.access_token,
57 | };
58 | } catch (error) {
59 | console.log(error.message);
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/src/common/Bar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './bar.css';
3 |
4 | export const Bar = () => {
5 | return (
6 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/creator/components/ReadmeBlock/markdown.css:
--------------------------------------------------------------------------------
1 | /* Additional vertical padding used by kbd tag. */
2 | .py-05 {
3 | padding-top: 0.125rem;
4 | padding-bottom: 0.125rem;
5 | }
6 |
7 | .markdown {
8 | @apply leading-normal break-words text-white;
9 | }
10 |
11 | .markdown > * + * {
12 | @apply mt-0 mb-4;
13 | }
14 |
15 | .markdown li + li {
16 | @apply mt-1;
17 | }
18 |
19 | .markdown li > p + p {
20 | @apply mt-6;
21 | }
22 |
23 | .markdown strong {
24 | @apply font-semibold;
25 | }
26 |
27 | .markdown a {
28 | @apply text-indigo-500 font-semibold;
29 | }
30 |
31 | .markdown strong a {
32 | @apply font-bold;
33 | }
34 |
35 | .markdown h1 {
36 | @apply leading-tight border-b text-4xl font-semibold text-white mb-4 mt-6 pb-2;
37 | }
38 |
39 | .markdown h2 {
40 | @apply leading-tight border-b text-2xl font-semibold text-white mb-4 mt-6 pb-2;
41 | }
42 |
43 | .markdown h3 {
44 | @apply leading-snug text-lg font-semibold mb-4 text-white mt-6;
45 | }
46 |
47 | .markdown h4 {
48 | @apply leading-none text-base font-semibold mb-4 text-white mt-6;
49 | }
50 |
51 | .markdown h5 {
52 | @apply leading-tight text-sm font-semibold mb-4 mt-6;
53 | }
54 |
55 | .markdown h6 {
56 | @apply leading-tight text-sm font-semibold text-white mb-4 mt-6;
57 | }
58 |
59 | .markdown blockquote {
60 | @apply text-base border-l-4 border-gray-300 pl-4 pr-4 text-white;
61 | }
62 |
63 | .markdown code {
64 | @apply font-mono text-sm inline rounded px-1 py-5;
65 | }
66 |
67 | .markdown pre {
68 | @apply rounded p-4;
69 | }
70 |
71 | .markdown pre code {
72 | @apply block p-0 overflow-visible rounded-none;
73 | }
74 |
75 | .markdown ul {
76 | @apply text-base pl-8 list-disc;
77 | }
78 |
79 | .markdown ol {
80 | @apply text-base pl-8 list-decimal;
81 | }
82 |
83 | .markdown kbd {
84 | @apply text-xs inline-block rounded border px-1 py-0 align-middle font-normal font-mono shadow;
85 | }
86 |
87 | .markdown table {
88 | @apply text-base border-gray-200;
89 | }
90 |
91 | .markdown th {
92 | @apply border py-1 px-3;
93 | }
94 |
95 | .markdown td {
96 | @apply border py-1 px-3;
97 | }
98 |
99 | /* Override pygments style background color. */
100 | .markdown .highlight pre {
101 | @apply bg-gray-200 border-gray-500 !important;
102 | }
103 |
--------------------------------------------------------------------------------
/src/creator/components/pages/SuccessPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import launchEditor from 'react-dev-utils/launchEditor';
3 | import { Link } from 'react-router-dom';
4 | import { ArrowLeftIcon } from '@heroicons/react/outline';
5 |
6 | export const SuccessPage = ({ location }: { location: { pathname: string; state: string } }) => {
7 | const projectPath = location.state;
8 |
9 | const redirectToEditor = () => {
10 | try {
11 | launchEditor(projectPath, 1, 1);
12 | } catch (error) {
13 | console.log(error);
14 | }
15 | };
16 |
17 | return (
18 |
19 |
20 |
21 |
Congratulation !
22 |
23 |
24 |
25 |
Your project has been created successfully.
26 |
31 |
39 |
44 |
45 | Open in editor
46 |
47 |
51 |
52 | Menu
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/__test__/integration/details.test.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | import React, { useReducer, useState } from 'react';
5 | import { cleanup, render, screen, fireEvent } from '@testing-library/react';
6 | import '@testing-library/jest-dom/extend-expect';
7 |
8 | import initialPackageJson from '../../creator/helpers/initialPackageJson';
9 | import initialState from '../../creator/helpers/initialState';
10 |
11 | import jsonPackageReducer from '../../creator/reducers/jsonPackageReducer';
12 | import { PackageJsonProvider } from '../../creator/components/Contexts/PackageJsonProvider';
13 | import { CardPackageJson } from '../../creator/components/PackageJsonBlock';
14 | import { DetailsForm } from '../../creator/components/DetailsBlock';
15 |
16 | afterEach(cleanup);
17 |
18 | describe('change information about the project', () => {
19 | it('should render the details form', () => {
20 | const tree = render( );
21 | expect(tree).toMatchSnapshot();
22 | });
23 |
24 | it('should change the app name', async () => {
25 | render( );
26 | const inputEle = screen.getByDisplayValue('0.1.0');
27 | fireEvent.change(inputEle, { target: { value: '3.0.0' } });
28 | expect(screen.getByTestId('packagejson')).toHaveTextContent('"version": "3.0.0"');
29 | });
30 |
31 | it('should change the version', async () => {
32 | render( );
33 | const inputEle = screen.getByPlaceholderText('Description');
34 | fireEvent.change(inputEle, { target: { value: 'blablablabla' } });
35 | expect(screen.getByTestId('packagejson')).toHaveTextContent('"description": "blablablabla"');
36 | });
37 |
38 | it('should change the description', async () => {
39 | render( );
40 | const inputEle = screen.getByPlaceholderText('App. name');
41 | fireEvent.change(inputEle, { target: { value: 'My react app' } });
42 | expect(screen.getByTestId('packagejson')).toHaveTextContent('"name": "My react app"');
43 | });
44 | });
45 |
46 | const FakeDetailsPage = () => {
47 | const [input, setInput] = useState(initialState);
48 | const [packageJson, dispatchJson] = useReducer(
49 | jsonPackageReducer,
50 | JSON.parse(JSON.stringify(initialPackageJson))
51 | );
52 | return (
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/common/bar.css:
--------------------------------------------------------------------------------
1 | #titlebar {
2 | display: block;
3 | position: fixed;
4 | z-index: 1000;
5 | height: 32px;
6 | width: 100%;
7 | }
8 |
9 | .maximized #titlebar {
10 | width: 100%;
11 | padding: 0;
12 | }
13 |
14 | #titlebar {
15 | padding: 0px;
16 | }
17 |
18 | #titlebar #drag-region {
19 | width: 100%;
20 | height: 100%;
21 | -webkit-app-region: drag;
22 | background: #181b33;
23 | }
24 |
25 | #titlebar {
26 | color: #fff;
27 | }
28 |
29 | #titlebar #drag-region {
30 | display: grid;
31 | grid-template-columns: auto 138px;
32 | }
33 |
34 | #window-title {
35 | grid-column: 1;
36 | display: flex;
37 | align-items: center;
38 | margin-left: 1.5rem;
39 | overflow: hidden;
40 | }
41 |
42 | #window-title span {
43 | overflow: hidden;
44 | text-overflow: ellipsis;
45 | white-space: nowrap;
46 | line-height: 1.5;
47 | }
48 |
49 | #window-controls {
50 | display: grid;
51 | grid-template-columns: repeat(2, 46px);
52 | position: absolute;
53 | top: 0;
54 | right: 0;
55 | height: 100%;
56 | }
57 |
58 | #window-controls {
59 | -webkit-app-region: no-drag;
60 | }
61 |
62 | #window-controls .button {
63 | grid-row: 1 / span 1;
64 | display: flex;
65 | justify-content: center;
66 | align-items: center;
67 | width: 100%;
68 | height: 100%;
69 | }
70 |
71 | @media (-webkit-device-pixel-ratio: 1.5),
72 | (device-pixel-ratio: 1.5),
73 | (-webkit-device-pixel-ratio: 2),
74 | (device-pixel-ratio: 2),
75 | (-webkit-device-pixel-ratio: 3),
76 | (device-pixel-ratio: 3) {
77 | #window-controls .icon {
78 | width: 10px;
79 | height: 10px;
80 | }
81 | }
82 |
83 | #button_git {
84 | user-select: none;
85 | -webkit-app-region: no-drag;
86 | cursor: pointer;
87 | }
88 |
89 | #window-controls .button {
90 | user-select: none;
91 | }
92 |
93 | #window-controls .button:hover {
94 | background: rgba(255, 255, 255, 0.1);
95 | }
96 |
97 | #window-controls .button:active {
98 | background: rgba(255, 255, 255, 0.2);
99 | }
100 |
101 | #close-button:hover {
102 | background: #e81123 !important;
103 | }
104 |
105 | #close-button:active {
106 | background: #f1707a !important;
107 | }
108 | #close-button:active .icon {
109 | filter: invert(1);
110 | }
111 |
112 | #min-button {
113 | grid-column: 1;
114 | }
115 | #close-button {
116 | grid-column: 2;
117 | }
118 | #switch button {
119 | -webkit-app-region: no-drag;
120 | }
121 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/ListPackages.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch } from 'react';
2 | // eslint-disable-next-line import/named
3 | import { DragDropContext, DropResult } from 'react-beautiful-dnd';
4 |
5 | import { actionPackageType, depStateType } from '../../helpers/types';
6 | import { CardDependencies } from './CardDependencies';
7 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
8 |
9 | import { ListPackagesSelected } from './ListPackagesSelected';
10 |
11 | export const ListPackages = ({
12 | listPackages,
13 | dispatchPackages,
14 | }: {
15 | listPackages: depStateType;
16 | dispatchPackages: Dispatch;
17 | }) => {
18 | const { packageJson, dispatchJson } = usePackageJson();
19 |
20 | const onDragEnd = (result: DropResult) => {
21 | const { draggableId, source, destination } = result;
22 |
23 | if (!destination) return;
24 | if (source.droppableId === destination.droppableId) return;
25 | dispatchPackages({
26 | type: 'CHANGE_TYPE',
27 | payload: {
28 | // @ts-ignores
29 | destination: destination.droppableId,
30 | source: source.droppableId,
31 | name: draggableId,
32 | },
33 | });
34 | dispatchJson({
35 | type: 'ADD',
36 | payload: {
37 | category: destination.droppableId,
38 | name: draggableId,
39 | version: packageJson[source.droppableId][draggableId],
40 | },
41 | });
42 | dispatchJson({
43 | type: 'REMOVE',
44 | payload: {
45 | category: source.droppableId,
46 | name: draggableId,
47 | },
48 | });
49 | };
50 |
51 | return (
52 |
53 |
54 |
55 |
60 |
61 |
62 |
67 |
68 |
69 |
70 | );
71 | };
72 |
--------------------------------------------------------------------------------
/src/creator/components/FeaturesBlock/FeatureSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { Switch } from '@headlessui/react';
2 | import React, { ReactNode } from 'react';
3 | import { searchOnePackage } from '../../../services/package.service';
4 | import { formInputType } from '../../helpers/types';
5 |
6 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
7 |
8 | export const FeatureSwitch = ({
9 | children,
10 | name,
11 | packageName,
12 | setInput,
13 | input,
14 | }: {
15 | children: ReactNode;
16 | name: string;
17 | packageName: string;
18 | setInput: (input: formInputType) => void;
19 | input: any;
20 | }) => {
21 | const { dispatchJson } = usePackageJson();
22 |
23 | const handleChange = async () => {
24 | setInput({ ...input, [name]: !input[name] });
25 | try {
26 | const packageFound = await searchOnePackage(packageName);
27 | dispatchJson({
28 | type: name,
29 | payload: { version: packageFound.collected.metadata.version },
30 | });
31 | } catch (error) {
32 | console.log(error);
33 | }
34 | };
35 |
36 | return (
37 |
38 |
39 |
40 | {children}
41 |
42 |
47 | Run
48 |
52 |
59 |
64 |
65 |
66 |
67 | );
68 | };
69 |
--------------------------------------------------------------------------------
/src/slices/dependenciesSlice.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3 | import { dependenciesStateType, depSelectType, depType } from '../manager/helpers/types';
4 |
5 | const initialState: dependenciesStateType = {
6 | dependencies: {},
7 | devDependencies: {},
8 | depSelected: {
9 | depName: '',
10 | depVersion: '',
11 | isDevDep: false,
12 | },
13 | };
14 |
15 | export const dependenciesSlice = createSlice({
16 | name: 'dependencies',
17 | initialState,
18 | reducers: {
19 | initDependencies: (
20 | state: dependenciesStateType,
21 | action: PayloadAction
22 | ) => {
23 | state.dependencies = action.payload.dependencies;
24 | state.devDependencies = action.payload.devDependencies;
25 | state.depSelected = action.payload.depSelected;
26 | },
27 | selectDep: (state: dependenciesStateType, action: PayloadAction) => {
28 | state.depSelected = action.payload;
29 | },
30 | updateDep: (state: dependenciesStateType, action: PayloadAction) => {
31 | const depCategory = action.payload.isDevDep ? 'devDependencies' : 'dependencies';
32 | const depName = action.payload.name;
33 | state[depCategory][depName].version = action.payload.version;
34 | state[depCategory][depName].status = action.payload.status;
35 | state[depCategory][depName].isDevDep = action.payload.isDevDep;
36 | },
37 | installDep: (state: dependenciesStateType, action: PayloadAction) => {
38 | const depCategory = action.payload.isDevDep ? 'devDependencies' : 'dependencies';
39 | state[depCategory][action.payload.name] = action.payload;
40 | },
41 | removeDep: (
42 | state: dependenciesStateType,
43 | action: PayloadAction<{ depName: string; isDevDep: boolean }>
44 | ) => {
45 | const depCategory = action.payload.isDevDep ? 'devDependencies' : 'dependencies';
46 | if (state.depSelected.depName === action.payload.depName) {
47 | const newSelectedDep = Object.entries(state.dependencies)[0][1];
48 | state.depSelected = {
49 | depName: newSelectedDep.name,
50 | depVersion: newSelectedDep.version,
51 | isDevDep: newSelectedDep.isDevDep,
52 | };
53 | }
54 | delete state[depCategory][action.payload.depName];
55 | },
56 | },
57 | });
58 |
59 | export const { initDependencies, selectDep, updateDep, removeDep, installDep } =
60 | dependenciesSlice.actions;
61 |
62 | export default dependenciesSlice.reducer;
63 |
--------------------------------------------------------------------------------
/src/slices/taskSlice.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/named
2 | import { createSlice, PayloadAction } from '@reduxjs/toolkit';
3 | import { tasksStateType } from '../manager/helpers/types';
4 |
5 | const initialState: tasksStateType = {
6 | tasks: {},
7 | };
8 |
9 | export const taskSlice = createSlice({
10 | name: 'tasks',
11 | initialState,
12 | reducers: {
13 | initTasks: (state, action: PayloadAction) => {
14 | state.tasks = action.payload.tasks;
15 | },
16 | switchTask: (state, action: PayloadAction) => {
17 | state.tasks[action.payload].enabled = !state.tasks[action.payload].enabled;
18 | },
19 | idleTask: (state, action: PayloadAction) => {
20 | state.tasks[action.payload].taskState = 'Idle';
21 | state.tasks[action.payload].enabled = false;
22 | state.tasks[action.payload].isKill = false;
23 | },
24 | pendingTask: (state, action: PayloadAction) => {
25 | state.tasks[action.payload].logs = '';
26 | state.tasks[action.payload].taskState = 'Pending';
27 | state.tasks[action.payload].enabled = true;
28 | },
29 | finishTask: (state, action: PayloadAction) => {
30 | state.tasks[action.payload].taskState = 'Success';
31 | state.tasks[action.payload].enabled = false;
32 | if (state.tasks[action.payload].isKill) {
33 | state.tasks[action.payload].taskState = 'Error';
34 | }
35 | state.tasks[action.payload].isKill = false;
36 | },
37 | stopTask: (state, action: PayloadAction) => {
38 | state.tasks[action.payload].taskState = 'Error';
39 | state.tasks[action.payload].enabled = false;
40 | state.tasks[action.payload].isKill = true;
41 | state.tasks[action.payload].logs += '\n\n Task aborted.';
42 | },
43 | errorTask: (state, action: PayloadAction<{ taskName: string; logs: string }>) => {
44 | state.tasks[action.payload.taskName].isKill = true;
45 | state.tasks[action.payload.taskName].logs = action.payload.logs;
46 | },
47 | updateLogs: (state, action: PayloadAction<{ taskName: string; logs: string }>) => {
48 | state.tasks[action.payload.taskName].logs += action.payload.logs;
49 | },
50 | clearLogs: (state, action: PayloadAction) => {
51 | state.tasks[action.payload].logs = '';
52 | },
53 | },
54 | });
55 |
56 | export const {
57 | clearLogs,
58 | initTasks,
59 | switchTask,
60 | idleTask,
61 | pendingTask,
62 | finishTask,
63 | stopTask,
64 | errorTask,
65 | updateLogs,
66 | } = taskSlice.actions;
67 |
68 | export default taskSlice.reducer;
69 |
--------------------------------------------------------------------------------
/src/common/sidenav.css:
--------------------------------------------------------------------------------
1 | .nav__link svg {
2 | transition: all 0.3s;
3 | padding: 0.2rem;
4 | border-radius: 50%;
5 | }
6 |
7 | .nav {
8 | padding: 1.5rem;
9 | border-right: 2px solid rgb(243, 243, 243);
10 | position: relative;
11 | display: flex;
12 | flex-direction: column;
13 | justify-content: space-between;
14 | min-width: 14rem;
15 | }
16 |
17 | .nav__logo {
18 | padding-bottom: 1.5rem;
19 | margin-bottom: 1.5rem;
20 | border-bottom: 1px solid #e7e7e7;
21 | display: flex;
22 | align-items: center;
23 | justify-content: space-around;
24 | color: white;
25 | font-weight: 900;
26 | font-size: 1.3rem;
27 | text-decoration: none;
28 | }
29 |
30 | .nav__list {
31 | padding: 0;
32 | }
33 |
34 | .nav__link {
35 | text-decoration: none;
36 | color: #e0e0e0;
37 | transition: all 0.3s;
38 | padding: 0.6rem 0.7rem 0.6rem 0.3rem;
39 | border-radius: 5px;
40 | width: 100%;
41 | display: flex;
42 | font-size: 1rem;
43 | align-items: center;
44 | }
45 |
46 | .nav__link:hover {
47 | color: white;
48 | background-color: #384257;
49 | }
50 |
51 | .nav__link:hover svg {
52 | stroke: white;
53 | }
54 |
55 | .nav__link.active {
56 | color: white;
57 | background: rgb(79 70 229);
58 | background: rgb(79 70 229);
59 | box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2);
60 | }
61 |
62 | .nav__link.active svg {
63 | stroke: white;
64 | background-color: #3e4ec72d;
65 | }
66 |
67 | .nav__item {
68 | margin: 0.3rem 0;
69 | width: 100%;
70 | }
71 |
72 | .nav__item-name {
73 | margin-left: 0.7rem;
74 | }
75 |
76 | .nav__footer {
77 | width: 100%;
78 | display: flex;
79 | flex-direction: column;
80 | padding: 1rem;
81 | border-top: 1px solid #e7e7e7;
82 | }
83 |
84 | .btn-bug {
85 | color: #e0e0e0;
86 | transition: all 0.3s;
87 | padding: 0.6rem 0.7rem 0.6rem 0.3rem;
88 | border-radius: 5px;
89 | width: 100%;
90 | font-size: 0.9rem;
91 | display: flex;
92 | justify-content: center;
93 | align-items: center;
94 | margin-bottom: 0.3rem;
95 | }
96 |
97 | .btn-bug:focus {
98 | outline: none;
99 | }
100 |
101 | .btn-bug:hover {
102 | color: white;
103 | background: rgb(82, 83, 109);
104 | }
105 |
106 | .btn-theme {
107 | color: #e0e0e0;
108 | transition: all 0.3s;
109 | padding: 0.6rem 0.7rem 0.6rem 0.3rem;
110 | border-radius: 5px;
111 | width: 100%;
112 | font-size: 0.9rem;
113 | display: flex;
114 | justify-content: center;
115 | align-items: center;
116 | margin-bottom: 0.3rem;
117 | }
118 |
119 | .btn-theme:hover {
120 | color: white;
121 | background: rgb(82, 83, 109);
122 | }
123 |
--------------------------------------------------------------------------------
/src/common/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, { MouseEventHandler, ReactNode } from 'react';
2 | import { TrashIcon } from '@heroicons/react/outline';
3 |
4 | export const Button = ({
5 | children,
6 | onClick,
7 | }: {
8 | children: ReactNode;
9 | onClick?: MouseEventHandler;
10 | }) => {
11 | return (
12 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | export const ButtonSecondary = ({
22 | children,
23 | onClick,
24 | }: {
25 | children: ReactNode;
26 | onClick?: MouseEventHandler;
27 | }) => {
28 | return (
29 |
33 | {children}
34 |
35 | );
36 | };
37 |
38 | export const ButtonDelete = ({
39 | children,
40 | onClick,
41 | disabled = false,
42 | }: {
43 | children: ReactNode;
44 | onClick?: MouseEventHandler;
45 | disabled?: boolean;
46 | }) => {
47 | return (
48 |
53 |
54 | {children}
55 |
56 | );
57 | };
58 |
59 | export const ButtonOutline = ({
60 | children,
61 | onClick,
62 | disabled = false,
63 | }: {
64 | children: ReactNode;
65 | onClick?: MouseEventHandler;
66 | disabled?: boolean;
67 | }) => {
68 | return (
69 |
74 | {children}
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/creator/components/PackageManagerBlock/SearchPackages.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect, Dispatch } from 'react';
2 | import { SearchIcon } from '@heroicons/react/outline';
3 |
4 | import { actionPackageType, listPackageType } from '../../helpers/types';
5 | import { searchPackages } from '../../../services/package.service';
6 |
7 | import { ListPackagesFound } from './ListPackagesFound';
8 | import { Input } from '../../../common/Input';
9 |
10 | export const SearchPackages = ({
11 | dispatchPackages,
12 | }: {
13 | dispatchPackages: Dispatch;
14 | }) => {
15 | const [input, setInput] = useState([]);
16 | const [isOpen, setIsOpen] = useState(false);
17 |
18 | const input_ref = useRef(null);
19 |
20 | const handleChange = async (e: React.ChangeEvent): Promise => {
21 | if (e.target.value !== '') {
22 | try {
23 | const packagesFound = await searchPackages(e.target.value);
24 | const results: listPackageType = packagesFound.map((ele: any) => ({
25 | name: ele.package.name,
26 | version: ele.package.version,
27 | description: ele.package.description,
28 | score: ele.score.final,
29 | scoreDetail: {
30 | quality: ele.score.detail.quality,
31 | popularity: ele.score.detail.popularity,
32 | maintenance: ele.score.detail.maintenance,
33 | },
34 | links: {
35 | npm: ele.package.links.npm,
36 | repository: ele.package.links.repository,
37 | },
38 | }));
39 | setInput(results);
40 | } catch (error) {
41 | console.log('Error fetching the API');
42 | }
43 | } else {
44 | setInput([]);
45 | }
46 | };
47 |
48 | const handleClick = (e: any): void => {
49 | if (input_ref.current.contains(e.target)) {
50 | setIsOpen(true);
51 | } else {
52 | setIsOpen(false);
53 | }
54 | };
55 |
56 | useEffect(() => {
57 | document.addEventListener('click', handleClick);
58 | return () => document.removeEventListener('click', handleClick);
59 | }, []);
60 |
61 | return (
62 |
63 |
64 |
65 |
66 |
74 | {isOpen &&
}
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/creator/helpers/featuresLists.ts:
--------------------------------------------------------------------------------
1 | import { featureType } from './types';
2 |
3 | export const featuresListCRA: featureType[] = [
4 | {
5 | title: 'Typescript',
6 | description: 'A strongly typed programming language.',
7 | name: 'typescript',
8 | packageName: 'typescript',
9 | link: 'https://www.typescriptlang.org/',
10 | },
11 | {
12 | title: 'Flow',
13 | description: 'A Static Type Checker for JavaScript.',
14 | name: 'flow',
15 | packageName: 'flow-bin',
16 | link: 'https://flow.org/',
17 | },
18 | {
19 | title: 'Prettier',
20 | description: 'An opinionated code formatter.',
21 | name: 'prettier',
22 | packageName: 'prettier',
23 | link: 'https://prettier.io/',
24 | },
25 | {
26 | title: 'Bundle analyze',
27 | description: 'Visualize size of files with an interactive zoomable treemap.',
28 | name: 'sourcemapexplorer',
29 | packageName: 'source-map-explorer',
30 | link: 'https://github.com/danvk/source-map-explorer',
31 | },
32 | {
33 | title: 'Storybook',
34 | description: 'A tool for building UI components and pages in isolation.',
35 | name: 'storybook',
36 | packageName: 'storybook',
37 | link: 'https://storybook.js.org/',
38 | },
39 | {
40 | title: 'Tailwindcss',
41 | description: 'A utility-first CSS framework.',
42 | name: 'tailwind',
43 | packageName: 'tailwind',
44 | link: 'https://tailwindcss.com/',
45 | },
46 | {
47 | title: 'Bootstrap',
48 | description: 'CSS Framework for developing responsive websites.',
49 | name: 'bootstrap',
50 | packageName: 'bootstrap',
51 | link: 'https://getbootstrap.com/',
52 | },
53 | {
54 | title: 'Css reset',
55 | description: 'Reset stylesheet to reduce browser inconsistencies.',
56 | name: 'normalize',
57 | packageName: 'normalize.css',
58 | link: 'https://necolas.github.io/normalize.css/',
59 | },
60 | ];
61 |
62 | export const featuresListVite: featureType[] = [
63 | {
64 | title: 'Typescript',
65 | description: 'A strongly typed programming language.',
66 | name: 'typescript',
67 | packageName: 'typescript',
68 | link: 'https://www.typescriptlang.org/',
69 | },
70 | {
71 | title: 'Prettier',
72 | description: 'An opinionated code formatter.',
73 | name: 'prettier',
74 | packageName: 'prettier',
75 | link: 'https://prettier.io/',
76 | },
77 | {
78 | title: 'Tailwindcss',
79 | description: 'A utility-first CSS framework.',
80 | name: 'tailwind',
81 | packageName: 'tailwind',
82 | link: 'https://tailwindcss.com/',
83 | },
84 | {
85 | title: 'Bootstrap',
86 | description: 'CSS Framework for developing responsive websites.',
87 | name: 'bootstrap',
88 | packageName: 'bootstrap',
89 | link: 'https://getbootstrap.com/',
90 | },
91 | ];
92 |
--------------------------------------------------------------------------------
/src/creator/components/StepControlButtons.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
3 | import { useLocation, useHistory } from 'react-router-dom';
4 |
5 | import { starterType } from '../helpers/types';
6 | import { stepsCRA, stepsVite } from '../helpers/steps';
7 |
8 | export const StepControlButtons = ({ starter }: { starter: starterType }) => {
9 | const location = useLocation();
10 | const history = useHistory();
11 | const [steps, setSteps] = useState(starter === 'cra' ? [...stepsCRA] : [...stepsVite]);
12 | const [stepsUrl, setStepsUrl] = useState({
13 | previousStep: '',
14 | nextStep: '',
15 | });
16 |
17 | useEffect(() => {
18 | const currentStepIndex = steps.find((ele) => ele.href === location.pathname).index;
19 | const nextStep = steps.find((ele) => ele.index === currentStepIndex + 1)?.href;
20 | const previousStep = steps.find((ele) => ele.index === currentStepIndex - 1)?.href;
21 | setStepsUrl({ previousStep: previousStep, nextStep: nextStep });
22 | }, [location.pathname]);
23 |
24 | const goNext = () => {
25 | history.push(stepsUrl.nextStep);
26 | };
27 |
28 | const goBack = () => {
29 | history.push(stepsUrl.previousStep);
30 | };
31 |
32 | return (
33 |
34 | {location.pathname !== '/creator' && location.pathname !== '/creatorVite' && (
35 |
42 | Previous
43 |
44 | Back
45 |
46 | )}
47 | {location.pathname !== '/creator/installation' &&
48 | location.pathname !== '/creatorVite/installation' && (
49 |
56 | Next
57 | Next
58 |
59 |
60 | )}
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/src/assets/logo_starter/remix.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Untitled 7
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/creator/components/DetailsBlock/DetailsForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 |
3 | import { formInputType } from '../../helpers/types';
4 |
5 | import { usePackageJson } from '../Contexts/PackageJsonProvider';
6 | import { TextArea, Input } from '../../../common/Input';
7 | import { Title } from '../../../common/Typo';
8 |
9 | export const DetailsForm = ({
10 | input,
11 | setInput,
12 | }: {
13 | input: formInputType;
14 | setInput: (input: formInputType) => void;
15 | }) => {
16 | const { packageJson, dispatchJson } = usePackageJson();
17 |
18 | const appname_ref = useRef(null);
19 | const description_ref = useRef(null);
20 | const version_ref = useRef(null);
21 |
22 | const handleChange = (e: React.ChangeEvent): void => {
23 | setInput({ ...input, [e.target.name]: e.target.value });
24 | packageJson.name = e.target.value;
25 | dispatchJson({
26 | type: 'CHANGE_INFO',
27 | payload: {
28 | name: appname_ref.current.value,
29 | version: version_ref.current.value,
30 | description: description_ref.current.value,
31 | },
32 | });
33 | };
34 |
35 | return (
36 |
37 |
38 |
39 |
40 | Name
41 |
42 |
43 |
53 |
54 |
55 |
56 |
57 | Version
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 | Description
75 |
76 |
77 |
86 |
87 |
88 |
89 | );
90 | };
91 |
--------------------------------------------------------------------------------
/src/creator/helpers/types.ts:
--------------------------------------------------------------------------------
1 | export type formInputType = {
2 | appname: string;
3 | description: string;
4 | version: string;
5 | typescript: boolean;
6 | prettier: boolean;
7 | flow: boolean;
8 | tailwind: boolean;
9 | bootstrap: boolean;
10 | reactbootstrap?: boolean;
11 | materialui?: boolean;
12 | styledcomponents?: boolean;
13 | normalize: boolean;
14 | reactrouter?: boolean;
15 | proptypes?: boolean;
16 | sourcemapexplorer: boolean;
17 | storybook?: boolean;
18 | };
19 |
20 | export type actionPackageType = {
21 | type: 'CHANGE_TYPE' | 'ADD' | 'REMOVE';
22 | payload: {
23 | destination: 'dependencies' | 'devDependencies';
24 | source?: string;
25 | name: string;
26 | size?: number;
27 | version?: string;
28 | dependencies?: string[];
29 | };
30 | };
31 |
32 | export type actionJsonType =
33 | | {
34 | type: 'CHANGE_INFO';
35 | payload: {
36 | name: string;
37 | version: string;
38 | description: string;
39 | };
40 | }
41 | | {
42 | type: 'CHANGE_SCRIPTS';
43 | payload: {
44 | scripts: string;
45 | };
46 | }
47 | | {
48 | type: 'ADD' | 'REMOVE';
49 | payload: {
50 | category: string;
51 | name: string;
52 | version?: string;
53 | description?: string;
54 | scripts?: string;
55 | };
56 | }
57 | | {
58 | type: string;
59 | payload: {
60 | category?: string;
61 | name?: string;
62 | version: string;
63 | description?: string;
64 | scripts?: string;
65 | };
66 | };
67 |
68 | export type listPackageType = packageFoundType[];
69 |
70 | export type packageFoundType = {
71 | name: string;
72 | version: string;
73 | description: string;
74 | score: number;
75 | };
76 |
77 | export type depStateType = {
78 | dependencies: { name: string; size: number; version: string; dependencies: string[] }[];
79 | devDependencies: { name: string; size: number; version: string; dependencies: string[] }[];
80 | };
81 |
82 | export type FileStructureType = {
83 | id: string;
84 | name: string;
85 | ancestor: string;
86 | isFolder: boolean;
87 | mode?: string;
88 | path: string;
89 | };
90 |
91 | export type structureStateType = FileStructureType[];
92 |
93 | export type AuthOptions = {
94 | hostname: string;
95 | clientId: string;
96 | clientSecret: string;
97 | };
98 |
99 | /**
100 | * The name of the tool to generate a react project, only CRA (create-react-app) or vite are supported at this time
101 | */
102 | export type starterType = 'vite' | 'cra';
103 |
104 | /**
105 | * The structure to display each features (e.g tailwind, typescript) which can be installed in a project from the feature page.
106 | */
107 | export type featureType = {
108 | title: string;
109 | description: string;
110 | name: string;
111 | packageName: string;
112 | link: string;
113 | };
114 |
--------------------------------------------------------------------------------
/src/manager/components/DependenciesBlock/DependenciesSearch.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect, MouseEvent } from 'react';
2 | import { SearchIcon } from '@heroicons/react/outline';
3 |
4 | import { searchPackages } from '../../../services/package.service';
5 | import { dependencyFoundType } from '../../../manager/helpers/types';
6 |
7 | import { Card } from '../../../common/Card';
8 | import { Input } from '../../../common/Input';
9 | import { ListDependenciesFound } from './ListDependenciesFound';
10 |
11 | export const DependenciesSearch = () => {
12 | const [input, setInput] = useState([]);
13 | const [isOpen, setIsOpen] = useState(false);
14 |
15 | const input_ref = useRef(null);
16 |
17 | const handleChange = async (e: React.ChangeEvent): Promise => {
18 | if (e.target.value !== '') {
19 | try {
20 | const packagesFound = await searchPackages(e.target.value);
21 | const results: dependencyFoundType[] = packagesFound.map((ele) => ({
22 | name: ele.package.name,
23 | version: ele.package.version,
24 | description: ele.package.description,
25 | score: ele.score.final,
26 | scoreDetail: {
27 | quality: ele.score.detail.quality,
28 | popularity: ele.score.detail.popularity,
29 | maintenance: ele.score.detail.maintenance,
30 | },
31 | links: {
32 | npm: ele.package.links.npm,
33 | repository: ele.package.links.repository,
34 | },
35 | }));
36 | setInput(results);
37 | } catch (error) {
38 | console.log('Error fetching the API');
39 | }
40 | } else {
41 | setInput([]);
42 | }
43 | };
44 |
45 | const handleClick = (e: MouseEvent): void => {
46 | if (input_ref.current.contains(e.target)) {
47 | setIsOpen(true);
48 | } else {
49 | setIsOpen(false);
50 | }
51 | };
52 |
53 | useEffect(() => {
54 | //@ts-ignore
55 | document.addEventListener('click', handleClick);
56 | //@ts-ignore
57 | return () => document.removeEventListener('click', handleClick);
58 | }, []);
59 |
60 | return (
61 |
62 |
63 |
Add packages
64 |
77 | {isOpen &&
}
78 |
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/src/assets/logo_starter/nextjs.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | next-black
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at leopold12d12@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
--------------------------------------------------------------------------------
/src/creator/components/ArchitectureBlock/CreateFolder.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent, Dispatch, FormEvent, useState } from 'react';
2 | import { nanoid } from 'nanoid';
3 |
4 | import { validateFileName } from '../../../utils/validateInput';
5 | import { structureStateType, FileStructureType } from '../../helpers/types';
6 |
7 | import { Input } from '../../../common/Input';
8 | import { Title } from '../../../common/Typo';
9 | import { ButtonOutline } from '../../..//common/Button';
10 |
11 | export const CreateFolder = ({
12 | structure,
13 | dispatchStructure,
14 | }: {
15 | structure: structureStateType;
16 | dispatchStructure: Dispatch;
17 | }) => {
18 | const [select, setSelect] = useState('1');
19 | const [foldername, setFoldername] = useState('');
20 | const [error, setError] = useState('');
21 |
22 | const handleChange = (e: ChangeEvent) => {
23 | setFoldername(e.target.value);
24 | };
25 |
26 | const handleSelect = (e: ChangeEvent) => {
27 | setSelect(e.target.options[e.target.selectedIndex].dataset.id);
28 | };
29 |
30 | const handleSubmit = (e: FormEvent) => {
31 | e.preventDefault();
32 | console.log(foldername);
33 | const isNameExist = structure.filter(
34 | (ele) =>
35 | ele.name.toLocaleLowerCase() === foldername.toLocaleLowerCase() && ele.ancestor === select
36 | );
37 | const isValid = validateFileName(foldername);
38 | if (isNameExist.length > 0) setError('Folder name already exist');
39 | else if (!isValid) setError('Invalid folder name');
40 | else {
41 | setError('');
42 | const ancestorPath = structure.find((ele) => ele.id === select).path;
43 | dispatchStructure({
44 | type: 'ADD',
45 | payload: {
46 | id: nanoid(),
47 | name: foldername,
48 | ancestor: select,
49 | isFolder: true,
50 | path: ancestorPath + '\\' + foldername,
51 | },
52 | });
53 | }
54 | };
55 |
56 | return (
57 |
95 | );
96 | };
97 |
--------------------------------------------------------------------------------
/src/manager/components/ComponentGenBlock/ComponentMode.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Listbox, Transition } from '@headlessui/react';
3 | import { CheckIcon, ChevronDownIcon } from '@heroicons/react/solid';
4 |
5 | const modeList = ['rfc', 'rcc', 'rfce', 'rafc', 'rafce', 'rafcp', 'rmc'];
6 |
7 | function classNames(...classes: string[]) {
8 | return classes.filter(Boolean).join(' ');
9 | }
10 |
11 | export const SelectComponentMode = ({
12 | mode,
13 | setMode,
14 | }: {
15 | mode: string;
16 | setMode: (mode: string) => void;
17 | }) => {
18 | return (
19 |
20 | {({ open }) => (
21 | <>
22 | Mode
23 |
24 |
25 | {mode}
26 |
27 |
28 |
29 |
30 |
31 |
38 |
39 | {modeList.map((ele, i) => (
40 |
43 | classNames(
44 | active ? 'text-white bg-indigo-600' : 'text-gray-900',
45 | 'relative cursor-default select-none py-2 pl-3 pr-9'
46 | )
47 | }
48 | value={ele}
49 | >
50 | {({ active, selected }) => (
51 | <>
52 |
58 | {ele}
59 |
60 |
61 | {selected ? (
62 |
68 |
69 |
70 | ) : null}
71 | >
72 | )}
73 |
74 | ))}
75 |
76 |
77 |
78 | >
79 | )}
80 |
81 | );
82 | };
83 |
--------------------------------------------------------------------------------