@dengzhibo/create-vue-template 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,15 @@
1
- # 更新日志
2
-
3
- ## v0 系列
4
-
5
- ### v0.0.1 (2026-10-22)
6
-
7
- * vue 脚手架创建项目
1
+ # 更新日志
2
+
3
+ ## v0 系列
4
+
5
+ ### v0.0.1 (2026-10-22)
6
+
7
+ * vue 脚手架创建项目
8
+
9
+ ### v0.0.2 (2026-10-22)
10
+
11
+ * 修复文本相同但文件不匹配问题
12
+
13
+ ### v0.0.3 (2026-10-22)
14
+
15
+ * 新增JSX功能,优化代码
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) [2026] [DengZhiBo]
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.
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [DengZhiBo]
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.
package/README.md CHANGED
@@ -1,63 +1,63 @@
1
- # Vue + Vite 模板脚手架
2
-
3
- 生成现有 Vue + Vite + TypeScript 应用,保留 Router、Pinia 和 Vue DevTools。
4
-
5
- ## 用户使用(发布到 npm 后)
6
-
7
- ```sh
8
- pnpm create @dengzhibo/vue-template@latest my-vue-app
9
-
10
- # 等价的完整包名调用
11
- pnpm dlx @dengzhibo/create-vue-template@latest my-vue-app
12
-
13
- # 全选并安装依赖
14
- pnpm create @dengzhibo/vue-template@latest my-vue-app --yes "--features=code-quality,unit-test,e2e" --install
15
-
16
- # 不选任何功能,不安装依赖
17
- pnpm create @dengzhibo/vue-template@latest my-vue-app --yes --features=
18
- ```
19
-
20
- 以上命令会从 npm 获取并运行脚手架,无需下载源码或全局安装;必须先发布对应包。`--install` 控制的是生成项目的依赖安装,不是脚手架自身的下载。每个示例独立执行,请使用空目录。
21
-
22
- 命令说明:[pnpm create](https://pnpm.io/cli/create)、[pnpm dlx](https://pnpm.io/cli/dlx)。
23
-
24
- 项目名取目标目录名,支持 my-vue-app 等带连字符名称;目录参数可使用相对路径、绝对路径或当前目录 .。目标目录必须为空,允许保留 .git。
25
-
26
- 向导顺序:创建位置 → 功能多选 → 是否安装依赖 → 确认、返回修改或取消。
27
-
28
- 功能默认全部不选,Space 勾选,Enter 确认,可任意组合:
29
-
30
- - 代码规范(code-quality):ESLint + Oxlint + Oxfmt。
31
- - 单元测试(unit-test):Vitest、Vue Test Utils 和现有测试示例。
32
- - 端到端测试(e2e):Playwright 和现有测试示例。
33
-
34
- Windows PowerShell 中,包含逗号的 `--features` 参数应加引号,如上例。
35
-
36
- --yes 使用非交互模式;--features= 指定逗号分隔的功能,空值为全部不选;--install 执行 pnpm install,默认不安装。
37
-
38
- 未选择的功能不生成对应配置、依赖、脚本或编辑器推荐。测试的 ESLint 规则仅在代码规范与相应测试同时选择时生成。
39
-
40
- 不生成 Git hooks、CI、Docker 或部署管理。基础模板现有 Vue RC 与 TypeScript bridge 覆盖保持原样;本次仅拆分现有能力。
41
-
42
- CLI Node 要求见 package.json;生成应用的 Node 要求见 templates/base/package.json。
43
-
44
- ## 源码开发与维护
45
-
46
- 仅在本包源码目录中使用以下命令进行本地调试:
47
-
48
- ```sh
49
- node bin/index.js my-vue-app
50
- ```
51
-
52
- 验证:
53
-
54
- ```sh
55
- node --test tests/*.test.mjs
56
- node scripts/check-package.mjs
57
- ```
58
-
59
- 测试覆盖 8 种功能组合、CLI 参数、向导返回修改、取消及目标目录保护。不要在 templates 内安装依赖。
60
-
61
- ## 发布文件范围
62
-
63
- `bin/`、`lib/`、`templates/`、README、CHANGELOG、LICENSE 与 package.json 随 npm 包发布。测试、预检脚本、AGENTS.md 和本地缓存仅用于仓库维护,不需要上传 npm。`_gitignore` 在生成项目时恢复为 `.gitignore`。
1
+ # Vue + Vite 模板脚手架
2
+
3
+ 生成现有 Vue + Vite + TypeScript 应用,保留 Router、Pinia 和 Vue DevTools。
4
+
5
+ ## 用户使用(发布到 npm 后)
6
+
7
+ ```sh
8
+ pnpm create @dengzhibo/vue-template@latest my-vue-app
9
+
10
+ # 等价的完整包名调用
11
+ pnpm dlx @dengzhibo/create-vue-template@latest my-vue-app
12
+
13
+ # 全选并安装依赖
14
+ pnpm create @dengzhibo/vue-template@latest my-vue-app --yes "--features=code-quality,unit-test,e2e" --install
15
+
16
+ # 不选任何功能,不安装依赖
17
+ pnpm create @dengzhibo/vue-template@latest my-vue-app --yes --features=
18
+ ```
19
+
20
+ 以上命令会从 npm 获取并运行脚手架,无需下载源码或全局安装;必须先发布对应包。`--install` 控制的是生成项目的依赖安装,不是脚手架自身的下载。每个示例独立执行,请使用空目录。
21
+
22
+ 命令说明:[pnpm create](https://pnpm.io/cli/create)、[pnpm dlx](https://pnpm.io/cli/dlx)。
23
+
24
+ 项目名取目标目录名,支持 my-vue-app 等带连字符名称;目录参数可使用相对路径、绝对路径或当前目录 .。目标目录必须为空,允许保留 .git。
25
+
26
+ 向导顺序:创建位置 → 功能多选 → 是否安装依赖 → 确认、返回修改或取消。
27
+
28
+ 功能默认全部不选,Space 勾选,Enter 确认,可任意组合:
29
+
30
+ - 代码规范(code-quality):ESLint + Oxlint + Oxfmt。
31
+ - 单元测试(unit-test):Vitest、Vue Test Utils 和现有测试示例。
32
+ - 端到端测试(e2e):Playwright 和现有测试示例。
33
+
34
+ Windows PowerShell 中,包含逗号的 `--features` 参数应加引号,如上例。
35
+
36
+ --yes 使用非交互模式;--features= 指定逗号分隔的功能,空值为全部不选;--install 执行 pnpm install,默认不安装。
37
+
38
+ 未选择的功能不生成对应配置、依赖、脚本或编辑器推荐。测试的 ESLint 规则仅在代码规范与相应测试同时选择时生成。
39
+
40
+ 不生成 Git hooks、CI、Docker 或部署管理。基础模板现有 Vue RC 与 TypeScript bridge 覆盖保持原样;本次仅拆分现有能力。
41
+
42
+ CLI Node 要求见 package.json;生成应用的 Node 要求见 templates/base/package.json。
43
+
44
+ ## 源码开发与维护
45
+
46
+ 仅在本包源码目录中使用以下命令进行本地调试:
47
+
48
+ ```sh
49
+ node bin/index.js my-vue-app
50
+ ```
51
+
52
+ 验证:
53
+
54
+ ```sh
55
+ node --test tests/*.test.mjs
56
+ node scripts/check-package.mjs
57
+ ```
58
+
59
+ 测试覆盖 8 种功能组合、CLI 参数、向导返回修改、取消及目标目录保护。不要在 templates 内安装依赖。
60
+
61
+ ## 发布文件范围
62
+
63
+ `bin/`、`lib/`、`templates/`、README、CHANGELOG、LICENSE 与 package.json 随 npm 包发布。测试、预检脚本、AGENTS.md 和本地缓存仅用于仓库维护,不需要上传 npm。`_gitignore` 在生成项目时恢复为 `.gitignore`。
package/bin/index.js CHANGED
@@ -1,39 +1,39 @@
1
- #!/usr/bin/env node
2
- /* eslint-disable no-console */
3
- import { execSync } from 'node:child_process'
4
- import { createPlan, writePlan } from '../lib/generate.js'
5
- import { help, parseArgs } from '../lib/options.js'
6
- import { Cancelled, collectOptions } from '../lib/prompts.js'
7
-
8
- async function main() {
9
- const args = process.argv.slice(2)
10
- const parsed = parseArgs(args)
11
- if (parsed.help) {
12
- console.log(help)
13
- return
14
- }
15
- if (!parsed.yes && (!process.stdin.isTTY || !process.stdout.isTTY))
16
- throw new Error('非交互环境请使用 --yes')
17
- const options = parsed.yes ? parsed.options : await collectOptions(parsed.options)
18
- if (!options) {
19
- console.log('已取消,未创建项目。')
20
- return
21
- }
22
- const target = writePlan(createPlan(options))
23
- console.log(`✔ 项目已创建:${target}`)
24
- if (options.install) {
25
- try {
26
- execSync('pnpm install', { cwd: target, stdio: 'inherit' })
27
- }
28
- catch {
29
- console.error('安装失败,文件保留,请手动运行 pnpm install')
30
- process.exitCode = 1
31
- return
32
- }
33
- }
34
- console.log(`cd "${target}"\n${options.install ? '' : 'pnpm install\n'}pnpm dev`)
35
- }
36
- main().catch((error) => {
37
- console.error(error instanceof Cancelled ? '已取消,未创建项目。' : error.message)
38
- process.exitCode = error instanceof Cancelled ? 130 : 1
39
- })
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ import { execSync } from 'node:child_process'
4
+ import { createPlan, writePlan } from '../lib/generate.js'
5
+ import { help, parseArgs } from '../lib/options.js'
6
+ import { Cancelled, collectOptions } from '../lib/prompts.js'
7
+
8
+ async function main() {
9
+ const args = process.argv.slice(2)
10
+ const parsed = parseArgs(args)
11
+ if (parsed.help) {
12
+ console.log(help)
13
+ return
14
+ }
15
+ if (!parsed.yes && (!process.stdin.isTTY || !process.stdout.isTTY))
16
+ throw new Error('非交互环境请使用 --yes')
17
+ const options = parsed.yes ? parsed.options : await collectOptions(parsed.options)
18
+ if (!options) {
19
+ console.log('已取消,未创建项目。')
20
+ return
21
+ }
22
+ const target = writePlan(createPlan(options))
23
+ console.log(`✔ 项目已创建:${target}`)
24
+ if (options.install) {
25
+ try {
26
+ execSync('pnpm install', { cwd: target, stdio: 'inherit' })
27
+ }
28
+ catch {
29
+ console.error('安装失败,文件保留,请手动运行 pnpm install')
30
+ process.exitCode = 1
31
+ return
32
+ }
33
+ }
34
+ console.log(`cd "${target}"\n${options.install ? '' : 'pnpm install\n'}pnpm dev`)
35
+ }
36
+ main().catch((error) => {
37
+ console.error(error instanceof Cancelled ? '已取消,未创建项目。' : error.message)
38
+ process.exitCode = error instanceof Cancelled ? 130 : 1
39
+ })
package/lib/generate.js CHANGED
@@ -1,100 +1,110 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { assertTarget, featureLabels, normalizeOptions } from './options.js'
5
-
6
- const templateDir = fileURLToPath(new URL('../templates/', import.meta.url))
7
- const skip = new Set(['node_modules', 'dist', '.npmignore', '.git', '.DS_Store', 'pnpm-lock.yaml', '.turbo'])
8
- const rename = { _gitignore: '.gitignore' }
9
- const encode = value => `${JSON.stringify(value, null, 2)}\n`
10
- export function createPlan(input = {}, cwd = process.cwd()) {
11
- const options = normalizeOptions(input)
12
- const target = path.resolve(cwd, options.location)
13
- const name = path.basename(target)
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { assertTarget, featureLabels, normalizeOptions } from './options.js'
5
+
6
+ const templateDir = fileURLToPath(new URL('../templates/', import.meta.url))
7
+ const skip = new Set(['node_modules', 'dist', '.npmignore', '.git', '.DS_Store', 'pnpm-lock.yaml', '.turbo'])
8
+ const rename = { _gitignore: '.gitignore' }
9
+ const encode = value => `${JSON.stringify(value, null, 2)}\n`
10
+ export function createPlan(input = {}, cwd = process.cwd()) {
11
+ const options = normalizeOptions(input)
12
+ const target = path.resolve(cwd, options.location)
13
+ const name = path.basename(target)
14
14
  if (!/^[a-z0-9][\w.-]*$/i.test(name))
15
- throw new Error('项目名不合法')
16
- assertTarget(target)
17
- const files = new Map()
18
- const has = feature => options.features.includes(feature)
19
- function add(directory, relative = '') {
20
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
15
+ throw new Error('项目名不合法')
16
+ assertTarget(target)
17
+ const files = new Map()
18
+ const has = feature => options.features.includes(feature)
19
+ function add(directory, relative = '') {
20
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
21
21
  if (skip.has(entry.name) || entry.name.endsWith('.fragment.json'))
22
- continue
23
- const source = path.join(directory, entry.name)
22
+ continue
23
+ const source = path.join(directory, entry.name)
24
24
  if (entry.isSymbolicLink())
25
- throw new Error(`模板不支持符号链接:${source}`)
26
- const destination = path.posix.join(relative, rename[entry.name] || entry.name)
25
+ throw new Error(`模板不支持符号链接:${source}`)
26
+ const destination = path.posix.join(relative, rename[entry.name] || entry.name)
27
27
  if (entry.isDirectory()) {
28
28
  add(source, destination)
29
- }
30
- else {
29
+ }
30
+ else {
31
31
  if (files.has(destination))
32
- throw new Error(`模板文件冲突:${destination}`)
33
- files.set(destination, fs.readFileSync(source))
34
- }
35
- }
36
- }
37
- add(path.join(templateDir, 'base'))
38
- const parse = file => JSON.parse(files.get(file).toString())
39
- const pkg = parse('package.json')
40
- for (const feature of options.features) {
41
- const directory = path.join(templateDir, 'features', feature)
42
- add(directory)
43
- const fragment = JSON.parse(fs.readFileSync(path.join(directory, 'package.fragment.json'), 'utf8'))
44
- for (const field of ['scripts', 'dependencies', 'devDependencies']) {
45
- for (const [key, value] of Object.entries(fragment[field] || {})) {
46
- pkg[field] ||= {}
32
+ throw new Error(`模板文件冲突:${destination}`)
33
+ files.set(destination, fs.readFileSync(source))
34
+ }
35
+ }
36
+ }
37
+ add(path.join(templateDir, 'base'))
38
+ const parse = file => JSON.parse(files.get(file).toString())
39
+ const pkg = parse('package.json')
40
+ for (const feature of options.features) {
41
+ const directory = path.join(templateDir, 'features', ({ 'unit-test': 'Vitest', 'jsx': 'JSX' })[feature] || feature)
42
+ add(directory)
43
+ const fragment = JSON.parse(fs.readFileSync(path.join(directory, 'package.fragment.json'), 'utf8'))
44
+ for (const field of ['scripts', 'dependencies', 'devDependencies']) {
45
+ for (const [key, value] of Object.entries(fragment[field] || {})) {
46
+ pkg[field] ||= {}
47
47
  if (Object.hasOwn(pkg[field], key) && pkg[field][key] !== value)
48
- throw new Error(`配置冲突:${key}`)
49
- pkg[field][key] = value
50
- }
51
- }
52
- }
53
- const extensions = parse('.vscode/extensions.json')
54
- if (has('unit-test')) {
55
- const tsconfig = parse('tsconfig.json')
56
- tsconfig.references.push({ path: './tsconfig.vitest.json' })
57
- files.set('tsconfig.json', encode(tsconfig))
58
- extensions.recommendations.push('vitest.explorer')
59
- }
48
+ throw new Error(`配置冲突:${key}`)
49
+ pkg[field][key] = value
50
+ }
51
+ }
52
+ }
53
+ if (has('jsx')) {
54
+ const vite = files.get('vite.config.ts').toString().replace(/\r\n/g, '\n').replace('import vue from \'@vitejs/plugin-vue\'', 'import vue from \'@vitejs/plugin-vue\'\nimport vueJsx from \'@vitejs/plugin-vue-jsx\'').replace(' vue(),', ' vue(),\n vueJsx(),')
55
+ files.set('vite.config.ts', vite)
56
+ const tsconfig = files.get('tsconfig.app.json').toString().replace('"compilerOptions": {', '"compilerOptions": {\n "jsx": "preserve",\n "jsxImportSource": "vue",')
57
+ files.set('tsconfig.app.json', tsconfig)
58
+ }
59
+ const extensions = parse('.vscode/extensions.json')
60
+ if (has('unit-test')) {
61
+ const tsconfig = parse('tsconfig.json')
62
+ tsconfig.references.push({ path: './tsconfig.vitest.json' })
63
+ files.set('tsconfig.json', encode(tsconfig))
64
+ extensions.recommendations.push('vitest.explorer')
65
+ }
60
66
  if (has('e2e'))
61
- extensions.recommendations.push('ms-playwright.playwright')
62
- if (has('code-quality')) {
63
- let eslint = files.get('eslint.config.ts').toString().replace(/\r\n/g, '\n')
64
- for (const [feature, plugin, dependency] of [['unit-test', 'pluginVitest', '@vitest/eslint-plugin'], ['e2e', 'pluginPlaywright', 'eslint-plugin-playwright']]) {
65
- if (!has(feature)) {
66
- delete pkg.devDependencies[dependency]
67
- eslint = eslint.replace(new RegExp(`^import ${plugin}.*\\n`, 'm'), '')
68
- eslint = eslint.replace(new RegExp(` \{\\n \\.\\.\\.${plugin}[\\s\\S]*?\\n \},\\n`), '')
69
- }
70
- }
71
- files.set('eslint.config.ts', eslint)
72
- const oxlint = parse('.oxlintrc.json')
67
+ extensions.recommendations.push('ms-playwright.playwright')
68
+ if (has('code-quality')) {
69
+ let eslint = files.get('eslint.config.ts').toString().replace(/\r\n/g, '\n')
70
+ for (const [feature, plugin, dependency] of [['unit-test', 'pluginVitest', '@vitest/eslint-plugin'], ['e2e', 'pluginPlaywright', 'eslint-plugin-playwright']]) {
71
+ if (!has(feature)) {
72
+ delete pkg.devDependencies[dependency]
73
+ eslint = eslint.replace(new RegExp(`^import ${plugin}.*\\n`, 'm'), '')
74
+ eslint = eslint.replace(new RegExp(` \{\\n \\.\\.\\.${plugin}[\\s\\S]*?\\n \},\\n`), '')
75
+ }
76
+ }
77
+ files.set('eslint.config.ts', eslint)
78
+ const oxlint = parse('.oxlintrc.json')
73
79
  if (!has('unit-test'))
74
- oxlint.plugins = oxlint.plugins.filter(p => p !== 'vitest')
75
- files.set('.oxlintrc.json', encode(oxlint))
76
- const settings = JSON.parse(fs.readFileSync(path.join(templateDir, 'features/code-quality/settings.fragment.json'), 'utf8'))
77
- files.set('.vscode/settings.json', encode({ ...parse('.vscode/settings.json'), ...settings }))
78
- extensions.recommendations.push('dbaeumer.vscode-eslint', 'EditorConfig.EditorConfig', 'oxc.oxc-vscode')
79
- }
80
- files.set('.vscode/extensions.json', encode(extensions))
81
- pkg.name = name
82
- files.set('package.json', encode(pkg))
83
- let readme = files.get('README.md').toString().replace(/\r\n/g, '\n')
84
- const sections = [['unit-test', 'Run Unit Tests'], ['e2e', 'Run End-to-End Tests'], ['code-quality', 'Lint with']]
85
- for (const [feature, title] of sections) {
80
+ oxlint.plugins = oxlint.plugins.filter(p => p !== 'vitest')
81
+ files.set('.oxlintrc.json', encode(oxlint))
82
+ const settings = JSON.parse(fs.readFileSync(path.join(templateDir, 'features/code-quality/settings.fragment.json'), 'utf8'))
83
+ files.set('.vscode/settings.json', encode({ ...parse('.vscode/settings.json'), ...settings }))
84
+ extensions.recommendations.push('dbaeumer.vscode-eslint', 'EditorConfig.EditorConfig', 'oxc.oxc-vscode')
85
+ }
86
+ files.set('.vscode/extensions.json', encode(extensions))
87
+ pkg.name = name
88
+ const scriptOrder = ['dev', 'build', 'preview', 'test:unit', 'test:e2e', 'build-only', 'type-check', 'lint', 'lint:oxlint', 'lint:eslint', 'format']
89
+ pkg.scripts = Object.fromEntries(scriptOrder.filter(key => Object.hasOwn(pkg.scripts, key)).map(key => [key, pkg.scripts[key]]))
90
+ for (const field of ['dependencies', 'devDependencies'])
91
+ pkg[field] = Object.fromEntries(Object.entries(pkg[field]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
92
+ files.set('package.json', encode(pkg))
93
+ let readme = files.get('README.md').toString().replace(/\r\n/g, '\n')
94
+ const sections = [['unit-test', 'Run Unit Tests'], ['e2e', 'Run End-to-End Tests'], ['code-quality', 'Lint with']]
95
+ for (const [feature, title] of sections) {
86
96
  if (!has(feature))
87
- readme = readme.replace(new RegExp(`### ${title}[\\s\\S]*?(?=\\n### |$)`), '')
88
- }
89
- files.set('README.md', `${readme.replace('# base', `# ${name}`).trimEnd()}\n\n已选功能:${options.features.map(f => featureLabels[f]).join('、') || '无'}\n`)
90
- return { target, options, files }
91
- }
92
- export function writePlan(plan) {
93
- assertTarget(plan.target)
94
- for (const [relative, content] of plan.files) {
95
- const file = path.join(plan.target, relative)
96
- fs.mkdirSync(path.dirname(file), { recursive: true })
97
- fs.writeFileSync(file, content)
98
- }
99
- return plan.target
100
- }
97
+ readme = readme.replace(new RegExp(`### ${title}[\\s\\S]*?(?=\\n### |$)`), '')
98
+ }
99
+ files.set('README.md', `${readme.replace('# base', `# ${name}`).trimEnd()}\n\n已选功能:${options.features.map(f => featureLabels[f]).join('、') || '无'}\n`)
100
+ return { target, options, files }
101
+ }
102
+ export function writePlan(plan) {
103
+ assertTarget(plan.target)
104
+ for (const [relative, content] of plan.files) {
105
+ const file = path.join(plan.target, relative)
106
+ fs.mkdirSync(path.dirname(file), { recursive: true })
107
+ fs.writeFileSync(file, content)
108
+ }
109
+ return plan.target
110
+ }
package/lib/options.js CHANGED
@@ -1,53 +1,53 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
-
4
- export const featureLabels = { 'code-quality': '代码规范', 'unit-test': '单元测试', 'e2e': '端到端测试' }
5
- export function normalizeOptions(input = {}) {
6
- const features = input.features ?? []
7
- if (!Array.isArray(features) || features.some(f => !Object.hasOwn(featureLabels, f)))
8
- throw new Error('功能仅支持 code-quality、unit-test、e2e')
9
- return { location: input.location || '.', features: Object.keys(featureLabels).filter(f => features.includes(f)), install: Boolean(input.install) }
10
- }
11
- export function assertTarget(target) {
12
- let current = target
13
- while (true) {
14
- if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
15
- throw new Error('目标路径不能经过符号链接')
16
- const parent = path.dirname(current)
17
- if (parent === current)
18
- break
19
- current = parent
20
- }
21
- if (fs.existsSync(target) && (!fs.statSync(target).isDirectory() || fs.readdirSync(target).some(name => name !== '.git')))
22
- throw new Error('目标必须为空目录(允许保留 .git)')
23
- }
24
-
25
- export const help = `用法:create-vue-template [目录] [选项]
26
- --yes, -y 非交互创建
27
- --features=code-quality,unit-test,e2e 选择功能,默认全部不选
28
- --features= 全部不选
29
- --install 安装依赖,默认不安装
30
- --help, -h 帮助
31
- 项目名取目标目录名,例如 my-vue-app。`
32
- export function parseArgs(args) {
33
- if (args.includes('--help') || args.includes('-h'))
34
- return { help: true }
35
- const options = normalizeOptions()
36
- const locations = []
37
- let yes = false
38
- for (const arg of args) {
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ export const featureLabels = { 'code-quality': '代码规范', 'unit-test': '单元测试', 'e2e': '端到端测试', 'jsx': 'JSX 支持' }
5
+ export function normalizeOptions(input = {}) {
6
+ const features = input.features ?? []
7
+ if (!Array.isArray(features) || features.some(f => !Object.hasOwn(featureLabels, f)))
8
+ throw new Error('功能仅支持 code-quality、unit-test、e2e、jsx')
9
+ return { location: input.location || '.', features: Object.keys(featureLabels).filter(f => features.includes(f)), install: Boolean(input.install) }
10
+ }
11
+ export function assertTarget(target) {
12
+ let current = target
13
+ while (true) {
14
+ if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
15
+ throw new Error('目标路径不能经过符号链接')
16
+ const parent = path.dirname(current)
17
+ if (parent === current)
18
+ break
19
+ current = parent
20
+ }
21
+ if (fs.existsSync(target) && (!fs.statSync(target).isDirectory() || fs.readdirSync(target).some(name => name !== '.git')))
22
+ throw new Error('目标必须为空目录(允许保留 .git)')
23
+ }
24
+
25
+ export const help = `用法:create-vue-template [目录] [选项]
26
+ --yes, -y 非交互创建
27
+ --features=code-quality,unit-test,e2e,jsx 选择功能,默认全部不选
28
+ --features= 全部不选
29
+ --install 安装依赖,默认不安装
30
+ --help, -h 帮助
31
+ 项目名取目标目录名,例如 my-vue-app。`
32
+ export function parseArgs(args) {
33
+ if (args.includes('--help') || args.includes('-h'))
34
+ return { help: true }
35
+ const options = normalizeOptions()
36
+ const locations = []
37
+ let yes = false
38
+ for (const arg of args) {
39
39
  if (['--yes', '-y'].includes(arg))
40
- yes = true
40
+ yes = true
41
41
  else if (arg.startsWith('--features='))
42
- options.features = arg.slice('--features='.length) === '' ? [] : arg.slice('--features='.length).split(',')
42
+ options.features = arg.slice('--features='.length) === '' ? [] : arg.slice('--features='.length).split(',')
43
43
  else if (arg === '--install')
44
- options.install = true
44
+ options.install = true
45
45
  else if (arg.startsWith('-'))
46
- throw new Error(`未知参数:${arg}`)
47
- else locations.push(arg)
48
- }
46
+ throw new Error(`未知参数:${arg}`)
47
+ else locations.push(arg)
48
+ }
49
49
  if (locations.length > 1)
50
- throw new Error('只能指定一个创建目录')
51
- options.location = locations[0] || '.'
52
- return { options: normalizeOptions(options), yes }
53
- }
50
+ throw new Error('只能指定一个创建目录')
51
+ options.location = locations[0] || '.'
52
+ return { options: normalizeOptions(options), yes }
53
+ }