mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-07 06:07:42 +02:00
feat(create-docusaurus): ask user for preferred language when no language CLI option provided (#9442)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
This commit is contained in:
parent
6fd8408a2c
commit
628752d92a
8 changed files with 301 additions and 160 deletions
|
@ -25,7 +25,8 @@ async function generateTemplateExample(template) {
|
||||||
// Run the docusaurus script to create the template in the examples folder
|
// Run the docusaurus script to create the template in the examples folder
|
||||||
const command = template.endsWith('-typescript')
|
const command = template.endsWith('-typescript')
|
||||||
? template.replace('-typescript', ' -- --typescript')
|
? template.replace('-typescript', ' -- --typescript')
|
||||||
: template;
|
: `${template} -- --javascript`;
|
||||||
|
|
||||||
shell.exec(
|
shell.exec(
|
||||||
// We use the published init script on purpose, because the local init is
|
// We use the published init script on purpose, because the local init is
|
||||||
// too new and could generate upcoming/unavailable config options.
|
// too new and could generate upcoming/unavailable config options.
|
||||||
|
|
|
@ -52,7 +52,7 @@ git diff --name-only -- '*.json' | sed 's, ,\\&,g' | xargs git checkout --
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Build skeleton website with new version
|
# Build skeleton website with new version
|
||||||
npm_config_registry="$CUSTOM_REGISTRY_URL" npx create-docusaurus@"$NEW_VERSION" test-website classic $EXTRA_OPTS
|
npm_config_registry="$CUSTOM_REGISTRY_URL" npx create-docusaurus@"$NEW_VERSION" test-website classic --javascript $EXTRA_OPTS
|
||||||
|
|
||||||
# Stop Docker container
|
# Stop Docker container
|
||||||
if [[ -z "${KEEP_CONTAINER:-true}" ]] && ( $(docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1) ); then
|
if [[ -z "${KEEP_CONTAINER:-true}" ]] && ( $(docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1) ); then
|
||||||
|
|
|
@ -25,7 +25,7 @@ For Docusaurus maintainers, templates can be tested with:
|
||||||
```bash
|
```bash
|
||||||
cd `git rev-parse --show-toplevel` # Back to repo root
|
cd `git rev-parse --show-toplevel` # Back to repo root
|
||||||
rm -rf test-website
|
rm -rf test-website
|
||||||
yarn create-docusaurus test-website classic
|
yarn create-docusaurus test-website classic --javascript
|
||||||
cd test-website
|
cd test-website
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
@ -37,7 +37,7 @@ Use the following to test the templates against local packages:
|
||||||
```bash
|
```bash
|
||||||
cd `git rev-parse --show-toplevel` # Back to repo root
|
cd `git rev-parse --show-toplevel` # Back to repo root
|
||||||
rm -rf test-website-in-workspace
|
rm -rf test-website-in-workspace
|
||||||
yarn create-docusaurus test-website-in-workspace classic
|
yarn create-docusaurus test-website-in-workspace classic --javascript
|
||||||
cd test-website-in-workspace
|
cd test-website-in-workspace
|
||||||
yarn build
|
yarn build
|
||||||
yarn start
|
yarn start
|
||||||
|
|
|
@ -38,6 +38,7 @@ program
|
||||||
'Do not run package manager immediately after scaffolding',
|
'Do not run package manager immediately after scaffolding',
|
||||||
)
|
)
|
||||||
.option('-t, --typescript', 'Use the TypeScript template variant')
|
.option('-t, --typescript', 'Use the TypeScript template variant')
|
||||||
|
.option('-j, --javascript', 'Use the JavaScript template variant')
|
||||||
.option(
|
.option(
|
||||||
'-g, --git-strategy <strategy>',
|
'-g, --git-strategy <strategy>',
|
||||||
`Only used if the template is a git repository.
|
`Only used if the template is a git repository.
|
||||||
|
|
|
@ -13,15 +13,29 @@ import logger from '@docusaurus/logger';
|
||||||
import shell from 'shelljs';
|
import shell from 'shelljs';
|
||||||
import prompts, {type Choice} from 'prompts';
|
import prompts, {type Choice} from 'prompts';
|
||||||
import supportsColor from 'supports-color';
|
import supportsColor from 'supports-color';
|
||||||
import {escapeShellArg} from '@docusaurus/utils';
|
import {escapeShellArg, askPreferredLanguage} from '@docusaurus/utils';
|
||||||
|
|
||||||
type CLIOptions = {
|
type LanguagesOptions = {
|
||||||
|
javascript?: boolean;
|
||||||
|
typescript?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CLIOptions = LanguagesOptions & {
|
||||||
packageManager?: PackageManager;
|
packageManager?: PackageManager;
|
||||||
skipInstall?: boolean;
|
skipInstall?: boolean;
|
||||||
typescript?: boolean;
|
|
||||||
gitStrategy?: GitStrategy;
|
gitStrategy?: GitStrategy;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function getLanguage(options: LanguagesOptions) {
|
||||||
|
if (options.typescript) {
|
||||||
|
return 'typescript';
|
||||||
|
}
|
||||||
|
if (options.javascript) {
|
||||||
|
return 'javascript';
|
||||||
|
}
|
||||||
|
return askPreferredLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
// Only used in the rare, rare case of running globally installed create +
|
// Only used in the rare, rare case of running globally installed create +
|
||||||
// using --skip-install. We need a default name to show the tip text
|
// using --skip-install. We need a default name to show the tip text
|
||||||
const defaultPackageManager = 'npm';
|
const defaultPackageManager = 'npm';
|
||||||
|
@ -153,11 +167,14 @@ async function readTemplates(): Promise<Template[]> {
|
||||||
async function copyTemplate(
|
async function copyTemplate(
|
||||||
template: Template,
|
template: Template,
|
||||||
dest: string,
|
dest: string,
|
||||||
typescript: boolean,
|
language: 'javascript' | 'typescript',
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await fs.copy(path.join(templatesDir, 'shared'), dest);
|
await fs.copy(path.join(templatesDir, 'shared'), dest);
|
||||||
|
|
||||||
await fs.copy(typescript ? template.tsVariantPath! : template.path, dest, {
|
const sourcePath =
|
||||||
|
language === 'typescript' ? template.tsVariantPath! : template.path;
|
||||||
|
|
||||||
|
await fs.copy(sourcePath, dest, {
|
||||||
// Symlinks don't exist in published npm packages anymore, so this is only
|
// Symlinks don't exist in published npm packages anymore, so this is only
|
||||||
// to prevent errors during local testing
|
// to prevent errors during local testing
|
||||||
filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
|
filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(),
|
||||||
|
@ -183,6 +200,33 @@ function createTemplateChoices(templates: Template[]): Choice[] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function askTemplateChoice({
|
||||||
|
templates,
|
||||||
|
cliOptions,
|
||||||
|
}: {
|
||||||
|
templates: Template[];
|
||||||
|
cliOptions: CLIOptions;
|
||||||
|
}) {
|
||||||
|
return cliOptions.gitStrategy
|
||||||
|
? 'Git repository'
|
||||||
|
: (
|
||||||
|
(await prompts(
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'template',
|
||||||
|
message: 'Select a template below...',
|
||||||
|
choices: createTemplateChoices(templates),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onCancel() {
|
||||||
|
logger.error('A choice is required.');
|
||||||
|
process.exit(1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)) as {template: Template | 'Git repository' | 'Local template'}
|
||||||
|
).template;
|
||||||
|
}
|
||||||
|
|
||||||
function isValidGitRepoUrl(gitRepoUrl: string): boolean {
|
function isValidGitRepoUrl(gitRepoUrl: string): boolean {
|
||||||
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
|
return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item));
|
||||||
}
|
}
|
||||||
|
@ -260,7 +304,7 @@ type Source =
|
||||||
| {
|
| {
|
||||||
type: 'template';
|
type: 'template';
|
||||||
template: Template;
|
template: Template;
|
||||||
typescript: boolean;
|
language: 'javascript' | 'typescript';
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'git';
|
type: 'git';
|
||||||
|
@ -272,12 +316,52 @@ type Source =
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getSource(
|
async function createTemplateSource({
|
||||||
reqTemplate: string | undefined,
|
template,
|
||||||
templates: Template[],
|
cliOptions,
|
||||||
cliOptions: CLIOptions,
|
}: {
|
||||||
): Promise<Source> {
|
template: Template;
|
||||||
if (reqTemplate) {
|
cliOptions: CLIOptions;
|
||||||
|
}): Promise<Source> {
|
||||||
|
const language = await getLanguage(cliOptions);
|
||||||
|
if (language === 'typescript' && !template.tsVariantPath) {
|
||||||
|
logger.error`Template name=${template.name} doesn't provide a TypeScript variant.`;
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'template',
|
||||||
|
template,
|
||||||
|
language,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemplateSource({
|
||||||
|
templateName,
|
||||||
|
templates,
|
||||||
|
cliOptions,
|
||||||
|
}: {
|
||||||
|
templateName: string;
|
||||||
|
templates: Template[];
|
||||||
|
cliOptions: CLIOptions;
|
||||||
|
}): Promise<Source> {
|
||||||
|
const template = templates.find((t) => t.name === templateName);
|
||||||
|
if (!template) {
|
||||||
|
logger.error('Invalid template.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return createTemplateSource({template, cliOptions});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the template source explicitly requested by the user provided cli option
|
||||||
|
async function getUserProvidedSource({
|
||||||
|
reqTemplate,
|
||||||
|
templates,
|
||||||
|
cliOptions,
|
||||||
|
}: {
|
||||||
|
reqTemplate: string;
|
||||||
|
templates: Template[];
|
||||||
|
cliOptions: CLIOptions;
|
||||||
|
}): Promise<Source> {
|
||||||
if (isValidGitRepoUrl(reqTemplate)) {
|
if (isValidGitRepoUrl(reqTemplate)) {
|
||||||
if (
|
if (
|
||||||
cliOptions.gitStrategy &&
|
cliOptions.gitStrategy &&
|
||||||
|
@ -293,46 +377,25 @@ async function getSource(
|
||||||
url: reqTemplate,
|
url: reqTemplate,
|
||||||
strategy: cliOptions.gitStrategy ?? 'deep',
|
strategy: cliOptions.gitStrategy ?? 'deep',
|
||||||
};
|
};
|
||||||
} else if (await fs.pathExists(path.resolve(reqTemplate))) {
|
}
|
||||||
|
if (await fs.pathExists(path.resolve(reqTemplate))) {
|
||||||
return {
|
return {
|
||||||
type: 'local',
|
type: 'local',
|
||||||
path: path.resolve(reqTemplate),
|
path: path.resolve(reqTemplate),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const template = templates.find((t) => t.name === reqTemplate);
|
return getTemplateSource({
|
||||||
if (!template) {
|
templateName: reqTemplate,
|
||||||
logger.error('Invalid template.');
|
templates,
|
||||||
process.exit(1);
|
cliOptions,
|
||||||
}
|
});
|
||||||
if (cliOptions.typescript && !template.tsVariantPath) {
|
}
|
||||||
logger.error`Template name=${reqTemplate} doesn't provide the TypeScript variant.`;
|
|
||||||
process.exit(1);
|
async function askGitRepositorySource({
|
||||||
}
|
cliOptions,
|
||||||
return {
|
}: {
|
||||||
type: 'template',
|
cliOptions: CLIOptions;
|
||||||
template,
|
}): Promise<Source> {
|
||||||
typescript: cliOptions.typescript ?? false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const template = cliOptions.gitStrategy
|
|
||||||
? 'Git repository'
|
|
||||||
: (
|
|
||||||
(await prompts(
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
name: 'template',
|
|
||||||
message: 'Select a template below...',
|
|
||||||
choices: createTemplateChoices(templates),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onCancel() {
|
|
||||||
logger.error('A choice is required.');
|
|
||||||
process.exit(1);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)) as {template: Template | 'Git repository' | 'Local template'}
|
|
||||||
).template;
|
|
||||||
if (template === 'Git repository') {
|
|
||||||
const {gitRepoUrl} = (await prompts(
|
const {gitRepoUrl} = (await prompts(
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
@ -385,7 +448,9 @@ async function getSource(
|
||||||
url: gitRepoUrl,
|
url: gitRepoUrl,
|
||||||
strategy: strategy ?? 'deep',
|
strategy: strategy ?? 'deep',
|
||||||
};
|
};
|
||||||
} else if (template === 'Local template') {
|
}
|
||||||
|
|
||||||
|
async function askLocalSource(): Promise<Source> {
|
||||||
const {templateDir} = (await prompts(
|
const {templateDir} = (await prompts(
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
@ -416,22 +481,28 @@ async function getSource(
|
||||||
type: 'local',
|
type: 'local',
|
||||||
path: templateDir,
|
path: templateDir,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSource(
|
||||||
|
reqTemplate: string | undefined,
|
||||||
|
templates: Template[],
|
||||||
|
cliOptions: CLIOptions,
|
||||||
|
): Promise<Source> {
|
||||||
|
if (reqTemplate) {
|
||||||
|
return getUserProvidedSource({reqTemplate, templates, cliOptions});
|
||||||
}
|
}
|
||||||
let useTS = cliOptions.typescript;
|
|
||||||
if (!useTS && template.tsVariantPath) {
|
const template = await askTemplateChoice({templates, cliOptions});
|
||||||
({useTS} = (await prompts({
|
if (template === 'Git repository') {
|
||||||
type: 'confirm',
|
return askGitRepositorySource({cliOptions});
|
||||||
name: 'useTS',
|
|
||||||
message:
|
|
||||||
'This template is available in TypeScript. Do you want to use the TS variant?',
|
|
||||||
initial: false,
|
|
||||||
})) as {useTS?: boolean});
|
|
||||||
}
|
}
|
||||||
return {
|
if (template === 'Local template') {
|
||||||
type: 'template',
|
return askLocalSource();
|
||||||
|
}
|
||||||
|
return createTemplateSource({
|
||||||
template,
|
template,
|
||||||
typescript: useTS ?? false,
|
cliOptions,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
|
async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) {
|
||||||
|
@ -452,6 +523,7 @@ export default async function init(
|
||||||
getSiteName(reqName, rootDir),
|
getSiteName(reqName, rootDir),
|
||||||
]);
|
]);
|
||||||
const dest = path.resolve(rootDir, siteName);
|
const dest = path.resolve(rootDir, siteName);
|
||||||
|
|
||||||
const source = await getSource(reqTemplate, templates, cliOptions);
|
const source = await getSource(reqTemplate, templates, cliOptions);
|
||||||
|
|
||||||
logger.info('Creating new Docusaurus project...');
|
logger.info('Creating new Docusaurus project...');
|
||||||
|
@ -470,7 +542,7 @@ export default async function init(
|
||||||
}
|
}
|
||||||
} else if (source.type === 'template') {
|
} else if (source.type === 'template') {
|
||||||
try {
|
try {
|
||||||
await copyTemplate(source.template, dest, source.typescript);
|
await copyTemplate(source.template, dest, source.language);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error`Copying Docusaurus template name=${source.template.name} failed!`;
|
logger.error`Copying Docusaurus template name=${source.template.name} failed!`;
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
"resolve-pathname": "^3.0.0",
|
"resolve-pathname": "^3.0.0",
|
||||||
"shelljs": "^0.8.5",
|
"shelljs": "^0.8.5",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
|
|
65
packages/docusaurus-utils/src/cliUtils.ts
Normal file
65
packages/docusaurus-utils/src/cliUtils.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import prompts, {type Choice} from 'prompts';
|
||||||
|
import logger from '@docusaurus/logger';
|
||||||
|
|
||||||
|
type PreferredLanguage = 'javascript' | 'typescript';
|
||||||
|
|
||||||
|
type AskPreferredLanguageOptions = {
|
||||||
|
fallback: PreferredLanguage | undefined;
|
||||||
|
exit: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DefaultOptions: AskPreferredLanguageOptions = {
|
||||||
|
fallback: undefined,
|
||||||
|
exit: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExitChoice: Choice = {title: logger.yellow('[Exit]'), value: '[Exit]'};
|
||||||
|
|
||||||
|
export async function askPreferredLanguage(
|
||||||
|
options: Partial<AskPreferredLanguageOptions> = {},
|
||||||
|
): Promise<'javascript' | 'typescript'> {
|
||||||
|
const {fallback, exit} = {...DefaultOptions, ...options};
|
||||||
|
|
||||||
|
const choices: Choice[] = [
|
||||||
|
{title: logger.bold('JavaScript'), value: 'javascript'},
|
||||||
|
{title: logger.bold('TypeScript'), value: 'typescript'},
|
||||||
|
];
|
||||||
|
if (exit) {
|
||||||
|
choices.push(ExitChoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {language} = await prompts(
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
name: 'language',
|
||||||
|
message: 'Which language do you want to use?',
|
||||||
|
choices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onCancel() {
|
||||||
|
exit && process.exit(0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (language === ExitChoice.value) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!language) {
|
||||||
|
if (fallback) {
|
||||||
|
logger.info`Falling back to language=${fallback}`;
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return language;
|
||||||
|
}
|
|
@ -117,3 +117,4 @@ export {
|
||||||
} from './dataFileUtils';
|
} from './dataFileUtils';
|
||||||
export {isDraft, isUnlisted} from './contentVisibilityUtils';
|
export {isDraft, isUnlisted} from './contentVisibilityUtils';
|
||||||
export {escapeRegexp} from './regExpUtils';
|
export {escapeRegexp} from './regExpUtils';
|
||||||
|
export {askPreferredLanguage} from './cliUtils';
|
||||||
|
|
Loading…
Add table
Reference in a new issue