Skip to content

Commit

Permalink
Merge pull request #9544 from marmelab/create-react-admin-flags
Browse files Browse the repository at this point in the history
Make create-react-admin usable in non-interactive mode
  • Loading branch information
fzaninotto authored Jan 3, 2024
2 parents 3dce829 + c6cd360 commit 456c394
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 33 deletions.
3 changes: 1 addition & 2 deletions packages/create-react-admin/src/ProjectState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type ProjectConfiguration = {
| 'finish';
dataProvider: string;
authProvider: string;
resources: string[];
resources?: string[];
messages: string[];
installer: string;
};
Expand All @@ -21,7 +21,6 @@ export const InitialProjectConfiguration: ProjectConfiguration = {
step: 'name',
dataProvider: '',
authProvider: '',
resources: [],
messages: [],
installer: '',
};
2 changes: 1 addition & 1 deletion packages/create-react-admin/src/StepAuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SelectInput from 'ink-select-input';
import { ChoiceType, SelectInputChoice } from './SelectInputChoice';
import { Stack } from './Stack';

const SupportedAuthProviders: ChoiceType[] = [
export const SupportedAuthProviders: ChoiceType[] = [
{
label: 'Hard coded local username/password',
value: 'local-auth-provider',
Expand Down
2 changes: 1 addition & 1 deletion packages/create-react-admin/src/StepDataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SelectInput from 'ink-select-input';
import { ChoiceType, SelectInputChoice } from './SelectInputChoice';
import { Stack } from './Stack';

const SupportedDataProviders: ChoiceType[] = [
export const SupportedDataProviders: ChoiceType[] = [
{
label: 'Fakerest',
value: 'ra-data-fakerest',
Expand Down
2 changes: 1 addition & 1 deletion packages/create-react-admin/src/StepInstall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const choices: ChoiceType[] = [
},
{
label: "Don't install dependencies, I'll do it myself.",
value: '',
value: 'skip',
},
];

Expand Down
106 changes: 80 additions & 26 deletions packages/create-react-admin/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,98 @@ import { StepGenerate } from './StepGenerate';
import { StepRunInstall } from './StepRunInstall';

type Props = {
name: string | undefined;
name?: string;
authProvider?: string;
dataProvider?: string;
resources?: string[];
install?: string;
};

const getNextStep = (state: ProjectConfiguration) => {
if (state.name) {
if (state.dataProvider) {
if (state.authProvider) {
if (state.resources) {
if (state.installer) {
return 'generate';
}
return 'install';
}
return 'resources';
}
return 'auth-provider';
}
return 'data-provider';
}
return 'name';
};

const stepReducer = (
state: ProjectConfiguration,
action
): ProjectConfiguration => {
switch (state.step) {
case 'name':
return {
case 'name': {
const newState = {
...state,
name: action.value,
step: 'data-provider',
};
case 'data-provider':
return {
...newState,
step: getNextStep(newState),
};
}
case 'data-provider': {
const newState = {
...state,
step: 'auth-provider',
dataProvider: action.value,
resources:
action.value === 'ra-data-fakerest'
? ['posts', 'comments']
: state.resources,
};
case 'auth-provider':
return {
...newState,
step: getNextStep(newState),
};
}
case 'auth-provider': {
const newState = {
...state,
step:
state.dataProvider === 'ra-data-fakerest'
? 'install'
: 'resources',
authProvider: action.value,
};
case 'resources':
return {
...newState,
step: getNextStep(newState),
};
}
case 'resources': {
const newState = {
...state,
step: 'install',
resources: action.value,
};
case 'install':
return {
...newState,
step: getNextStep(newState),
};
}
case 'install':
const newState = {
...state,
installer: action.value,
step: 'generate',
};
return {
...newState,
step: getNextStep(newState),
};
case 'generate':
return {
...state,
messages: action.value.messages,
step: state.installer ? 'run-install' : 'finish',
step:
state.installer && state.installer !== 'skip'
? 'run-install'
: 'finish',
};
case 'run-install':
return {
Expand All @@ -74,12 +116,22 @@ const stepReducer = (
}
};

export default function App({ name = 'my-admin' }: Props) {
const sanitizedName = sanitizeName(name);
const [state, dispatch] = useReducer(stepReducer, {
export default function App(props: Props) {
const sanitizedName = sanitizeName(props.name);
const initialState = {
...InitialProjectConfiguration,
dataProvider: props.dataProvider,
authProvider: props.authProvider,
resources: props.resources?.includes('skip') ? [] : props.resources,
installer: props.install,
name: sanitizedName,
step: sanitizedName === name ? 'data-provider' : 'name',
};

const initialStep = getNextStep(initialState);

const [state, dispatch] = useReducer(stepReducer, {
...initialState,
step: initialStep,
});

const handleSubmit = (value: any) => {
Expand Down Expand Up @@ -145,11 +197,13 @@ export default function App({ name = 'my-admin' }: Props) {
);
}

const sanitizeName = (name: string) => {
const sanitizeName = (name?: string) => {
return name
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z\d\-~]+/g, '-');
? name
.trim()
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^[._]/, '')
.replace(/[^a-z\d\-~]+/g, '-')
: undefined;
};
51 changes: 49 additions & 2 deletions packages/create-react-admin/src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,66 @@ import React from 'react';
import { render } from 'ink';
import meow from 'meow';
import App from './app.js';
import { SupportedDataProviders } from './StepDataProvider.js';
import { SupportedAuthProviders } from './StepAuthProvider.js';

const cli = meow(
`
Usage
$ create-admin-app <name>
Options
--data-provider Set the data provider to use ("ra-data-fakerest", "ra-data-simple-rest", "ra-data-json-server" or "none")
--auth-provider Set the auth provider to use ("local-auth-provider" or "none")
--resource Add a resource that will be initialized with guessers (can be used multiple times). Set to "skip" to bypass the interactive resource step.
--install Set the package manager to use for installing dependencies ("yarn", "npm" or "skip" to bypass the interactive install step)
Examples
$ create-admin-app my-admin
`
$ create-admin-app my-admin --data-provider ra-data-json-server --auth-provider local-auth-provider --resource posts --resource comments --install npm
`,
{
flags: {
help: {
type: 'boolean',
alias: 'h',
},
dataProvider: {
type: 'string',
choices: SupportedDataProviders.map(choice => choice.value),
},
authProvider: {
type: 'string',
choices: SupportedAuthProviders.map(choice => choice.value),
},
resource: {
type: 'string',
isMultiple: true,
},
install: {
type: 'string',
choices: ['yarn', 'npm', 'skip'],
},
},
}
);

if (cli.flags.h) {
cli.showHelp();
} else {
render(<App name={cli.input.length > 0 ? cli.input[0] : 'my-admin'} />);
render(
<App
name={cli.input.length > 0 ? cli.input[0] : undefined}
dataProvider={cli.flags.dataProvider}
authProvider={cli.flags.authProvider}
resources={
cli.flags.resource.includes('skip')
? []
: cli.flags.resource.length > 0
? cli.flags.resource
: undefined
}
install={cli.flags.install}
/>
);
}

0 comments on commit 456c394

Please sign in to comment.