diff --git a/.eslintignore b/.eslintignore index be16d13ed64..f729b9fb21d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ build lib esm prism.js +packages/create-react-admin/templates/** diff --git a/.prettierignore b/.prettierignore index dd449725e18..54f7990d656 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ *.md +packages/create-react-admin/templates/** \ No newline at end of file diff --git a/packages/create-react-admin/src/ProjectState.ts b/packages/create-react-admin/src/ProjectState.ts index 8f8d3e57100..9b12a13a8ae 100644 --- a/packages/create-react-admin/src/ProjectState.ts +++ b/packages/create-react-admin/src/ProjectState.ts @@ -12,6 +12,7 @@ export type ProjectConfiguration = { dataProvider: string; authProvider: string; resources: string[]; + messages: string[]; installer: string; }; @@ -21,5 +22,6 @@ export const InitialProjectConfiguration: ProjectConfiguration = { dataProvider: '', authProvider: '', resources: [], + messages: [], installer: '', }; diff --git a/packages/create-react-admin/src/SelectInputChoice.tsx b/packages/create-react-admin/src/SelectInputChoice.tsx index e0def89c17f..282677b0f61 100644 --- a/packages/create-react-admin/src/SelectInputChoice.tsx +++ b/packages/create-react-admin/src/SelectInputChoice.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Text } from 'ink'; -import { Stack } from './Stack.js'; +import { Stack } from './Stack'; export type ChoiceType = { label: string; diff --git a/packages/create-react-admin/src/StepAuthProvider.tsx b/packages/create-react-admin/src/StepAuthProvider.tsx index cf36bcc19f6..9141bbe071d 100644 --- a/packages/create-react-admin/src/StepAuthProvider.tsx +++ b/packages/create-react-admin/src/StepAuthProvider.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Text } from 'ink'; import SelectInput from 'ink-select-input'; -import { ChoiceType, SelectInputChoice } from './SelectInputChoice.js'; -import { Stack } from './Stack.js'; +import { ChoiceType, SelectInputChoice } from './SelectInputChoice'; +import { Stack } from './Stack'; const SupportedAuthProviders: ChoiceType[] = [ { diff --git a/packages/create-react-admin/src/StepDataProvider.tsx b/packages/create-react-admin/src/StepDataProvider.tsx index bea91d0888d..0695abff9f8 100644 --- a/packages/create-react-admin/src/StepDataProvider.tsx +++ b/packages/create-react-admin/src/StepDataProvider.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import { Text } from 'ink'; import SelectInput from 'ink-select-input'; -import { ChoiceType, SelectInputChoice } from './SelectInputChoice.js'; -import { Stack } from './Stack.js'; +import { ChoiceType, SelectInputChoice } from './SelectInputChoice'; +import { Stack } from './Stack'; const SupportedDataProviders: ChoiceType[] = [ { diff --git a/packages/create-react-admin/src/StepGenerate.tsx b/packages/create-react-admin/src/StepGenerate.tsx new file mode 100644 index 00000000000..faeaf9e4db4 --- /dev/null +++ b/packages/create-react-admin/src/StepGenerate.tsx @@ -0,0 +1,20 @@ +import React, { useEffect } from 'react'; +import { Text } from 'ink'; +import { generateProject } from './generateProject'; +import { ProjectConfiguration } from './ProjectState'; + +export const StepGenerate = ({ + config, + onCompleted, +}: { + config: ProjectConfiguration; + onCompleted: (value: any) => void; +}) => { + useEffect(() => { + generateProject(config).then(messages => onCompleted({ messages })); + // Disabled as we want to run this only once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return Generating your application...; +}; diff --git a/packages/create-react-admin/src/StepInstall.tsx b/packages/create-react-admin/src/StepInstall.tsx index ccef8f500ab..706f71de343 100644 --- a/packages/create-react-admin/src/StepInstall.tsx +++ b/packages/create-react-admin/src/StepInstall.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import SelectInput from 'ink-select-input'; -import { ChoiceType, SelectInputChoice } from './SelectInputChoice.js'; import { Text } from 'ink'; -import { Stack } from './Stack.js'; +import SelectInput from 'ink-select-input'; +import { ChoiceType, SelectInputChoice } from './SelectInputChoice'; +import { Stack } from './Stack'; const choices: ChoiceType[] = [ { diff --git a/packages/create-react-admin/src/StepName.tsx b/packages/create-react-admin/src/StepName.tsx index 89900eee71d..0911e736543 100644 --- a/packages/create-react-admin/src/StepName.tsx +++ b/packages/create-react-admin/src/StepName.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useState } from 'react'; import TextInput from 'ink-text-input'; import { Text } from 'ink'; -import { Stack } from './Stack.js'; +import { Stack } from './Stack'; export const StepName = ({ onSubmit, diff --git a/packages/create-react-admin/src/StepResources.tsx b/packages/create-react-admin/src/StepResources.tsx index 2dbb8c879f6..0e09a174c2c 100644 --- a/packages/create-react-admin/src/StepResources.tsx +++ b/packages/create-react-admin/src/StepResources.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useState } from 'react'; import TextInput from 'ink-text-input'; import { Text } from 'ink'; -import { Stack } from './Stack.js'; +import { Stack } from './Stack'; export const StepResources = ({ onSubmit, diff --git a/packages/create-react-admin/src/StepRunInstall.tsx b/packages/create-react-admin/src/StepRunInstall.tsx new file mode 100644 index 00000000000..b51781c783b --- /dev/null +++ b/packages/create-react-admin/src/StepRunInstall.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from 'react'; +import { Text } from 'ink'; +import { ProjectConfiguration } from './ProjectState'; +import { useInstallDeps } from './useInstallDeps'; +import { useRunFormatter } from './useRunFormatter'; + +export const StepRunInstall = ({ + config, + onCompleted, +}: { + config: ProjectConfiguration; + onCompleted: (value: any) => void; +}) => { + const installDeps = useInstallDeps(); + const runFormatter = useRunFormatter(); + + useEffect(() => { + installDeps(config).then(() => { + runFormatter(config).then(() => { + onCompleted({}); + }); + }); + // Disabled as we want to run this only once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return Generating your application...; +}; diff --git a/packages/create-react-admin/src/app.tsx b/packages/create-react-admin/src/app.tsx index 8e8c917d078..ce422c59e29 100644 --- a/packages/create-react-admin/src/app.tsx +++ b/packages/create-react-admin/src/app.tsx @@ -1,16 +1,16 @@ -import React, { useReducer, useRef } from 'react'; +import React, { useReducer } from 'react'; import { Box, Text, Newline } from 'ink'; import { InitialProjectConfiguration, ProjectConfiguration, } from './ProjectState.js'; -import { generateProject } from './generateProject.js'; import { StepDataProvider } from './StepDataProvider.js'; import { StepAuthProvider } from './StepAuthProvider.js'; import { StepResources } from './StepResources.js'; import { StepInstall } from './StepInstall.js'; -import { useInstallDeps } from './useInstallDeps.js'; import { StepName } from './StepName.js'; +import { StepGenerate } from './StepGenerate'; +import { StepRunInstall } from './StepRunInstall'; type Props = { name: string | undefined; @@ -61,6 +61,7 @@ const stepReducer = ( case 'generate': return { ...state, + messages: action.value.messages, step: state.installer ? 'run-install' : 'finish', }; case 'run-install': @@ -80,8 +81,7 @@ export default function App({ name = 'my-admin' }: Props) { name: sanitizedName, step: sanitizedName === name ? 'data-provider' : 'name', }); - const helpMessages = useRef([]); - const installDeps = useInstallDeps(); + const handleSubmit = (value: any) => { dispatch({ value }); }; @@ -102,17 +102,10 @@ export default function App({ name = 'my-admin' }: Props) { return ; } if (state.step === 'generate') { - generateProject(state).then(messages => { - helpMessages.current = messages; - dispatch({}); - }); - return Generating your application...; + return ; } if (state.step === 'run-install') { - installDeps(state).then(() => { - dispatch({}); - }); - return Installing dependencies...; + return ; } return ( <> @@ -144,7 +137,7 @@ export default function App({ name = 'my-admin' }: Props) { )} - {helpMessages.current.map((line, index) => ( + {state.messages.map((line, index) => ( {line} ))} diff --git a/packages/create-react-admin/src/generateProject.ts b/packages/create-react-admin/src/generateProject.ts index 844b797ad50..756e18eb434 100644 --- a/packages/create-react-admin/src/generateProject.ts +++ b/packages/create-react-admin/src/generateProject.ts @@ -178,19 +178,28 @@ const BasePackageJson = { build: 'vite build', serve: 'vite preview', 'type-check': 'tsc --noEmit', + lint: 'eslint --fix --ext .js,.jsx,.ts,.tsx ./src', + format: 'prettier --write ./src', }, dependencies: { react: '^18.2.0', - 'react-admin': '^4.9.0', + 'react-admin': '^4.11.3', 'react-dom': '^18.2.0', }, devDependencies: { + '@typescript-eslint/parser': '^5.60.1', + '@typescript-eslint/eslint-plugin': '^5.60.1', '@types/node': '^18.16.1', '@types/react': '^18.0.22', '@types/react-dom': '^18.0.7', - '@vitejs/plugin-react': '^2.2.0', - typescript: '^4.6.4', - vite: '^3.2.0', + '@vitejs/plugin-react': '^4.0.1', + eslint: '^8.43.0', + 'eslint-config-prettier': '^8.8.0', + 'eslint-plugin-react': '^7.32.2', + 'eslint-plugin-react-hooks': '^4.6.0', + prettier: '^2.8.8', + typescript: '^5.1.6', + vite: '^4.3.9', }, }; diff --git a/packages/create-react-admin/src/useRunFormatter.ts b/packages/create-react-admin/src/useRunFormatter.ts new file mode 100644 index 00000000000..bbf0e1866bc --- /dev/null +++ b/packages/create-react-admin/src/useRunFormatter.ts @@ -0,0 +1,16 @@ +import execa from 'execa'; +import { useStderr } from 'ink'; +import { ProjectConfiguration } from './ProjectState'; + +export const useRunFormatter = () => { + const { stderr } = useStderr(); + + return async (state: ProjectConfiguration) => { + const command = execa(`${state.installer}`, ['run', 'format'], { + cwd: `./${state.name}`, + }); + command.stderr.pipe(stderr); + + await command; + }; +}; diff --git a/packages/create-react-admin/templates/common/.eslintrc.js b/packages/create-react-admin/templates/common/.eslintrc.js new file mode 100644 index 00000000000..ed680282c37 --- /dev/null +++ b/packages/create-react-admin/templates/common/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "env": { + "browser": true, + "es2021": true + }, + "settings": { + "react": { + "version": "detect" + } + } +} diff --git a/packages/create-react-admin/templates/common/.gitignore b/packages/create-react-admin/templates/common/.gitignore new file mode 100644 index 00000000000..54f07af58b4 --- /dev/null +++ b/packages/create-react-admin/templates/common/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? \ No newline at end of file diff --git a/packages/create-react-admin/templates/common/prettier.config.js b/packages/create-react-admin/templates/common/prettier.config.js new file mode 100644 index 00000000000..7c6d6c73d3d --- /dev/null +++ b/packages/create-react-admin/templates/common/prettier.config.js @@ -0,0 +1 @@ +module.exports = {} \ No newline at end of file diff --git a/packages/create-react-admin/templates/local-auth-provider/authProvider.ts b/packages/create-react-admin/templates/local-auth-provider/authProvider.ts index cfc6bda8c7b..eb5bc09f40e 100644 --- a/packages/create-react-admin/templates/local-auth-provider/authProvider.ts +++ b/packages/create-react-admin/templates/local-auth-provider/authProvider.ts @@ -11,6 +11,7 @@ export const authProvider: AuthProvider = { ); if (user) { + // eslint-disable-next-line no-unused-vars let { password, ...userToPersist } = user; localStorage.setItem('user', JSON.stringify(userToPersist)); return Promise.resolve(); diff --git a/packages/create-react-admin/templates/ra-data-fakerest/package.json b/packages/create-react-admin/templates/ra-data-fakerest/package.json index 46570f19f96..8349ded83c5 100644 --- a/packages/create-react-admin/templates/ra-data-fakerest/package.json +++ b/packages/create-react-admin/templates/ra-data-fakerest/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "ra-data-fakerest": "^4.9.2" + "ra-data-fakerest": "^4.11.3" } -} \ No newline at end of file +} diff --git a/packages/create-react-admin/templates/ra-data-json-server/package.json b/packages/create-react-admin/templates/ra-data-json-server/package.json index 97afe458a83..ec8e8a48ccb 100644 --- a/packages/create-react-admin/templates/ra-data-json-server/package.json +++ b/packages/create-react-admin/templates/ra-data-json-server/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "ra-data-json-server": "^4.9.2" + "ra-data-json-server": "^4.11.3" } -} \ No newline at end of file +} diff --git a/packages/create-react-admin/templates/ra-data-simple-rest/package.json b/packages/create-react-admin/templates/ra-data-simple-rest/package.json index 8cd003bde8e..37ac8e508ef 100644 --- a/packages/create-react-admin/templates/ra-data-simple-rest/package.json +++ b/packages/create-react-admin/templates/ra-data-simple-rest/package.json @@ -1,5 +1,5 @@ { "dependencies": { - "ra-data-simple-rest": "^4.9.2" + "ra-data-simple-rest": "^4.11.3" } -} \ No newline at end of file +}