├── 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 |
    13 | 14 | <PackagesManager listPackages={listPackages} dispatchPackages={dispatch} /> 15 | </div> 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 | <ul className="h-72 flex flex-col space-y-2 overflow-y-auto"> 11 | {Object.keys(scripts) 12 | .filter((ele) => ele !== startScript) 13 | .map((ele, i) => ( 14 | <TasksItem key={i} taskName={ele} /> 15 | ))} 16 | </ul> 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 | <div id="layout" className="relative bg-gray-50 overflow-y-auto pt-8 flex flex-row h-screen"> 9 | <SideNav /> 10 | <div className="flex w-full px-8 py-7">{children}</div> 11 | <Toaster 12 | position="top-center" 13 | toastOptions={{ 14 | style: { 15 | margin: '300px', 16 | }, 17 | }} 18 | /> 19 | </div> 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 | <div className="space-y-2"> 8 | <DependenciesSearch /> 9 | <h2 className="font-bold py-2">Dependencies:</h2> 10 | <div className="flex space-x-8"> 11 | <div className="w-5/12 bg-white shadow rounded overflow-hidden"> 12 | <DependenciesList /> 13 | </div> 14 | <div className="w-7/12 "> 15 | <DependencySelectedCard /> 16 | </div> 17 | </div> 18 | </div> 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/runCmd.ts: -------------------------------------------------------------------------------- 1 | import child_process from 'child_process'; 2 | 3 | export const runCmd = (cmd: string): Promise<string> => { 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<string, string>, isDevDep: boolean) => { 4 | const newDependencies: Record<string, depType> = {}; 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<FileStructureType[]> => { 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 | <div className="w-8/12 mx-auto"> 13 | <div className="p-6 bg-white text-gray-800 rounded shadow flex flex-col items-align"> 14 | <h2 className="text-center">Treemap (TODO)</h2> 15 | <div id="treemap"></div> 16 | </div> 17 | </div> 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<typeof store.getState>; 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<FileStructureType[]>) => { 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<PackageContextType>(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 | <PackageContext.Provider value={{ packageJson, dispatchJson }}> 22 | {children} 23 | </PackageContext.Provider> 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 | <button 7 | className="my-1 mx-auto bg-indigo-500 opacity-100 px-4 py-1 outline-none 8 | tracking-wider text-white rounded hover:opacity-90 focus:outline-none transition duration-250" 9 | > 10 | Save 11 | </button> 12 | ); 13 | } else { 14 | return ( 15 | <button 16 | className="my-1 mx-auto bg-indigo-500 px-4 py-1 outline-none opacity-60 cursor-not-allowed 17 | tracking-wider text-white rounded hover:opacity-90 focus:outline-none transition duration-250" 18 | disabled 19 | > 20 | Save 21 | </button> 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<HTMLButtonElement>): Promise<void> => { 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 <Button onClick={handleSubmit}>Install</Button>; 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 | <div className="flex flex-col items-center justify-center w-full space-y-2"> 20 | <Title title="Features" /> 21 | <FeaturesList 22 | input={input} 23 | setInput={setInput} 24 | listFeatures={starter === 'vite' ? featuresListVite : featuresListCRA} 25 | /> 26 | </div> 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 | <div className="flex flex-col justify-center"> 15 | <div className="text-center font-bold my-2">Authentication...</div> 16 | </div> 17 | ); 18 | return ( 19 | <div className="flex flex-col justify-center pt-3 w-1/3"> 20 | <Title title="Initialize a github repository" /> 21 | {!github.token ? <ButtonGithubLogin setLoading={setLoading} /> : <GithubForm />} 22 | </div> 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<any>; 12 | }) => { 13 | return ( 14 | <div className="flex w-full justify-center divide-x-2 divide-gray-300 space-x-4"> 15 | <div className="w-1/3"> 16 | <TreeFolder structure={structure} dispatchStructure={dispatch} /> 17 | </div> 18 | <div className="w-2/3 flex divide-x-2 divide-gray-300"> 19 | <CreateFolder structure={structure} dispatchStructure={dispatch} /> 20 | <CreateComponent structure={structure} dispatchStructure={dispatch} /> 21 | </div> 22 | </div> 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<actionPackageType>; 15 | }) => { 16 | return ( 17 | <LoadingPackageProvider> 18 | <div className="flex flex-col justify-center items-center space-y-8 w-2/3"> 19 | <SearchPackages dispatchPackages={dispatchPackages} /> 20 | <ListPackages dispatchPackages={dispatchPackages} listPackages={listPackages} /> 21 | </div> 22 | </LoadingPackageProvider> 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 | <div 17 | style={{ bottom: -mouse.y + 10, left: mouse.x + 30 }} 18 | className={`absolute z-50 text-white text-sm bg-gray-700 w-56 opacity-95 p-2 rounded flex flex-col justify-start items-center shadow`} 19 | > 20 | <h2 className="text-center font-bold"> 21 | {data.name}@{data.version} 22 | </h2> 23 | <p className="text-center">{data.description}</p> 24 | <p className="font-bold text-indigo-500">Npm score: {data.score.toPrecision(3)}</p> 25 | </div> 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<string>('Edit'); 14 | 15 | return ( 16 | <div 17 | className="bg-blueGray rounded shadow hover:shadow-lg transition duration-200 p-6 w-full" 18 | style={{ width: '600px' }} 19 | > 20 | <ReadmeHeader tab={tab} setTab={setTab} /> 21 | <div className="w-full"> 22 | {tab === 'Edit' ? ( 23 | <ReadmeEdit readme={readme} setReadme={setReadme} /> 24 | ) : ( 25 | <ReadmePreview readme={readme} /> 26 | )} 27 | </div> 28 | </div> 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 | <Card> 20 | <div className="flex flex-col items-center"> 21 | <h2 className="font-medium pb-2"> 22 | {title} ({listPackages.length}) : 23 | </h2> 24 | <div className="overflow-y-auto max-h-56 w-full flex justify-center"> 25 | {loading ? <ClipLoader color="#6366F1" loading={loading} size={40} /> : children} 26 | </div> 27 | </div> 28 | </Card> 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 | <dependenciesContext.Provider value={{ listPackages, dispatch }}> 20 | {children} 21 | </dependenciesContext.Provider> 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 | <div className="text-center px-6 w-1/2"> 8 | <FolderOpenIcon strokeWidth={1} className="mx-auto h-12 w-12 text-gray-400" /> 9 | <h3 className="mt-2 text-sm font-medium text-gray-900">Open a project.</h3> 10 | <p className="mt-1 text-sm text-gray-500">Pick a React project and start working.</p> 11 | <div className="mt-6"> 12 | <Link 13 | to="/manager" 14 | className="inline-flex transition duration-200 items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" 15 | > 16 | Open project 17 | </Link> 18 | </div> 19 | </div> 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<HTMLInputElement | HTMLTextAreaElement>): void => { 11 | setReadme(e.target.value); 12 | }; 13 | 14 | return ( 15 | <div className="py-6 space-y-4 h-big flex flex-col items-center"> 16 | <textarea 17 | onChange={handleChange} 18 | className="shadow rounded py-2 px-2 19 | focus:outline-none 20 | w-full h-full resize-none transition duration-200 text-white 21 | placeholder-gray-500 border-gray-600 bg-gray-700 focus:ring-1 22 | focus:ring-indigo-500 focus:bg-gray-900 focus:border-transparent" 23 | placeholder="Write something about your project ?" 24 | name="content" 25 | value={readme} 26 | /> 27 | </div> 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<GithubContextType>(null); 22 | 23 | export const GithubProvider = ({ children }: { children: ReactNode }) => { 24 | const [github, setGithub] = useState<GithubStateType>(initialState); 25 | 26 | return <githubContext.Provider value={{ github, setGithub }}>{children}</githubContext.Provider>; 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 | <button 15 | type="button" 16 | className="flex-shrink-0 ml-1 h-4 w-4 rounded-full inline-flex items-center justify-center text-indigo-400 hover:bg-indigo-200 hover:text-indigo-500 focus:outline-none focus:bg-indigo-500 focus:text-white" 17 | onClick={handleClick} 18 | > 19 | <svg className="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8"> 20 | <path strokeLinecap="round" strokeWidth="1.5" d="M1 1l6 6m0-6L1 7" /> 21 | </svg> 22 | </button> 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 | <div className="flex flex-col w-1/2 bg-white shadow overflow-auto h-96 rounded-md divide-y divide-gray-200"> 17 | {listFeatures.map((feature) => ( 18 | <FeatureSwitch 19 | key={feature.name} 20 | name={feature.name} 21 | packageName={feature.packageName} 22 | setInput={setInput} 23 | input={input} 24 | > 25 | <div className="flex items-center text-gray-700 font-medium">{feature.title}</div> 26 | <div className="text-gray-400">{feature.description}</div> 27 | </FeatureSwitch> 28 | ))} 29 | </div> 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<HTMLButtonElement>) => { 5 | e.stopPropagation(); 6 | setIsEdit(true); 7 | }; 8 | 9 | return ( 10 | <button onClick={editNameItem}> 11 | <svg 12 | xmlns="http://www.w3.org/2000/svg" 13 | className="icon icon-tabler icon-tabler-pencil transform hover:rotate-45 transition duration-200" 14 | width="20" 15 | height="20" 16 | viewBox="0 0 24 24" 17 | strokeWidth="1.5" 18 | stroke="#3d3d3d" 19 | fill="none" 20 | strokeLinecap="round" 21 | strokeLinejoin="round" 22 | > 23 | <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 24 | <path d="M4 20h4l10.5 -10.5a1.5 1.5 0 0 0 -4 -4l-10.5 10.5v4" /> 25 | <line x1="13.5" y1="6.5" x2="17.5" y2="10.5" /> 26 | </svg> 27 | </button> 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 | <ul className="h-[26rem] w-full flex flex-col divide-y divide-gray-200 overflow-y-auto"> 12 | {Object.entries(dependencies.dependencies).map((ele) => ( 13 | <DependenciesItem 14 | key={ele[0]} 15 | depName={ele[0]} 16 | depVersion={ele[1].version} 17 | isDevDep={false} 18 | status={ele[1].status} 19 | /> 20 | ))} 21 | {Object.entries(dependencies.devDependencies).map((ele) => ( 22 | <DependenciesItem 23 | key={ele[0]} 24 | depName={ele[0]} 25 | depVersion={ele[1].version} 26 | isDevDep={true} 27 | status={ele[1].status} 28 | /> 29 | ))} 30 | </ul> 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<any>; 12 | }) => { 13 | const rootItem = structure.filter((ele: FileStructureType) => ele.ancestor === ''); 14 | 15 | return ( 16 | <div className="w-full shadow-sm bg-white rounded-sm flex flex-col h-96 overflow-y-auto"> 17 | <Title title="Project Structure" /> 18 | <ul className="py-2 w-full"> 19 | {rootItem.map((ele) => ( 20 | <TreeItem 21 | key={ele.id} 22 | id={ele.id} 23 | structure={structure} 24 | name={ele.name} 25 | isFolder={ele.isFolder} 26 | dispatchStructure={dispatchStructure} 27 | ancestor={ele.ancestor} 28 | /> 29 | ))} 30 | </ul> 31 | </div> 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 | <div className="w-full bg-white dark:bg-blueGray dark:text-white rounded shadow p-6 flex flex-col justify-start items-center hover:shadow-lg transition duration-200"> 23 | <h3 className="font-bold pb-4">Install size (kb) :</h3> 24 | <ChartSize baseSize={baseSize} depsSize={depsSize} devDepsSize={devDepsSize} /> 25 | </div> 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 <BadgeCheckIcon className="h-5 w-5 text-green-500" aria-hidden="true" />; 9 | case 'Error': 10 | return <ExclamationIcon className="h-5 w-5 text-red-600" aria-hidden="true" />; 11 | case 'Pending': 12 | return <CogIcon className="h-5 w-5 animate-spin text-gray-500 " aria-hidden="true" />; 13 | } 14 | }; 15 | 16 | const statusText: Record<string, string> = { 17 | Success: 'Success', 18 | Error: 'Error', 19 | Pending: 'Pending...', 20 | }; 21 | 22 | export const TaskStatut = ({ taskState }: { taskState: taskStateType }) => { 23 | if (taskState === 'Idle') { 24 | return <div>Idle</div>; 25 | } else { 26 | return ( 27 | <div className="flex items-center space-x-1"> 28 | {displayStateIcon(taskState)} 29 | <span>{statusText[taskState]}</span> 30 | </div> 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<dependencyFoundType>(null); 12 | const ref = useRef(null); 13 | 14 | return ( 15 | <> 16 | <ul 17 | className="absolute w-5/12 z-10 text-gray-800 top-23 max-h-medium overflow-y-scroll shadow" 18 | ref={ref} 19 | > 20 | {results.map((ele: dependencyFoundType) => ( 21 | <DependencyItemFound 22 | key={ele.name} 23 | dep={ele} 24 | setDepData={setDepData} 25 | toggleModal={toggleModal} 26 | /> 27 | ))} 28 | </ul> 29 | <DependencyModal depData={depData} open={open} toggleModal={toggleModal} /> 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 | <div> 36 | { 37 | // @ts-ignore 38 | <Pie 39 | width={200} 40 | height={190} 41 | data={data} 42 | options={{ maintainAspectRatio: false, color: 'gray' }} 43 | /> 44 | } 45 | </div> 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<terminalOutputProps>(({ 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 | <XTerm 31 | className={`${inModal ? 'h-48' : 'h-full'} bg-blueGray`} 32 | addons={[fitAddon]} 33 | ref={xtermRef} 34 | /> 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 <XTerm className="h-full w-full bg-blueGray pl-2" addons={[fitAddon]} ref={xtermRef} />; 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 | <button 24 | onClick={handleOpen} 25 | className="disabled:bg-gray-100 disabled:hover:bg-gray-100 bg-white hover:bg-gray-50 flex items-center justify-between w-full h-9 relative text-sm px-2" 26 | disabled={!!dependencies.dependencies[dep.name] || !!dependencies.dependencies[dep.name]} 27 | > 28 | <div>{dep.name}</div> 29 | <div className="h-5 w-5 text-indigo-600"> 30 | {(dependencies.dependencies[dep.name] || dependencies.dependencies[dep.name]) && ( 31 | <CheckIcon /> 32 | )} 33 | </div> 34 | </button> 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/utils/promisifyFs.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import fs from 'fs'; 3 | 4 | export const promisifyReadFs = (fullpath: string): Promise<string> => { 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<string> => { 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<string> => { 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<string> => { 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<any>; 10 | }) => { 11 | const removeItem = (e: MouseEvent<HTMLButtonElement>) => { 12 | e.stopPropagation(); 13 | dispatchStructure({ type: 'REMOVE', payload: { id: id } }); 14 | }; 15 | 16 | return ( 17 | <button onClick={removeItem}> 18 | <svg 19 | xmlns="http://www.w3.org/2000/svg" 20 | className="icon icon-tabler icon-tabler-trash transform hover:rotate-45 transition duration-200" 21 | width="20" 22 | height="20" 23 | viewBox="0 0 24 24" 24 | strokeWidth="1.5" 25 | stroke="#3d3d3d" 26 | fill="none" 27 | strokeLinecap="round" 28 | strokeLinejoin="round" 29 | > 30 | <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 31 | <line x1="4" y1="7" x2="20" y2="7" /> 32 | <line x1="10" y1="11" x2="10" y2="17" /> 33 | <line x1="14" y1="11" x2="14" y2="17" /> 34 | <path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" /> 35 | <path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" /> 36 | </svg> 37 | </button> 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<actionPackageType>; 15 | }) => { 16 | return ( 17 | <Droppable droppableId={type}> 18 | {(provided) => ( 19 | <ul 20 | className="text-center w-3/4 min-h-small" 21 | ref={provided.innerRef} 22 | {...provided.droppableProps} 23 | > 24 | {listPackages.length === 0 && ( 25 | <div className="pt-2 text-center text-sm text-gray-400">Empty list</div> 26 | )} 27 | {listPackages.map((ele, index) => ( 28 | <ItemPackage 29 | type={type} 30 | packageName={ele.name} 31 | key={ele.name} 32 | index={index} 33 | dispatchPackages={dispatchPackages} 34 | /> 35 | ))} 36 | {provided.placeholder} 37 | </ul> 38 | )} 39 | </Droppable> 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 | <SyntaxHighlighter 26 | style={atomDark} 27 | language={match[1]} 28 | PreTag="div" 29 | children={String(children).replace(/\n$/, '')} 30 | {...props} 31 | /> 32 | ) : ( 33 | <code className={className} {...props}> 34 | {children} 35 | </code> 36 | ); 37 | }, 38 | }; 39 | 40 | export const MarkdownWrapper = ({ content }: { content: string }) => { 41 | return ( 42 | <ReactMarkdown 43 | className="markdown" 44 | children={content} 45 | remarkPlugins={[gfm]} 46 | //@ts-ignore 47 | components={components} 48 | /> 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 | <SyntaxHighlighter 25 | style={atomDark} 26 | language={match[1]} 27 | PreTag="div" 28 | children={String(children).replace(/\n$/, '')} 29 | {...props} 30 | /> 31 | ) : ( 32 | <code className={className} {...props}> 33 | {children} 34 | </code> 35 | ); 36 | }, 37 | }; 38 | 39 | export const MarkdownWrapper = ({ content }: { content: string }) => { 40 | return ( 41 | <ReactMarkdown 42 | className="markdown py-0" 43 | remarkPlugins={[gfm]} 44 | children={content} 45 | //@ts-ignore 46 | components={components} 47 | /> 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<HTMLButtonElement>) => { 5 | const target = e.target as HTMLLIElement; 6 | setTab(target.innerText); 7 | }; 8 | 9 | return ( 10 | <div className="flex justify-between items-center"> 11 | <h1 className="font-extrabold text-white text-xl text-center">Readme.md</h1> 12 | <div className="space-x-1"> 13 | <button 14 | className={` text-white hover:bg-gray-600 focus:outline-none border-b-4 border-transparent focus:border-indigo-600 15 | ${ 16 | tab === 'Edit' ? 'border-indigo-600' : '' 17 | } rounded transition duration-200 px-2 py-1`} 18 | onClick={handleTab} 19 | > 20 | Edit 21 | </button> 22 | <button 23 | className={` text-white hover:bg-gray-600 focus:outline-none border-b-4 border-transparent focus:border-indigo-600 24 | ${ 25 | tab === 'Preview' ? 'border-indigo-600' : '' 26 | } rounded transition duration-200 px-2 py-1`} 27 | onClick={handleTab} 28 | > 29 | Preview 30 | </button> 31 | </div> 32 | </div> 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<projectStateType>) => { 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 | <div className="relative"> 16 | <div className="absolute z-10 w-40 px-4 mt-3 transform -translate-y-1/2 -top-20 -translate-x-1/3 sm:px-0 lg:max-w-3xl"> 17 | <div className="overflow-hidden rounded shadow bg-white"> 18 | <div className="flex flex-col items-center py-2 px-2"> 19 | <span className="text-sm font-medium text-gray-700 pb-2">Score:</span> 20 | <ul className="space-y-1 text-sm"> 21 | <li> 22 | <span className="text-sm text-gray-500">Quality:</span>{' '} 23 | {scoreDetail.quality.toFixed(3)} 24 | </li> 25 | <li> 26 | <span className="text-sm text-gray-500">Popularity:</span>{' '} 27 | {scoreDetail.popularity.toFixed(3)} 28 | </li> 29 | <li> 30 | <span className="text-sm text-gray-500">Maintenance:</span>{' '} 31 | {scoreDetail.maintenance.toFixed(3)} 32 | </li> 33 | </ul> 34 | </div> 35 | </div> 36 | </div> 37 | </div> 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<actionPackageType>; 16 | }) => { 17 | return ( 18 | <Draggable draggableId={packageName} index={index} key={packageName}> 19 | {(provided, snapshot) => { 20 | console.log(snapshot.isDragging); 21 | return ( 22 | <li 23 | ref={provided.innerRef} 24 | {...provided.draggableProps} 25 | {...provided.dragHandleProps} 26 | className="bg-indigo-600 text-gray-200 border-1 text-sm font-semibold opacity-90 hover:opacity-80 transition duration-200 27 | rounded flex items-center justify-start shadow h-9 my-2 w-full" 28 | > 29 | <ButtonRemovePackage 30 | type={type} 31 | packageName={packageName} 32 | dispatchPackages={dispatchPackages} 33 | /> 34 | <div className="text-center w-full px-2 overflow-ellipsis">{packageName}</div> 35 | </li> 36 | ); 37 | }} 38 | </Draggable> 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<any[]> => { 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<any> => { 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<any> => { 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<actionPackageType>; 17 | }) => { 18 | const ref = useRef(null); 19 | 20 | const [open, toggleModal] = useModal(); 21 | const [depData, setDepData] = useState<dependencyFoundType>(null); 22 | 23 | return ( 24 | <> 25 | <ul 26 | className="absolute left-1/2 -translate-x-1/2 w-7/12 z-10 text-gray-800 max-h-medium overflow-y-scroll shadow" 27 | ref={ref} 28 | > 29 | {results.map((ele) => ( 30 | <ItemPackageFound 31 | key={ele.name} 32 | packageData={ele} 33 | dispatchPackages={dispatchPackages} 34 | dep={ele} 35 | setDepData={setDepData} 36 | toggleModal={toggleModal} 37 | /> 38 | ))} 39 | </ul> 40 | <DependencyModalCreator 41 | dispatchPackages={dispatchPackages} 42 | depData={depData} 43 | open={open} 44 | toggleModal={toggleModal} 45 | /> 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 | <svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/> 3 | <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/> 4 | <defs> 5 | <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse"> 6 | <stop stop-color="#41D1FF"/> 7 | <stop offset="1" stop-color="#BD34FE"/> 8 | </linearGradient> 9 | <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse"> 10 | <stop stop-color="#FFEA83"/> 11 | <stop offset="0.0833333" stop-color="#FFDD35"/> 12 | <stop offset="1" stop-color="#FFA800"/> 13 | </linearGradient> 14 | </defs> 15 | </svg> 16 | -------------------------------------------------------------------------------- /src/assets/waves.svg: -------------------------------------------------------------------------------- 1 | <svg id="visual" viewBox="0 0 900 600" width="900" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><path d="M0 215L30 209.8C60 204.7 120 194.3 180 189.8C240 185.3 300 186.7 360 190.3C420 194 480 200 540 206C600 212 660 218 720 217.7C780 217.3 840 210.7 870 207.3L900 204L900 0L870 0C840 0 780 0 720 0C660 0 600 0 540 0C480 0 420 0 360 0C300 0 240 0 180 0C120 0 60 0 30 0L0 0Z" fill="#272a44"></path><path d="M0 185L30 181C60 177 120 169 180 164.3C240 159.7 300 158.3 360 163.2C420 168 480 179 540 182.7C600 186.3 660 182.7 720 180.7C780 178.7 840 178.3 870 178.2L900 178L900 0L870 0C840 0 780 0 720 0C660 0 600 0 540 0C480 0 420 0 360 0C300 0 240 0 180 0C120 0 60 0 30 0L0 0Z" fill="#23263f"></path><path d="M0 141L30 139.7C60 138.3 120 135.7 180 134.8C240 134 300 135 360 137.5C420 140 480 144 540 144.7C600 145.3 660 142.7 720 134.3C780 126 840 112 870 105L900 98L900 0L870 0C840 0 780 0 720 0C660 0 600 0 540 0C480 0 420 0 360 0C300 0 240 0 180 0C120 0 60 0 30 0L0 0Z" fill="#1f223b"></path><path d="M0 71L30 72C60 73 120 75 180 80.5C240 86 300 95 360 98.5C420 102 480 100 540 100.3C600 100.7 660 103.3 720 98C780 92.7 840 79.3 870 72.7L900 66L900 0L870 0C840 0 780 0 720 0C660 0 600 0 540 0C480 0 420 0 360 0C300 0 240 0 180 0C120 0 60 0 30 0L0 0Z" fill="#1b1e37"></path><path d="M0 57L30 58.5C60 60 120 63 180 60.7C240 58.3 300 50.7 360 49.7C420 48.7 480 54.3 540 56.3C600 58.3 660 56.7 720 53.7C780 50.7 840 46.3 870 44.2L900 42L900 0L870 0C840 0 780 0 720 0C660 0 600 0 540 0C480 0 420 0 360 0C300 0 240 0 180 0C120 0 60 0 30 0L0 0Z" fill="#181b33"></path></svg> -------------------------------------------------------------------------------- /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<any> => { 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<actionPackageType>; 14 | }) => { 15 | const { dispatchJson } = usePackageJson(); 16 | 17 | const removePackages = (e: React.MouseEvent<HTMLElement>): 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 | <button 30 | data-name={packageName} 31 | className="px-2 h-full border-none focus:bg-white focus:outline-none transition duration-200" 32 | onClick={removePackages} 33 | > 34 | <svg 35 | xmlns="http://www.w3.org/2000/svg" 36 | className="h-5 w-5" 37 | viewBox="0 0 20 20" 38 | fill="#e5e7eb" 39 | > 40 | <path 41 | fillRule="evenodd" 42 | d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" 43 | clipRule="evenodd" 44 | /> 45 | </svg> 46 | </button> 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 | <div className="flex flex-col bg-white shadow overflow-auto rounded-md divide-y divide-gray-200"> 18 | <ComponentSwitch name="function_component" setInput={setInput} input={input}> 19 | <div className="flex items-center text-gray-700 font-medium">Function component</div> 20 | <div className="text-gray-400">Lorem dolor sit amet consectetur adipisicing elit.</div> 21 | </ComponentSwitch> 22 | <ComponentSwitch name="styled_component" setInput={setInput} input={input}> 23 | <div className="flex items-center text-gray-700 font-medium">Styled component</div> 24 | <div className="text-gray-400">Lorem ipsum dolor sit amet adipisicing elit.</div> 25 | </ComponentSwitch> 26 | <ComponentSwitch name="typescript" setInput={setInput} input={input}> 27 | <div className="flex items-center text-gray-700 font-medium">Typescript</div> 28 | <div className="text-gray-400"> 29 | Lorem ipsum dolor sit amet consectetur adipisicing elit. 30 | </div> 31 | </ComponentSwitch> 32 | </div> 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<actionPackageType>; 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 | <li 26 | key={packageData.name} 27 | className="flex items-center justify-center w-full h-9 relative" 28 | onClick={handleOpen} 29 | > 30 | {Object.keys(packageJson.dependencies).includes(packageData.name) || 31 | Object.keys(packageJson.devDependencies).includes(packageData.name) ? ( 32 | <div className="flex items-center justify-center text-sm font-semibold bg-gray-200 w-full h-full px-2"> 33 | {packageData.name} 34 | </div> 35 | ) : ( 36 | <div 37 | className="flex items-center justify-center text-sm font-semibold bg-white hover:bg-blue-100 transition duration-200 38 | w-full h-full px-2 cursor-pointer" 39 | > 40 | {packageData.name} 41 | </div> 42 | )} 43 | </li> 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 | <li> 28 | <button 29 | onClick={handleClick} 30 | className={`${ 31 | depSelectedName === depName 32 | ? 'bg-indigo-600 text-white hover:bg-indigo-700' 33 | : 'bg-white text-gray-700 hover:bg-gray-50' 34 | } cursor-pointer flex justify-between items-center w-full h-full px-6 py-4 transition duration-200`} 35 | > 36 | <div className="overflow-hidden font-medium text-sm overflow-ellipsis w-[11rem] whitespace-nowrap"> 37 | {depName} 38 | </div> 39 | {status === 'Pending' ? ( 40 | <CogIcon className="h-5 w-5 animate-spin text-gray-700 " aria-hidden="true" /> 41 | ) : ( 42 | <div className="text-sm">{depVersion}</div> 43 | )} 44 | </button> 45 | </li> 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 | <button 24 | onClick={handleAuthentication} 25 | className="flex items-center text-sm mx-auto shadow bg-gray-700 opacity-100 px-4 py-2 outline-none font-semibold 26 | tracking-wider text-white rounded hover:opacity-90 focus:outline-none transition duration-250" 27 | > 28 | <span className="mr-2">Login</span> 29 | <svg viewBox="0 0 24 24" aria-hidden="true" fill="#fff" width="20"> 30 | <path d="M12 2A10 10 0 002 12a10 10 0 006.8 9.5c.5 0 .7-.2.7-.5v-1.7C6.7 20 6.1 18 6.1 18c-.4-1.2-1-1.5-1-1.5-1-.6 0-.6 0-.6 1 0 1.5 1 1.5 1 .9 1.6 2.4 1.1 3 .9 0-.7.3-1.1.6-1.4-2.2-.2-4.6-1-4.6-4.9 0-1.1.4-2 1-2.7 0-.3-.4-1.3.2-2.7 0 0 .8-.2 2.7 1a9.4 9.4 0 015 0c2-1.2 2.8-1 2.8-1 .5 1.4.1 2.4 0 2.7.7.7 1 1.6 1 2.7 0 3.8-2.3 4.7-4.5 5 .4.2.7.8.7 1.8V21c0 .3.2.6.7.5 4-1.3 6.8-5 6.8-9.5A10 10 0 0012 2z"></path> 31 | </svg> 32 | </button> 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 | <div className="text-center px-6 w-1/2"> 13 | <svg 14 | className="mx-auto h-12 w-12 text-gray-400" 15 | fill="none" 16 | viewBox="0 0 24 24" 17 | stroke="currentColor" 18 | aria-hidden="true" 19 | > 20 | <path 21 | vectorEffect="non-scaling-stroke" 22 | strokeLinecap="round" 23 | strokeLinejoin="round" 24 | strokeWidth={2} 25 | d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" 26 | /> 27 | </svg> 28 | <h3 className="mt-2 text-sm font-medium text-gray-900">Create a project.</h3> 29 | <p className="mt-1 text-sm text-gray-500">Get started with a fresh new React project.</p> 30 | <div className="mt-6"> 31 | <button 32 | className="transition duration-200 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" 33 | onClick={() => toggleModal()} 34 | > 35 | <PlusIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" /> 36 | New Project 37 | </button> 38 | <CreatorMenuModal open={open} toggleModal={toggleModal} /> 39 | </div> 40 | </div> 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 | <Card> 23 | <div className="flex justify-between items-center h-full"> 24 | <div className="w-1/4 text-left font-bold text-gray-700 hover:opacity-90 transition duration-250"> 25 | {taskName} 26 | </div> 27 | <div className="w-1/4 text-gray-500"> 28 | <TaskStatut taskState={task.taskState} /> 29 | </div> 30 | <div className="w-1/4"> 31 | <button 32 | onClick={toggleModal} 33 | className="px-3 py-1 rounded-full text-gray-600 text-sm bg-transparent border-2 border-indigo-400 hover:opacity-80 transition duration-200" 34 | > 35 | Open logs 36 | </button> 37 | </div> 38 | <TaskSwitch taskName={taskName} enabled={task.enabled} taskState={task.taskState} /> 39 | </div> 40 | <TaskModal taskName={taskName} open={open} toggleModal={toggleModal} /> 41 | </Card> 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 | <div> 9 | 10 | </div> 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 | <div> 21 | 22 | </div> 23 | ) 24 | } 25 | };`; 26 | 27 | case 'rfce': 28 | return `import React from 'react'; 29 | 30 | function ${name}() { 31 | return <div></div> 32 | }; 33 | 34 | export default ${name};`; 35 | 36 | case 'rafc': 37 | return `import React from 'react'; 38 | 39 | export const ${name} = () => { 40 | return ( 41 | <div> 42 | 43 | </div> 44 | ) 45 | };`; 46 | 47 | case 'rafce': 48 | return `import React from 'react'; 49 | 50 | const ${name} = () => { 51 | return <div></div> 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 | <div></div> 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 | <div></div> 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<HTMLInputElement>; 11 | }; 12 | 13 | type textAreaProps = { 14 | className?: string; 15 | value?: string; 16 | name?: string; 17 | id?: string; 18 | placeholder?: string; 19 | onChange?: ChangeEventHandler<HTMLTextAreaElement>; 20 | }; 21 | 22 | export const Input = React.forwardRef<any, inputProps>( 23 | ({ className, value, name, id, placeholder, type, onChange }, ref) => { 24 | return ( 25 | <input 26 | type={type} 27 | className={`${ 28 | className ? className : '' 29 | } block text-center bg-white border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm`} 30 | value={value} 31 | name={name} 32 | id={id} 33 | placeholder={placeholder} 34 | onChange={onChange} 35 | ref={ref} 36 | /> 37 | ); 38 | } 39 | ); 40 | 41 | export const TextArea = React.forwardRef<any, textAreaProps>( 42 | ({ className, value, name, id, placeholder, onChange }, ref) => { 43 | return ( 44 | <textarea 45 | className={`${ 46 | className ? className : '' 47 | } block text-center bg-white border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-sm`} 48 | value={value} 49 | name={name} 50 | id={id} 51 | placeholder={placeholder} 52 | onChange={onChange} 53 | ref={ref} 54 | /> 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 | <div className="flex flex-col items-center space-y-6"> 35 | <h1 className="text-lg text-gray-700 font-bold">Component generator</h1> 36 | <div className="space-y-2 text-center"> 37 | <ButtonOutline onClick={selectLocation}>Select folder</ButtonOutline> 38 | {location && ( 39 | <div className="text-sm"> 40 | Location:{' '} 41 | <span className="text-gray-700 hover:text-black transition duration-200"> 42 | {location} 43 | </span> 44 | </div> 45 | )} 46 | </div> 47 | <FormComponent input={input} setInput={setInput} location={location} /> 48 | </div> 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 | <div className="flex items-center relative justify-center space-x-3"> 34 | {starterName !== '' && ( 35 | <img className="w-8 h-8" src={pathToLogo(starterName)} alt="logo_starter" /> 36 | )} 37 | <span className="text-center text-2xl font-extrabold">{projectName}</span> 38 | <div className="relative"> 39 | <span className="flex items-center justify-center h-3 w-3 absolute"> 40 | <span 41 | className={`${taskState === 'Pending' ? 'bg-green-400 animate-ping' : 'bg-gray-100'} 42 | absolute inline-flex h-full w-full rounded-full`} 43 | aria-hidden="true" 44 | /> 45 | <span 46 | className={`${ 47 | taskState === 'Pending' ? 'bg-green-400 ' : 'bg-gray-400' 48 | } relative inline-flex rounded-full h-2 w-2`} 49 | /> 50 | </span> 51 | </div> 52 | </div> 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<formInputType>(initialState); 18 | const [structure, dispatch] = useReducer( 19 | structureReducer, 20 | JSON.parse(JSON.stringify(initialStructure)) 21 | ); 22 | 23 | const { path } = useRouteMatch(); 24 | 25 | return ( 26 | <LayoutCreator starter="cra"> 27 | <Route exact path={path} render={() => <DetailsPage input={input} setInput={setInput} />} /> 28 | <Route 29 | exact 30 | path={`${path}/features`} 31 | render={() => <FeaturesPage input={input} setInput={setInput} starter="cra" />} 32 | /> 33 | <Route exact path={`${path}/packages`} render={() => <PackagesPage />} /> 34 | <Route 35 | exact 36 | path={`${path}/components`} 37 | render={() => <ArchitecturePage structure={structure} dispatch={dispatch} />} 38 | /> 39 | <Route 40 | exact 41 | path={`${path}/installation`} 42 | render={() => <InstallationPage input={input} structure={structure} starter="cra" />} 43 | /> 44 | </LayoutCreator> 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<formInputType>(initialStateVite); 18 | const [structure, dispatch] = useReducer( 19 | structureReducer, 20 | JSON.parse(JSON.stringify(initialStructure)) 21 | ); 22 | 23 | const { path } = useRouteMatch(); 24 | 25 | return ( 26 | <LayoutCreator starter="vite"> 27 | <Route exact path={path} render={() => <DetailsPage input={input} setInput={setInput} />} /> 28 | <Route 29 | exact 30 | path={`${path}/features`} 31 | render={() => <FeaturesPage input={input} setInput={setInput} starter="vite" />} 32 | /> 33 | <Route exact path={`${path}/packages`} render={() => <PackagesPage />} /> 34 | <Route 35 | exact 36 | path={`${path}/components`} 37 | render={() => <ArchitecturePage structure={structure} dispatch={dispatch} />} 38 | /> 39 | <Route 40 | exact 41 | path={`${path}/installation`} 42 | render={() => <InstallationPage input={input} structure={structure} starter="vite" />} 43 | /> 44 | </LayoutCreator> 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 | <div id="layout" className="relative bg-gray-50 h-screen"> 19 | <div className="absolute left-6 top-12"> 20 | <Link 21 | to="/" 22 | className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 transition duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" 23 | > 24 | <ArrowLeftIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" /> 25 | Menu 26 | </Link> 27 | </div> 28 | <div className="absolute right-6 top-12"> 29 | <img src="../assets/icons/png/32x32.png" alt="icon" /> 30 | </div> 31 | <div className="flex justify-center items-center space-x-2 text-center pt-12 pb-4"> 32 | <h1 className=" text-lg text-black">Creation process</h1> 33 | </div> 34 | <div className="flex justify-center mx-auto pt-4 pb-12"> 35 | <StepBar starter={starter} /> 36 | </div> 37 | <div className="flex w-full px-8 py-4">{children}</div> 38 | <div className="w-full text-center absolute bottom-6"> 39 | <StepControlButtons starter={starter} /> 40 | </div> 41 | <Toaster 42 | position="top-center" 43 | toastOptions={{ 44 | style: { 45 | margin: '300px', 46 | }, 47 | }} 48 | /> 49 | </div> 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 | <Switch.Group> 22 | <div className="flex items-center hover:bg-gray-50 pr-2 transition duration-200"> 23 | <Switch.Label className="flex-grow cursor-pointer py-2 pl-4 text-sm"> 24 | {children} 25 | </Switch.Label> 26 | <Switch 27 | checked={input[name]} 28 | onChange={handleChange} 29 | className={`flex-shrink-0 group relative py-2 rounded-full inline-flex items-center justify-center h-full w-10 cursor-pointer focus:outline-none`} 30 | > 31 | <span className="sr-only">Enable</span> 32 | <span 33 | aria-hidden="true" 34 | className="pointer-events-none absolute w-full h-full rounded-md" 35 | /> 36 | <span 37 | aria-hidden="true" 38 | className={` 39 | ${input[name] ? 'bg-indigo-600' : 'bg-gray-300'} 40 | pointer-events-none absolute h-4 w-9 mx-auto rounded-full transition-colors ease-in-out duration-200 41 | `} 42 | /> 43 | <span 44 | className={`${ 45 | input[name] ? 'translate-x-5' : 'translate-x-1' 46 | } pointer-events-none absolute left-0 inline-block h-5 w-5 border border-gray-200 rounded-full bg-white shadow transform ring-0 transition-transform ease-in-out duration-200`} 47 | /> 48 | </Switch> 49 | </div> 50 | </Switch.Group> 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<string, taskType>; 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<string, depType>; 62 | devDependencies: Record<string, depType>; 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<HTMLInputElement | HTMLTextAreaElement>): 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 | <Card> 31 | <h2 className="font-extrabold text-gray-700 text-xl text-center py-2">Scripts edit</h2> 32 | <form className="flex flex-col items-center" onSubmit={handleAdd}> 33 | <div className="flex flex-col sm:flex-row justify-center flex-wrap py-4"> 34 | <Input 35 | className="m-1 w-3/4" 36 | onChange={handleChange} 37 | type="text" 38 | name="name" 39 | placeholder="Script name..." 40 | value={input.name} 41 | /> 42 | <Input 43 | onChange={handleChange} 44 | className="m-1 w-3/4" 45 | type="text" 46 | name="cmd" 47 | placeholder="Command..." 48 | value={input.cmd} 49 | /> 50 | </div> 51 | <Button>Add</Button> 52 | </form> 53 | <ListScripts scripts={packageJson.scripts} /> 54 | </Card> 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(<FakeFeaturePage />); 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(<FakeFeaturePage />); 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 | <PackageJsonProvider packageJson={packageJson} dispatchJson={dispatchJson}> 48 | <FeaturesPage input={input} setInput={setInput} /> 49 | <CardPackageJson /> 50 | </PackageJsonProvider> 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<HTMLInputElement>) => { 9 | setGithub({ ...github, reponame: e.target.value }); 10 | }; 11 | 12 | const handleChangeCheckbox = (e: ChangeEvent<HTMLInputElement>) => { 13 | // @ts-ignore 14 | setGithub({ ...github, visibility: e.target.value }); 15 | }; 16 | 17 | return ( 18 | <div className="space-y-2"> 19 | <div> 20 | <label htmlFor="reponame" className="block text-sm font-medium text-gray-700"> 21 | Name 22 | </label> 23 | <Input 24 | type="text" 25 | value={github.reponame} 26 | className="w-full" 27 | name="reponame" 28 | id="reponame" 29 | placeholder="repo name" 30 | onChange={handleChange} 31 | /> 32 | </div> 33 | <div> 34 | <div className="flex justify-center space-x-4" onChange={handleChangeCheckbox}> 35 | <div className="space-x-2"> 36 | <input 37 | type="radio" 38 | id="public" 39 | name="visibility" 40 | value="public" 41 | checked={github.visibility === 'public'} 42 | /> 43 | <label className="text-sm" htmlFor="public"> 44 | Public 45 | </label> 46 | </div> 47 | <div className="space-x-2"> 48 | <input 49 | type="radio" 50 | id="private" 51 | name="visibility" 52 | value="private" 53 | checked={github.visibility === 'private'} 54 | /> 55 | <label className="text-sm" htmlFor="private"> 56 | Private 57 | </label> 58 | </div> 59 | </div> 60 | </div> 61 | </div> 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 | <Card> 20 | <div className="flex h-52"> 21 | <div className="w-5/12 flex flex-col justify-center items-center space-y-3 relative"> 22 | <h2 className="text-black font-bold text-lg">Development server</h2> 23 | <div className="justify-self-center"> 24 | <TaskMainSwitch taskName={startScript} /> 25 | </div> 26 | <button 27 | id="open_project" 28 | className="flex items-center absolute bottom-4 space-x-1 cursor-pointer" 29 | onClick={redirectToEditor} 30 | > 31 | <svg 32 | xmlns="http://www.w3.org/2000/svg" 33 | className="h-4 w-4" 34 | fill="none" 35 | viewBox="0 0 24 24" 36 | stroke="#6b7280" 37 | strokeWidth={2} 38 | > 39 | <path 40 | strokeLinecap="round" 41 | strokeLinejoin="round" 42 | d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" 43 | /> 44 | </svg> 45 | <span className="text-sm text-gray-500">Open in editor</span> 46 | </button> 47 | </div> 48 | <div className="w-7/12"> 49 | <TerminalOutput taskName={startScript} /> 50 | </div> 51 | </div> 52 | </Card> 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<actionPackageType>; 14 | }) => { 15 | const { loading, setLoading } = useLoading(); 16 | const { dispatchJson } = usePackageJson(); 17 | 18 | const addPackages = async (e: React.MouseEvent<HTMLElement>): Promise<void> => { 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 | <button 53 | className="flex items-center justify-center text-sm font-semibold bg-white hover:bg-blue-100 transition duration-200 54 | w-full h-full px-2 cursor-pointer" 55 | data-name={packageData.name} 56 | data-version={packageData.version} 57 | onClick={addPackages} 58 | disabled={loading} 59 | > 60 | {packageData.name} 61 | </button> 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<any> => { 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 | <div id="titlebar"> 7 | <div id="drag-region"> 8 | <div id="window-title"> 9 | <a id="button_git"> 10 | <svg viewBox="0 0 24 24" aria-hidden="true" fill="#fff" width="20"> 11 | <path d="M12 2A10 10 0 002 12a10 10 0 006.8 9.5c.5 0 .7-.2.7-.5v-1.7C6.7 20 6.1 18 6.1 18c-.4-1.2-1-1.5-1-1.5-1-.6 0-.6 0-.6 1 0 1.5 1 1.5 1 .9 1.6 2.4 1.1 3 .9 0-.7.3-1.1.6-1.4-2.2-.2-4.6-1-4.6-4.9 0-1.1.4-2 1-2.7 0-.3-.4-1.3.2-2.7 0 0 .8-.2 2.7 1a9.4 9.4 0 015 0c2-1.2 2.8-1 2.8-1 .5 1.4.1 2.4 0 2.7.7.7 1 1.6 1 2.7 0 3.8-2.3 4.7-4.5 5 .4.2.7.8.7 1.8V21c0 .3.2.6.7.5 4-1.3 6.8-5 6.8-9.5A10 10 0 0012 2z"></path> 12 | </svg> 13 | </a> 14 | </div> 15 | <div id="window-controls"> 16 | <div className="button" id="min-button"> 17 | <svg 18 | xmlns="http://www.w3.org/2000/svg" 19 | className="icon icon-tabler icon-tabler-minus" 20 | width="22" 21 | height="22" 22 | viewBox="0 0 24 24" 23 | strokeWidth="1.5" 24 | stroke="#e5e7eb" 25 | fill="none" 26 | strokeLinecap="round" 27 | strokeLinejoin="round" 28 | > 29 | <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 30 | <line x1="5" y1="12" x2="19" y2="12" /> 31 | </svg> 32 | </div> 33 | <div className="button" id="close-button"> 34 | <svg 35 | xmlns="http://www.w3.org/2000/svg" 36 | className="icon icon-tabler icon-tabler-x" 37 | width="22" 38 | height="22" 39 | viewBox="0 0 24 24" 40 | strokeWidth="1.5" 41 | stroke="#e5e7eb" 42 | fill="none" 43 | strokeLinecap="round" 44 | strokeLinejoin="round" 45 | > 46 | <path stroke="none" d="M0 0h24v24H0z" fill="none" /> 47 | <line x1="18" y1="6" x2="6" y2="18" /> 48 | <line x1="6" y1="6" x2="18" y2="18" /> 49 | </svg> 50 | </div> 51 | </div> 52 | </div> 53 | </div> 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 | <div id="layout" className="relative bg-gray-50 h-screen"> 19 | <div className="flex items-center h-full w-full px-8 py-4"> 20 | <div className="flex flex-col items-center justify-center w-full space-y-4"> 21 | <h1 className="font-bold text-2xl text-indigo-500 leading-4 py-6">Congratulation !</h1> 22 | <div className="w-1/3"> 23 | <img src="../assets/undraw_well_done.svg" alt="well_done" /> 24 | </div> 25 | <p className="pt-6 pb-2">Your project has been created successfully.</p> 26 | <button 27 | id="open_project" 28 | className="flex items-center space-x-1 cursor-pointer mb-6" 29 | onClick={redirectToEditor} 30 | > 31 | <svg 32 | xmlns="http://www.w3.org/2000/svg" 33 | className="h-4 w-4" 34 | fill="none" 35 | viewBox="0 0 24 24" 36 | stroke="#575c65" 37 | strokeWidth={2} 38 | > 39 | <path 40 | strokeLinecap="round" 41 | strokeLinejoin="round" 42 | d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" 43 | /> 44 | </svg> 45 | <span className="text-sm text-gray-700">Open in editor</span> 46 | </button> 47 | <Link 48 | to="/" 49 | className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" 50 | > 51 | <ArrowLeftIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" /> 52 | Menu 53 | </Link> 54 | </div> 55 | </div> 56 | </div> 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(<FakeDetailsPage />); 21 | expect(tree).toMatchSnapshot(); 22 | }); 23 | 24 | it('should change the app name', async () => { 25 | render(<FakeDetailsPage />); 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(<FakeDetailsPage />); 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(<FakeDetailsPage />); 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 | <PackageJsonProvider packageJson={packageJson} dispatchJson={dispatchJson}> 54 | <DetailsForm input={input} setInput={setInput} /> 55 | <CardPackageJson /> 56 | </PackageJsonProvider> 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<actionPackageType>; 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 | <DragDropContext onDragEnd={onDragEnd}> 53 | <div className="flex w-full justify-around items-start space-x-4"> 54 | <CardDependencies title="Dependencies" listPackages={listPackages.dependencies}> 55 | <ListPackagesSelected 56 | type="dependencies" 57 | dispatchPackages={dispatchPackages} 58 | listPackages={listPackages.dependencies} 59 | /> 60 | </CardDependencies> 61 | <CardDependencies title="Dev dependencies" listPackages={listPackages.devDependencies}> 62 | <ListPackagesSelected 63 | type="devDependencies" 64 | dispatchPackages={dispatchPackages} 65 | listPackages={listPackages.devDependencies} 66 | /> 67 | </CardDependencies> 68 | </div> 69 | </DragDropContext> 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 | <Switch.Group> 38 | <div className="flex items-center justify-between hover:bg-gray-50 pr-2 transition duration-200"> 39 | <Switch.Label className="flex-grow cursor-pointer py-2 pl-4 text-sm"> 40 | {children} 41 | </Switch.Label> 42 | <Switch 43 | checked={input[name]} 44 | onChange={handleChange} 45 | className={`flex-shrink-0 group relative py-2 rounded-full inline-flex items-center justify-center h-full w-10 cursor-pointer focus:outline-none`} 46 | > 47 | <span className="sr-only">Run</span> 48 | <span 49 | aria-hidden="true" 50 | className="pointer-events-none absolute w-full h-full rounded-md" 51 | /> 52 | <span 53 | aria-hidden="true" 54 | className={` 55 | ${input[name] ? 'bg-indigo-600' : 'bg-gray-300'} 56 | pointer-events-none absolute h-4 w-9 mx-auto rounded-full transition-colors ease-in-out duration-200 57 | `} 58 | /> 59 | <span 60 | className={`${ 61 | input[name] ? 'translate-x-5' : 'translate-x-1' 62 | } pointer-events-none absolute left-0 inline-block h-5 w-5 border border-gray-200 rounded-full bg-white shadow transform ring-0 transition-transform ease-in-out duration-200`} 63 | /> 64 | </Switch> 65 | </div> 66 | </Switch.Group> 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<dependenciesStateType> 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<depSelectType>) => { 28 | state.depSelected = action.payload; 29 | }, 30 | updateDep: (state: dependenciesStateType, action: PayloadAction<depType>) => { 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<depType>) => { 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<tasksStateType>) => { 14 | state.tasks = action.payload.tasks; 15 | }, 16 | switchTask: (state, action: PayloadAction<string>) => { 17 | state.tasks[action.payload].enabled = !state.tasks[action.payload].enabled; 18 | }, 19 | idleTask: (state, action: PayloadAction<string>) => { 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<string>) => { 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<string>) => { 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<string>) => { 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<string>) => { 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<HTMLButtonElement>; 10 | }) => { 11 | return ( 12 | <button 13 | onClick={onClick} 14 | className="mx-auto px-4 py-2 border border-transparent text-base font-medium rounded shadow text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-200" 15 | > 16 | {children} 17 | </button> 18 | ); 19 | }; 20 | 21 | export const ButtonSecondary = ({ 22 | children, 23 | onClick, 24 | }: { 25 | children: ReactNode; 26 | onClick?: MouseEventHandler<HTMLButtonElement>; 27 | }) => { 28 | return ( 29 | <button 30 | onClick={onClick} 31 | className="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-4 font-medium shadow rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-200" 32 | > 33 | {children} 34 | </button> 35 | ); 36 | }; 37 | 38 | export const ButtonDelete = ({ 39 | children, 40 | onClick, 41 | disabled = false, 42 | }: { 43 | children: ReactNode; 44 | onClick?: MouseEventHandler<HTMLButtonElement>; 45 | disabled?: boolean; 46 | }) => { 47 | return ( 48 | <button 49 | disabled={disabled} 50 | onClick={onClick} 51 | className="inline-flex items-center leading-4 px-4 py-2 rounded border disabled:opacity-60 disabled:border-gray-400 disabled:text-gray-400 border-red-600 shadow text-sm text-red-600 bg-white hover:bg-gray-50 font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-200" 52 | > 53 | <TrashIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" /> 54 | {children} 55 | </button> 56 | ); 57 | }; 58 | 59 | export const ButtonOutline = ({ 60 | children, 61 | onClick, 62 | disabled = false, 63 | }: { 64 | children: ReactNode; 65 | onClick?: MouseEventHandler<HTMLButtonElement>; 66 | disabled?: boolean; 67 | }) => { 68 | return ( 69 | <button 70 | disabled={disabled} 71 | onClick={onClick} 72 | className="inline-flex items-center leading-4 px-4 py-2 rounded shadow border disabled:opacity-60 disabled:border-gray-400 disabled:text-gray-400 border-indigo-600 text-sm text-indigo-600 bg-white hover:bg-gray-50 font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-200" 73 | > 74 | {children} 75 | </button> 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<actionPackageType>; 14 | }) => { 15 | const [input, setInput] = useState<listPackageType>([]); 16 | const [isOpen, setIsOpen] = useState(false); 17 | 18 | const input_ref = useRef(null); 19 | 20 | const handleChange = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => { 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 | <div ref={input_ref} className="relative w-2/3"> 63 | <div className="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center"> 64 | <SearchIcon className="h-5 w-5 text-gray-700" aria-hidden="true" /> 65 | </div> 66 | <Input 67 | id="search_package" 68 | name="search_package" 69 | className="w-full" 70 | placeholder="Search" 71 | type="search_package" 72 | onChange={handleChange} 73 | /> 74 | {isOpen && <ListPackagesFound dispatchPackages={dispatchPackages} results={input} />} 75 | </div> 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 | <span className="relative z-0 inline-flex shadow-sm rounded-md"> 34 | {location.pathname !== '/creator' && location.pathname !== '/creatorVite' && ( 35 | <button 36 | type="button" 37 | onClick={goBack} 38 | className={`${ 39 | location.pathname === '/creator/installation' ? 'rounded-sm' : 'rounded-l-md' 40 | } relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200`} 41 | > 42 | <span className="sr-only">Previous</span> 43 | <ChevronLeftIcon className="h-5 w-5" aria-hidden="true" /> 44 | Back 45 | </button> 46 | )} 47 | {location.pathname !== '/creator/installation' && 48 | location.pathname !== '/creatorVite/installation' && ( 49 | <button 50 | type="button" 51 | onClick={goNext} 52 | className={`${ 53 | location.pathname === '/creator' ? 'rounded-sm' : 'rounded-r-md' 54 | } relative inline-flex items-center px-2 py-2 border border-transparent bg-indigo-600 text-sm font-medium text-white hover:bg-indigo-700 focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 transition duration-200`} 55 | > 56 | <span className="sr-only">Next</span> 57 | Next 58 | <ChevronRightIcon className="h-5 w-5" aria-hidden="true" /> 59 | </button> 60 | )} 61 | </span> 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /src/assets/logo_starter/remix.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <svg width="746px" height="186px" viewBox="0 0 746 186" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 3 | <title>Untitled 7 4 | 5 | 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 | <div> 39 | <label htmlFor="appname" className="block text-sm font-medium text-gray-700"> 40 | Name 41 | </label> 42 | <div className="mt-1"> 43 | <Input 44 | onChange={handleChange} 45 | value={input.appname} 46 | className=" w-full" 47 | type="text" 48 | name="appname" 49 | id="appname" 50 | placeholder="App. name" 51 | ref={appname_ref} 52 | /> 53 | </div> 54 | </div> 55 | <div> 56 | <label htmlFor="version" className="block text-sm font-medium text-gray-700"> 57 | Version 58 | </label> 59 | <div className="mt-1"> 60 | <Input 61 | onChange={handleChange} 62 | value={input.version} 63 | className="w-full" 64 | type="text" 65 | name="version" 66 | id="version" 67 | placeholder="Version" 68 | ref={version_ref} 69 | /> 70 | </div> 71 | </div> 72 | <div> 73 | <label htmlFor="description" className="block text-sm font-medium text-gray-700"> 74 | Description 75 | </label> 76 | <div className="mt-1"> 77 | <TextArea 78 | onChange={handleChange} 79 | value={input.description} 80 | className="resize-none w-full" 81 | name="description" 82 | id="description" 83 | placeholder="Description" 84 | ref={description_ref} 85 | /> 86 | </div> 87 | </div> 88 | </div> 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<dependencyFoundType[]>([]); 13 | const [isOpen, setIsOpen] = useState(false); 14 | 15 | const input_ref = useRef(null); 16 | 17 | const handleChange = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => { 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 | <Card> 62 | <div ref={input_ref} className="flex flex-col items-center relative"> 63 | <h2 className="font-extrabold text-lg text-gray-700 pb-4 text-center">Add packages</h2> 64 | <div className="relative w-1/2 mb-3"> 65 | <div className="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center"> 66 | <SearchIcon className="h-5 w-5 text-gray-700" aria-hidden="true" /> 67 | </div> 68 | <Input 69 | id="search_package" 70 | name="search_package" 71 | className="w-full" 72 | placeholder="Search" 73 | type="search_package" 74 | onChange={handleChange} 75 | /> 76 | </div> 77 | {isOpen && <ListDependenciesFound results={input} />} 78 | </div> 79 | </Card> 80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/assets/logo_starter/nextjs.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <svg width="207px" height="124px" viewBox="0 0 207 124" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 3 | <!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch --> 4 | <title>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 |
    58 | 59 | <div className="w-11/12"> 60 | <label className="block text-sm font-medium text-gray-700" htmlFor="foldername"> 61 | Name 62 | </label> 63 | <Input 64 | className="w-full" 65 | type="text" 66 | name="foldername" 67 | id="foldername" 68 | placeholder="e.g. component" 69 | onChange={handleChange} 70 | value={foldername} 71 | /> 72 | </div> 73 | <div className="w-11/12"> 74 | <label className="block text-sm font-medium text-gray-700" htmlFor="folderlocation"> 75 | Location 76 | </label> 77 | <select 78 | id="folderlocation" 79 | name="folderlocation" 80 | className="block w-full pl-3 pr-10 py-2 text-sm border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" 81 | onChange={handleSelect} 82 | > 83 | {structure 84 | .filter((ele: FileStructureType) => ele.isFolder) 85 | .map((ele) => ( 86 | <option key={ele.id} data-id={ele.id}> 87 | {ele.name} 88 | </option> 89 | ))} 90 | </select> 91 | </div> 92 | <div className="text-red-600 h-4">{error && error}</div> 93 | <ButtonOutline>Create</ButtonOutline> 94 | </form> 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 | <Listbox value={mode} onChange={setMode}> 20 | {({ open }) => ( 21 | <> 22 | <Listbox.Label className="block font-medium text-gray-700">Mode</Listbox.Label> 23 | <div className="relative mt-1"> 24 | <Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm"> 25 | <span className="block truncate">{mode}</span> 26 | <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> 27 | <ChevronDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> 28 | </span> 29 | </Listbox.Button> 30 | 31 | <Transition 32 | show={open} 33 | as={Fragment} 34 | leave="transition ease-in duration-100" 35 | leaveFrom="opacity-100" 36 | leaveTo="opacity-0" 37 | > 38 | <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> 39 | {modeList.map((ele, i) => ( 40 | <Listbox.Option 41 | key={i} 42 | className={({ active }) => 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 | <span 53 | className={classNames( 54 | selected ? 'font-semibold' : 'font-normal', 55 | 'block truncate' 56 | )} 57 | > 58 | {ele} 59 | </span> 60 | 61 | {selected ? ( 62 | <span 63 | className={classNames( 64 | active ? 'text-white' : 'text-indigo-600', 65 | 'absolute inset-y-0 right-0 flex items-center pr-4' 66 | )} 67 | > 68 | <CheckIcon className="h-5 w-5" aria-hidden="true" /> 69 | </span> 70 | ) : null} 71 | </> 72 | )} 73 | </Listbox.Option> 74 | ))} 75 | </Listbox.Options> 76 | </Transition> 77 | </div> 78 | </> 79 | )} 80 | </Listbox> 81 | ); 82 | }; 83 | --------------------------------------------------------------------------------