From 29a8f2bea93a8e5f19169ecd71df570c0257e23f Mon Sep 17 00:00:00 2001 From: neverland Date: Sat, 13 Jul 2024 09:05:25 +0800 Subject: [PATCH] feat(create-rsbuild): allow to create via CLI options (#2903) --- packages/create-rsbuild/package.json | 2 + packages/create-rsbuild/src/index.ts | 154 +++++++++++++++++++-------- pnpm-lock.yaml | 11 ++ 3 files changed, 125 insertions(+), 42 deletions(-) diff --git a/packages/create-rsbuild/package.json b/packages/create-rsbuild/package.json index d03031301b..87376ab101 100644 --- a/packages/create-rsbuild/package.json +++ b/packages/create-rsbuild/package.json @@ -32,8 +32,10 @@ }, "devDependencies": { "@clack/prompts": "^0.7.0", + "@types/minimist": "^1.2.5", "@types/node": "18.x", "deepmerge": "^4.3.1", + "minimist": "^1.2.8", "rslog": "^1.2.2", "typescript": "^5.5.2" }, diff --git a/packages/create-rsbuild/src/index.ts b/packages/create-rsbuild/src/index.ts index 8f161a8651..6c6925f18a 100644 --- a/packages/create-rsbuild/src/index.ts +++ b/packages/create-rsbuild/src/index.ts @@ -12,6 +12,7 @@ import { text, } from '@clack/prompts'; import deepmerge from 'deepmerge'; +import minimist from 'minimist'; import { logger } from 'rslog'; function cancelAndExit() { @@ -67,49 +68,47 @@ function isEmptyDir(path: string) { return files.length === 0 || (files.length === 1 && files[0] === '.git'); } -export async function main() { - console.log(''); - logger.greet('◆ Create Rsbuild Project'); - - const cwd = process.cwd(); - const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent); - const pkgManager = pkgInfo ? pkgInfo.name : 'npm'; - const packageRoot = path.resolve(__dirname, '..'); - const packageJsonPath = path.join(packageRoot, 'package.json'); - const { version } = require(packageJsonPath); - - const projectName = checkCancel( - await text({ - message: 'Project name or path', - placeholder: 'rsbuild-project', - defaultValue: 'rsbuild-project', - validate(value) { - if (value.length === 0) { - return 'Project name is required'; - } - }, - }), - ); - - const { targetDir, packageName } = formatProjectName(projectName); - const distFolder = path.isAbsolute(targetDir) - ? targetDir - : path.join(cwd, targetDir); +type Argv = { + help?: boolean; + dir?: string; + template?: string; + override?: boolean; + tools?: string | string[]; +}; - if (fs.existsSync(distFolder) && !isEmptyDir(distFolder)) { - const option = checkCancel( - await select({ - message: `"${targetDir}" is not empty, please choose:`, - options: [ - { value: 'yes', label: 'Continue and override files' }, - { value: 'no', label: 'Cancel operation' }, - ], - }), - ); +function logHelpMessage() { + logger.log(` + Usage: create-rsbuild [options] + + Options: + + -h, --help display help for command + -d, --dir create project in specified directory + -t, --template specify the template to use + --tools select additional tools (biome, eslint, prettier) + --override override files in target directory + + Templates: + + react react-ts + vue3 vue3-ts + vue2 vue2-ts + lit lit-ts + preact preact-ts + svelte svelte-ts + solid solid-ts + vanilla vanilla-ts +`); +} - if (option === 'no') { - cancelAndExit(); - } +async function getTemplate({ template }: Argv) { + if (template) { + const pair = template.split('-'); + const language = pair[1] ?? 'js'; + return { + framework: pair[0], + language, + }; } const framework = checkCancel( @@ -138,7 +137,18 @@ export async function main() { }), ); - const tools = checkCancel( + return { + framework, + language, + }; +} + +async function getTools({ tools }: Argv) { + if (tools) { + return Array.isArray(tools) ? tools : [tools]; + } + + return checkCancel( await multiselect({ message: 'Select additional tools (press enter to continue)', options: [ @@ -149,6 +159,66 @@ export async function main() { required: false, }), ); +} + +export async function main() { + const argv = minimist(process.argv.slice(2), { + alias: { h: 'help', d: 'dir', t: 'template' }, + }); + + console.log(''); + logger.greet('◆ Create Rsbuild Project'); + + if (argv.help) { + logHelpMessage(); + return; + } + + const cwd = process.cwd(); + const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent); + const pkgManager = pkgInfo ? pkgInfo.name : 'npm'; + const packageRoot = path.resolve(__dirname, '..'); + const packageJsonPath = path.join(packageRoot, 'package.json'); + const { version } = require(packageJsonPath); + + const projectName = + argv.dir ?? + checkCancel( + await text({ + message: 'Project name or path', + placeholder: 'rsbuild-project', + defaultValue: 'rsbuild-project', + validate(value) { + if (value.length === 0) { + return 'Project name is required'; + } + }, + }), + ); + + const { targetDir, packageName } = formatProjectName(projectName); + const distFolder = path.isAbsolute(targetDir) + ? targetDir + : path.join(cwd, targetDir); + + if (!argv.override && fs.existsSync(distFolder) && !isEmptyDir(distFolder)) { + const option = checkCancel( + await select({ + message: `"${targetDir}" is not empty, please choose:`, + options: [ + { value: 'yes', label: 'Continue and override files' }, + { value: 'no', label: 'Cancel operation' }, + ], + }), + ); + + if (option === 'no') { + cancelAndExit(); + } + } + + const { framework, language } = await getTemplate(argv); + const tools = await getTools(argv); const srcFolder = path.join(packageRoot, `template-${framework}-${language}`); const commonFolder = path.join(packageRoot, 'template-common'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5201705d8..b66d51171c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -821,12 +821,18 @@ importers: '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 + '@types/minimist': + specifier: ^1.2.5 + version: 1.2.5 '@types/node': specifier: 18.x version: 18.19.31 deepmerge: specifier: ^4.3.1 version: 4.3.1 + minimist: + specifier: ^1.2.8 + version: 1.2.8 rslog: specifier: ^1.2.2 version: 1.2.2 @@ -3430,6 +3436,9 @@ packages: '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -10089,6 +10098,8 @@ snapshots: '@types/mdx@2.0.13': {} + '@types/minimist@1.2.5': {} + '@types/ms@0.7.34': {} '@types/node-sass@4.11.7':