@farris/cli 2.1.1 → 2.1.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.
Files changed (64) hide show
  1. package/lib/commands/build-components.js +39 -39
  2. package/lib/commands/build-components.js.map +1 -1
  3. package/lib/commands/build-css.js +38 -38
  4. package/lib/commands/build-css.js.map +1 -1
  5. package/lib/commands/build-lib.js +25 -25
  6. package/lib/commands/build.js +14 -14
  7. package/lib/commands/build.js.map +1 -1
  8. package/lib/commands/create-app.js +54 -54
  9. package/lib/commands/dev-serve.js +18 -18
  10. package/lib/commands/extract-i18n.js +74 -74
  11. package/lib/commands/extract-i18n.js.map +1 -1
  12. package/lib/commands/generate/api.js +124 -124
  13. package/lib/commands/generate/api.js.map +1 -1
  14. package/lib/commands/generate/common.js +100 -100
  15. package/lib/commands/generate/common.js.map +1 -1
  16. package/lib/commands/generate/component.js +156 -156
  17. package/lib/commands/generate/component.js.map +1 -1
  18. package/lib/commands/generate/view.js +165 -165
  19. package/lib/commands/generate/view.js.map +1 -1
  20. package/lib/commands/preview-serve.js +16 -16
  21. package/lib/commands/workspace/add-app.js +180 -173
  22. package/lib/commands/workspace/add-app.js.map +1 -1
  23. package/lib/commands/workspace/create.js +379 -379
  24. package/lib/commands/workspace/create.js.map +1 -1
  25. package/lib/commands/workspace/inspect.js +30 -30
  26. package/lib/commands/workspace/inspect.js.map +1 -1
  27. package/lib/commands/workspace/verify.js +38 -38
  28. package/lib/commands/workspace/verify.js.map +1 -1
  29. package/lib/common/constant.js +64 -63
  30. package/lib/common/constant.js.map +1 -1
  31. package/lib/common/generate-app.js +44 -44
  32. package/lib/common/get-dependencies.js +9 -9
  33. package/lib/common/get-farris-config.js +11 -11
  34. package/lib/common/get-farris-config.js.map +1 -1
  35. package/lib/common/get-version.js +6 -6
  36. package/lib/common/get-vite-config.js +105 -105
  37. package/lib/common/get-vite-config.js.map +1 -1
  38. package/lib/common/output.js +94 -94
  39. package/lib/common/output.js.map +1 -1
  40. package/lib/common/safety.js +171 -171
  41. package/lib/common/safety.js.map +1 -1
  42. package/lib/common/template-renderer.js +114 -114
  43. package/lib/common/template-renderer.js.map +1 -1
  44. package/lib/common/transaction.js +83 -83
  45. package/lib/common/transaction.js.map +1 -1
  46. package/lib/common/verifier.js +146 -146
  47. package/lib/common/verifier.js.map +1 -1
  48. package/lib/config/vite-app.js +13 -13
  49. package/lib/config/vite-common.js +20 -20
  50. package/lib/config/vite-common.js.map +1 -1
  51. package/lib/config/vite-component.js +30 -30
  52. package/lib/config/vite-component.js.map +1 -1
  53. package/lib/config/vite-lib.js +26 -26
  54. package/lib/index.js +175 -177
  55. package/lib/index.js.map +1 -1
  56. package/lib/plugins/create-component-style.js +44 -44
  57. package/lib/plugins/create-package-json.js +33 -33
  58. package/lib/plugins/create-package-json.js.map +1 -1
  59. package/lib/plugins/dts.js +8 -8
  60. package/lib/plugins/html-system.js +9 -9
  61. package/lib/plugins/replace.js +17 -17
  62. package/lib/plugins/systemjs-bundle.js +15 -15
  63. package/lib/plugins/systemjs-bundle.js.map +1 -1
  64. package/package.json +2 -2
@@ -1,380 +1,380 @@
1
- import { resolve, join } from 'node:path';
2
- import { existsSync, mkdirSync } from 'node:fs';
3
- import { spawn } from 'node:child_process';
4
- import inquirer from 'inquirer';
5
- import { DEFAULT_CATALOG_VERSIONS, CLI_VERSION, ExitCode, } from '../../common/constant.js';
6
- import { inspectTarget, checkPlannedConflicts } from '../../common/safety.js';
7
- import { collectTemplateOutputPaths, renderTemplateDir } from '../../common/template-renderer.js';
8
- import { Transaction } from '../../common/transaction.js';
9
- import { verifyWorkspace } from '../../common/verifier.js';
10
- import { sendJson, logInfo, logWarn, fail, setJsonMode, createSpinner } from '../../common/output.js';
11
- import { getDirname } from '../../common/constant.js';
12
- /**
13
- * 从包名推导目录名(去掉 scope 并转为合法 kebab-case)
14
- */
15
- export function deriveAppDir(name) {
16
- const withoutScope = name.replace(/^@[^/]+\//, '');
17
- return withoutScope
18
- .toLowerCase()
19
- .replace(/[^a-z0-9-_]/g, '-')
20
- .replace(/^-+|-+$/g, '');
21
- }
22
- /**
23
- * 校验 npm 包名有效性
24
- */
25
- export function validatePackageName(name) {
26
- if (!name || typeof name !== 'string') {
27
- return false;
28
- }
29
- return /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
30
- }
31
- /**
32
- * 执行 pnpm install 安装依赖
33
- */
34
- export function runPnpmInstall(targetDir, isJson = false) {
35
- return new Promise((resolve) => {
36
- const child = spawn('pnpm', ['install'], {
37
- cwd: targetDir,
38
- shell: true,
39
- stdio: isJson ? ['ignore', 'pipe', 'pipe'] : 'inherit'
40
- });
41
- let stderrData = '';
42
- if (isJson && child.stderr) {
43
- child.stderr.on('data', (chunk) => {
44
- stderrData += chunk.toString();
45
- process.stderr.write(chunk);
46
- });
47
- }
48
- child.on('close', (code) => {
49
- if (code === 0) {
50
- resolve({ success: true });
51
- }
52
- else {
53
- resolve({ success: false, error: `pnpm install 退出码: ${code}` });
54
- }
55
- });
56
- child.on('error', (err) => {
57
- resolve({ success: false, error: err.message });
58
- });
59
- });
60
- }
61
- /* eslint-disable complexity */
62
- export async function createWorkspace(target = '.', options = {}) {
63
- const isJson = !!options.json;
64
- setJsonMode(isJson);
65
- const inspect = inspectTarget(target);
66
- if (!inspect.canCreate) {
67
- fail({
68
- command: 'workspace.create',
69
- error: `目标目录无法创建 Workspace,状态: ${inspect.status}。${inspect.conflicts.length > 0 ? `冲突项: ${inspect.conflicts.join(', ')}` : ''}`,
70
- exitCode: ExitCode.TARGET_CONFLICT,
71
- extra: { status: inspect.status, conflicts: inspect.conflicts }
72
- });
73
- }
74
- let { name, title, appDir } = options;
75
- let router = options.router ?? false;
76
- let routerMode = options.routerMode || 'hash';
77
- let format = options.format || 'es';
78
- let style = options.style || 'css';
79
- let install = options.install ?? false;
80
- // 非 JSON 模式下若缺失必填项,进行交互询问
81
- if (!isJson && !options.yes && !name) {
82
- const prompt = inquirer.createPromptModule();
83
- const answers = await prompt([
84
- {
85
- type: 'input',
86
- name: 'name',
87
- message: '请输入应用名称 (package name):',
88
- default: 'my-app',
89
- validate: (input) => (validatePackageName(input) ? true : '请输入合法的 npm 包名 (如 my-app 或 @scope/my-app)')
90
- },
91
- {
92
- type: 'input',
93
- name: 'title',
94
- message: '请输入展示标题 (title):',
95
- default: (ans) => ans.name
96
- },
97
- {
98
- type: 'confirm',
99
- name: 'router',
100
- message: '是否启用 Vue Router?',
101
- default: false
102
- },
103
- {
104
- type: 'list',
105
- name: 'routerMode',
106
- message: '选择路由模式:',
107
- choices: ['hash', 'history'],
108
- default: 'hash',
109
- when: (ans) => ans.router
110
- },
111
- {
112
- type: 'list',
113
- name: 'style',
114
- message: '选择应用样式方案:',
115
- choices: ['css', 'scss', 'none'],
116
- default: 'css'
117
- },
118
- {
119
- type: 'list',
120
- name: 'format',
121
- message: '选择交付格式:',
122
- choices: ['es', 'systemjs'],
123
- default: 'es'
124
- },
125
- {
126
- type: 'confirm',
127
- name: 'install',
128
- message: '是否在创建完成后自动安装依赖 (pnpm install)?',
129
- default: false
130
- }
131
- ]);
132
- ({ name } = answers);
133
- title = answers.title || name;
134
- router = !!answers.router;
135
- if (answers.routerMode) {
136
- ({ routerMode } = answers);
137
- }
138
- if (answers.style) {
139
- ({ style } = answers);
140
- }
141
- if (answers.format) {
142
- ({ format } = answers);
143
- }
144
- if (answers.install !== undefined) {
145
- install = !!answers.install;
146
- }
147
- }
148
- if (!name) {
149
- fail({
150
- command: 'workspace.create',
151
- error: '必须指定应用名称 (--name <name>)',
152
- exitCode: ExitCode.GENERAL_ERROR
153
- });
154
- }
155
- if (!validatePackageName(name)) {
156
- fail({
157
- command: 'workspace.create',
158
- error: `非法的应用包名: "${name}"`,
159
- exitCode: ExitCode.GENERAL_ERROR
160
- });
161
- }
162
- title = title || name;
163
- appDir = appDir || deriveAppDir(name);
164
- if (!appDir || !/^[a-z0-9-_]+$/.test(appDir)) {
165
- fail({
166
- command: 'workspace.create',
167
- error: `非法的子目录名: "${appDir}",仅支持小写字母、数字、短横线与下划线`,
168
- exitCode: ExitCode.GENERAL_ERROR
169
- });
170
- }
171
- const targetDir = inspect.target;
172
- const templatesBase = resolve(getDirname(import.meta.url), '../../../templates/workspace');
173
- const rootTemplateDir = join(templatesBase, 'root');
174
- const appTemplateDir = join(templatesBase, 'app');
175
- const rootVariables = {
176
- pnpmVersion: DEFAULT_CATALOG_VERSIONS['pnpm'] || '9.15.4',
177
- farrisUiVueVersion: DEFAULT_CATALOG_VERSIONS['@farris/ui-vue'] || '1.8.3',
178
- farrisCliVersion: CLI_VERSION,
179
- typescriptVersion: DEFAULT_CATALOG_VERSIONS['typescript'] || '5.7.3',
180
- viteVersion: DEFAULT_CATALOG_VERSIONS['vite'] || '5.4.19',
181
- vueVersion: DEFAULT_CATALOG_VERSIONS['vue'] || '3.5.13',
182
- vueRouterVersion: DEFAULT_CATALOG_VERSIONS['vue-router'] || '4.5.0',
183
- vueTscVersion: DEFAULT_CATALOG_VERSIONS['vue-tsc'] || '2.2.0',
184
- sassVersion: DEFAULT_CATALOG_VERSIONS['sass'] || '1.90.0',
185
- title,
186
- appDir,
187
- appName: name
188
- };
189
- const appTargetDir = join(targetDir, 'packages', appDir);
190
- // 组装条件变量
191
- const routerDependency = router ? ',\n "vue-router": "catalog:"' : '';
192
- const sassDevDependency = style === 'scss' ? ',\n "sass": "catalog:"' : '';
193
- let styleImport = '';
194
- if (style === 'css') {
195
- styleImport = "\nimport './styles/index.css';";
196
- }
197
- else if (style === 'scss') {
198
- styleImport = "\nimport './styles/index.scss';";
199
- }
200
- const routerImport = router ? "\nimport router from './router';" : '';
201
- const routerUse = router ? '\napp.use(router);' : '';
202
- // 模板保留扩展点,但通用 CLI 不再内置 i18n 运行时契约。
203
- const i18nImport = '';
204
- const i18nUse = '';
205
- const appRouterImport = router ? "\nimport { RouterView } from 'vue-router';" : '';
206
- const appTemplateBody = router
207
- ? ' <RouterView />'
208
- : ` <h1>{${JSON.stringify(title)}}</h1>`;
209
- const appVariables = {
210
- appName: name,
211
- title,
212
- appDir,
213
- base: './',
214
- routerMode: router ? routerMode : 'none',
215
- format,
216
- routerDependency,
217
- sassDevDependency,
218
- styleImport,
219
- routerImport,
220
- routerUse,
221
- i18nImport,
222
- i18nUse,
223
- appRouterImport,
224
- appTemplateBody,
225
- routerHistoryFunction: routerMode === 'history' ? 'createWebHistory' : 'createWebHashHistory',
226
- routerHistoryCall: routerMode === 'history'
227
- ? 'createWebHistory(import.meta.env.BASE_URL)'
228
- : 'createWebHashHistory()'
229
- };
230
- // 模板文件过滤规则
231
- const appFilter = (relPath) => {
232
- const normalized = relPath.replace(/\\/g, '/');
233
- // 样式过滤
234
- if (style === 'none' && normalized.startsWith('src/styles')) {
235
- return false;
236
- }
237
- if (style === 'css' && (normalized === 'src/styles/index.scss' || normalized === 'src/styles/index.scss.tpl')) {
238
- return false;
239
- }
240
- if (style === 'scss' && (normalized === 'src/styles/index.css' || normalized === 'src/styles/index.css.tpl')) {
241
- return false;
242
- }
243
- // Router 与 Views 过滤
244
- if (!router) {
245
- if (normalized.startsWith('src/router') || normalized.startsWith('src/views')) {
246
- return false;
247
- }
248
- }
249
- return true;
250
- };
251
- const plannedRelativeFiles = [
252
- ...collectTemplateOutputPaths(rootTemplateDir),
253
- ...collectTemplateOutputPaths(appTemplateDir, appFilter).map((path) => join('packages', appDir, path))
254
- ];
255
- const conflicts = checkPlannedConflicts(targetDir, plannedRelativeFiles);
256
- if (conflicts.length > 0 && !options.force) {
257
- const shouldOverwrite = !isJson &&
258
- !options.yes &&
259
- (await inquirer.createPromptModule()([
260
- {
261
- type: 'confirm',
262
- name: 'overwrite',
263
- message: `以下 ${conflicts.length} 个文件已存在,是否覆盖?\n${conflicts.join('\n')}`,
264
- default: false
265
- }
266
- ])).overwrite;
267
- if (!shouldOverwrite) {
268
- fail({
269
- command: 'workspace.create',
270
- error: `目标目录包含将被模板写入的文件: ${conflicts.join(', ')}。请交互确认,或显式传入 --force 覆盖。`,
271
- exitCode: ExitCode.TARGET_CONFLICT,
272
- extra: { conflicts }
273
- });
274
- }
275
- }
276
- const transaction = new Transaction();
277
- plannedRelativeFiles.forEach((path) => transaction.prepareWrite(join(targetDir, path)));
278
- const spinner = createSpinner('正在生成 Workspace 工程模板...');
279
- spinner.start();
280
- try {
281
- if (!existsSync(targetDir)) {
282
- mkdirSync(targetDir, { recursive: true });
283
- transaction.recordCreatedDir(targetDir);
284
- }
285
- // 1. 渲染根目录模板
286
- const rootRender = renderTemplateDir({
287
- sourceDir: rootTemplateDir,
288
- targetDir,
289
- variables: rootVariables,
290
- skipExisting: false
291
- });
292
- if (rootRender.unreplacedPlaceholders.length > 0) {
293
- throw new Error(`根模板渲染出现未替换占位符: ${rootRender.unreplacedPlaceholders.map((p) => `${p.file} (${p.placeholder})`).join(', ')}`);
294
- }
295
- // 2. 渲染子应用目录 (packages/<appDir>/)
296
- mkdirSync(appTargetDir, { recursive: true });
297
- transaction.recordCreatedDir(appTargetDir);
298
- const appRender = renderTemplateDir({
299
- sourceDir: appTemplateDir,
300
- targetDir: appTargetDir,
301
- variables: appVariables,
302
- skipExisting: false,
303
- filter: appFilter
304
- });
305
- if (appRender.unreplacedPlaceholders.length > 0) {
306
- throw new Error(`应用模板渲染出现未替换占位符: ${appRender.unreplacedPlaceholders.map((p) => `${p.file} (${p.placeholder})`).join(', ')}`);
307
- }
308
- // 3. 执行 Verify
309
- const verifyResult = verifyWorkspace(targetDir, inspect.preservedInputEntries);
310
- if (!verifyResult.ok) {
311
- throw new Error(`Workspace 校验失败: ${verifyResult.errors.join('; ')}`);
312
- }
313
- // 4. 提交事务
314
- transaction.commit();
315
- spinner.succeed('Workspace 工程生成并校验通过!');
316
- // 5. 若启用 --install,自动执行依赖安装
317
- let installed = false;
318
- let installError;
319
- if (install) {
320
- const installSpinner = createSpinner('正在安装依赖 (pnpm install)...');
321
- installSpinner.start();
322
- const installRes = await runPnpmInstall(targetDir, isJson);
323
- if (installRes.success) {
324
- installed = true;
325
- installSpinner.succeed('依赖安装成功!');
326
- }
327
- else {
328
- installed = false;
329
- installError = installRes.error;
330
- installSpinner.fail('依赖安装失败');
331
- logWarn(`\n提示: 依赖安装出现异常 (${installError}),您可以稍后进入目录手动执行 "pnpm install"`);
332
- }
333
- }
334
- const allCreated = [...rootRender.filesCreated, ...appRender.filesCreated];
335
- if (isJson) {
336
- sendJson({
337
- command: 'workspace.create',
338
- ok: true,
339
- target: targetDir,
340
- appDir,
341
- appName: name,
342
- title,
343
- router,
344
- routerMode: router ? routerMode : 'none',
345
- format,
346
- style,
347
- installed,
348
- ...(installError ? { installError } : {}),
349
- filesCreated: allCreated,
350
- preservedInputVerified: inspect.preservedInputEntries.length > 0
351
- });
352
- return;
353
- }
354
- logInfo(`\n✨ Workspace 已就绪: ${targetDir}`);
355
- logInfo(`- 应用: ${name} (目录: packages/${appDir})`);
356
- logInfo(`- 路由: ${router ? routerMode : '未启用'}`);
357
- logInfo(`- 样式: ${style}`);
358
- logInfo(`- 交付格式: ${format}`);
359
- logInfo(`- 依赖安装: ${installed ? '已完成' : '未执行 (可通过 pnpm install 安装)'}`);
360
- logInfo('\n下一步 (Next Steps):');
361
- logInfo(` 1. cd ${target}`);
362
- if (!installed) {
363
- logInfo(' 2. pnpm install');
364
- logInfo(` 3. pnpm --filter "${name}" dev\n`);
365
- }
366
- else {
367
- logInfo(` 2. pnpm --filter "${name}" dev\n`);
368
- }
369
- }
370
- catch (err) {
371
- spinner.fail('Workspace 生成失败,正在回滚...');
372
- transaction.rollback();
373
- fail({
374
- command: 'workspace.create',
375
- error: `生成 Workspace 失败: ${err.message}`,
376
- exitCode: ExitCode.GENERAL_ERROR
377
- });
378
- }
379
- }
1
+ import { resolve, join } from 'node:path';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { spawn } from 'node:child_process';
4
+ import inquirer from 'inquirer';
5
+ import { DEFAULT_CATALOG_VERSIONS, ExitCode, } from '../../common/constant.js';
6
+ import { inspectTarget, checkPlannedConflicts } from '../../common/safety.js';
7
+ import { collectTemplateOutputPaths, renderTemplateDir } from '../../common/template-renderer.js';
8
+ import { Transaction } from '../../common/transaction.js';
9
+ import { verifyWorkspace } from '../../common/verifier.js';
10
+ import { sendJson, logInfo, logWarn, fail, setJsonMode, createSpinner } from '../../common/output.js';
11
+ import { getDirname } from '../../common/constant.js';
12
+ /**
13
+ * 从包名推导目录名(去掉 scope 并转为合法 kebab-case)
14
+ */
15
+ export function deriveAppDir(name) {
16
+ const withoutScope = name.replace(/^@[^/]+\//, '');
17
+ return withoutScope
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9-_]/g, '-')
20
+ .replace(/^-+|-+$/g, '');
21
+ }
22
+ /**
23
+ * 校验 npm 包名有效性
24
+ */
25
+ export function validatePackageName(name) {
26
+ if (!name || typeof name !== 'string') {
27
+ return false;
28
+ }
29
+ return /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
30
+ }
31
+ /**
32
+ * 执行 pnpm install 安装依赖
33
+ */
34
+ export function runPnpmInstall(targetDir, isJson = false) {
35
+ return new Promise((resolve) => {
36
+ const child = spawn('pnpm', ['install'], {
37
+ cwd: targetDir,
38
+ shell: true,
39
+ stdio: isJson ? ['ignore', 'pipe', 'pipe'] : 'inherit'
40
+ });
41
+ let stderrData = '';
42
+ if (isJson && child.stderr) {
43
+ child.stderr.on('data', (chunk) => {
44
+ stderrData += chunk.toString();
45
+ process.stderr.write(chunk);
46
+ });
47
+ }
48
+ child.on('close', (code) => {
49
+ if (code === 0) {
50
+ resolve({ success: true });
51
+ }
52
+ else {
53
+ resolve({ success: false, error: `pnpm install 退出码: ${code}` });
54
+ }
55
+ });
56
+ child.on('error', (err) => {
57
+ resolve({ success: false, error: err.message });
58
+ });
59
+ });
60
+ }
61
+ /* eslint-disable complexity */
62
+ export async function createWorkspace(target = '.', options = {}) {
63
+ const isJson = !!options.json;
64
+ setJsonMode(isJson);
65
+ const inspect = inspectTarget(target);
66
+ if (!inspect.canCreate) {
67
+ fail({
68
+ command: 'workspace.create',
69
+ error: `目标目录无法创建 Workspace,状态: ${inspect.status}。${inspect.conflicts.length > 0 ? `冲突项: ${inspect.conflicts.join(', ')}` : ''}`,
70
+ exitCode: ExitCode.TARGET_CONFLICT,
71
+ extra: { status: inspect.status, conflicts: inspect.conflicts }
72
+ });
73
+ }
74
+ let { name, title } = options;
75
+ let router = options.router ?? false;
76
+ let routerMode = options.routerMode || 'hash';
77
+ let format = options.format || 'es';
78
+ let style = options.style || 'css';
79
+ let install = options.install ?? false;
80
+ // 非 JSON 模式下若缺失必填项,进行交互询问
81
+ if (!isJson && !options.yes && !name) {
82
+ const prompt = inquirer.createPromptModule();
83
+ const answers = await prompt([
84
+ {
85
+ type: 'input',
86
+ name: 'name',
87
+ message: '请输入应用名称 (package name):',
88
+ default: 'my-app',
89
+ validate: (input) => (validatePackageName(input) ? true : '请输入合法的 npm 包名 (如 my-app 或 @scope/my-app)')
90
+ },
91
+ {
92
+ type: 'input',
93
+ name: 'title',
94
+ message: '请输入展示标题 (title):',
95
+ default: (ans) => ans.name
96
+ },
97
+ {
98
+ type: 'confirm',
99
+ name: 'router',
100
+ message: '是否启用 Vue Router?',
101
+ default: false
102
+ },
103
+ {
104
+ type: 'list',
105
+ name: 'routerMode',
106
+ message: '选择路由模式:',
107
+ choices: ['hash', 'history'],
108
+ default: 'hash',
109
+ when: (ans) => ans.router
110
+ },
111
+ {
112
+ type: 'list',
113
+ name: 'style',
114
+ message: '选择应用样式方案:',
115
+ choices: ['css', 'scss', 'none'],
116
+ default: 'css'
117
+ },
118
+ {
119
+ type: 'list',
120
+ name: 'format',
121
+ message: '选择交付格式:',
122
+ choices: ['es', 'systemjs'],
123
+ default: 'es'
124
+ },
125
+ {
126
+ type: 'confirm',
127
+ name: 'install',
128
+ message: '是否在创建完成后自动安装依赖 (pnpm install)?',
129
+ default: false
130
+ }
131
+ ]);
132
+ ({ name } = answers);
133
+ title = answers.title || name;
134
+ router = !!answers.router;
135
+ if (answers.routerMode) {
136
+ ({ routerMode } = answers);
137
+ }
138
+ if (answers.style) {
139
+ ({ style } = answers);
140
+ }
141
+ if (answers.format) {
142
+ ({ format } = answers);
143
+ }
144
+ if (answers.install !== undefined) {
145
+ install = !!answers.install;
146
+ }
147
+ }
148
+ if (!name) {
149
+ fail({
150
+ command: 'workspace.create',
151
+ error: '必须指定应用名称 (--name <name>)',
152
+ exitCode: ExitCode.GENERAL_ERROR
153
+ });
154
+ }
155
+ if (!validatePackageName(name)) {
156
+ fail({
157
+ command: 'workspace.create',
158
+ error: `非法的应用包名: "${name}"`,
159
+ exitCode: ExitCode.GENERAL_ERROR
160
+ });
161
+ }
162
+ title = title || name;
163
+ const appDir = deriveAppDir(name);
164
+ if (!appDir || !/^[a-z0-9-_]+$/.test(appDir)) {
165
+ fail({
166
+ command: 'workspace.create',
167
+ error: `无法从应用名 "${name}" 推导出合法的子目录名`,
168
+ exitCode: ExitCode.GENERAL_ERROR
169
+ });
170
+ }
171
+ const targetDir = inspect.target;
172
+ const templatesBase = resolve(getDirname(import.meta.url), '../../../templates/workspace');
173
+ const rootTemplateDir = join(templatesBase, 'root');
174
+ const appTemplateDir = join(templatesBase, 'app');
175
+ const rootVariables = {
176
+ pnpmVersion: DEFAULT_CATALOG_VERSIONS['pnpm'],
177
+ farrisUiVueVersion: DEFAULT_CATALOG_VERSIONS['@farris/ui-vue'],
178
+ farrisCliVersion: DEFAULT_CATALOG_VERSIONS['@farris/cli'],
179
+ typescriptVersion: DEFAULT_CATALOG_VERSIONS['typescript'],
180
+ viteVersion: DEFAULT_CATALOG_VERSIONS['vite'],
181
+ vueVersion: DEFAULT_CATALOG_VERSIONS['vue'],
182
+ vueRouterVersion: DEFAULT_CATALOG_VERSIONS['vue-router'],
183
+ vueTscVersion: DEFAULT_CATALOG_VERSIONS['vue-tsc'],
184
+ sassVersion: DEFAULT_CATALOG_VERSIONS['sass'],
185
+ title,
186
+ appDir,
187
+ appName: name
188
+ };
189
+ const appTargetDir = join(targetDir, 'packages', appDir);
190
+ // 组装条件变量
191
+ const routerDependency = router ? ',\n "vue-router": "catalog:"' : '';
192
+ const sassDevDependency = style === 'scss' ? ',\n "sass": "catalog:"' : '';
193
+ let styleImport = '';
194
+ if (style === 'css') {
195
+ styleImport = "\nimport './styles/index.css';";
196
+ }
197
+ else if (style === 'scss') {
198
+ styleImport = "\nimport './styles/index.scss';";
199
+ }
200
+ const routerImport = router ? "\nimport router from './router';" : '';
201
+ const routerUse = router ? '\napp.use(router);' : '';
202
+ // 模板保留扩展点,但通用 CLI 不再内置 i18n 运行时契约。
203
+ const i18nImport = '';
204
+ const i18nUse = '';
205
+ const appRouterImport = router ? "\nimport { RouterView } from 'vue-router';" : '';
206
+ const appTemplateBody = router
207
+ ? ' <RouterView />'
208
+ : ` <h1>{${JSON.stringify(title)}}</h1>`;
209
+ const appVariables = {
210
+ appName: name,
211
+ title,
212
+ appDir,
213
+ base: './',
214
+ routerMode: router ? routerMode : 'none',
215
+ format,
216
+ routerDependency,
217
+ sassDevDependency,
218
+ styleImport,
219
+ routerImport,
220
+ routerUse,
221
+ i18nImport,
222
+ i18nUse,
223
+ appRouterImport,
224
+ appTemplateBody,
225
+ routerHistoryFunction: routerMode === 'history' ? 'createWebHistory' : 'createWebHashHistory',
226
+ routerHistoryCall: routerMode === 'history'
227
+ ? 'createWebHistory(import.meta.env.BASE_URL)'
228
+ : 'createWebHashHistory()'
229
+ };
230
+ // 模板文件过滤规则
231
+ const appFilter = (relPath) => {
232
+ const normalized = relPath.replace(/\\/g, '/');
233
+ // 样式过滤
234
+ if (style === 'none' && normalized.startsWith('src/styles')) {
235
+ return false;
236
+ }
237
+ if (style === 'css' && (normalized === 'src/styles/index.scss' || normalized === 'src/styles/index.scss.tpl')) {
238
+ return false;
239
+ }
240
+ if (style === 'scss' && (normalized === 'src/styles/index.css' || normalized === 'src/styles/index.css.tpl')) {
241
+ return false;
242
+ }
243
+ // Router 与 Views 过滤
244
+ if (!router) {
245
+ if (normalized.startsWith('src/router') || normalized.startsWith('src/views')) {
246
+ return false;
247
+ }
248
+ }
249
+ return true;
250
+ };
251
+ const plannedRelativeFiles = [
252
+ ...collectTemplateOutputPaths(rootTemplateDir),
253
+ ...collectTemplateOutputPaths(appTemplateDir, appFilter).map((path) => join('packages', appDir, path))
254
+ ];
255
+ const conflicts = checkPlannedConflicts(targetDir, plannedRelativeFiles);
256
+ if (conflicts.length > 0 && !options.force) {
257
+ const shouldOverwrite = !isJson &&
258
+ !options.yes &&
259
+ (await inquirer.createPromptModule()([
260
+ {
261
+ type: 'confirm',
262
+ name: 'overwrite',
263
+ message: `以下 ${conflicts.length} 个文件已存在,是否覆盖?\n${conflicts.join('\n')}`,
264
+ default: false
265
+ }
266
+ ])).overwrite;
267
+ if (!shouldOverwrite) {
268
+ fail({
269
+ command: 'workspace.create',
270
+ error: `目标目录包含将被模板写入的文件: ${conflicts.join(', ')}。请交互确认,或显式传入 --force 覆盖。`,
271
+ exitCode: ExitCode.TARGET_CONFLICT,
272
+ extra: { conflicts }
273
+ });
274
+ }
275
+ }
276
+ const transaction = new Transaction();
277
+ plannedRelativeFiles.forEach((path) => transaction.prepareWrite(join(targetDir, path)));
278
+ const spinner = createSpinner('正在生成 Workspace 工程模板...');
279
+ spinner.start();
280
+ try {
281
+ if (!existsSync(targetDir)) {
282
+ mkdirSync(targetDir, { recursive: true });
283
+ transaction.recordCreatedDir(targetDir);
284
+ }
285
+ // 1. 渲染根目录模板
286
+ const rootRender = renderTemplateDir({
287
+ sourceDir: rootTemplateDir,
288
+ targetDir,
289
+ variables: rootVariables,
290
+ skipExisting: false
291
+ });
292
+ if (rootRender.unreplacedPlaceholders.length > 0) {
293
+ throw new Error(`根模板渲染出现未替换占位符: ${rootRender.unreplacedPlaceholders.map((p) => `${p.file} (${p.placeholder})`).join(', ')}`);
294
+ }
295
+ // 2. 渲染子应用目录 (packages/<appDir>/)
296
+ mkdirSync(appTargetDir, { recursive: true });
297
+ transaction.recordCreatedDir(appTargetDir);
298
+ const appRender = renderTemplateDir({
299
+ sourceDir: appTemplateDir,
300
+ targetDir: appTargetDir,
301
+ variables: appVariables,
302
+ skipExisting: false,
303
+ filter: appFilter
304
+ });
305
+ if (appRender.unreplacedPlaceholders.length > 0) {
306
+ throw new Error(`应用模板渲染出现未替换占位符: ${appRender.unreplacedPlaceholders.map((p) => `${p.file} (${p.placeholder})`).join(', ')}`);
307
+ }
308
+ // 3. 执行 Verify
309
+ const verifyResult = verifyWorkspace(targetDir, inspect.preservedInputEntries);
310
+ if (!verifyResult.ok) {
311
+ throw new Error(`Workspace 校验失败: ${verifyResult.errors.join('; ')}`);
312
+ }
313
+ // 4. 提交事务
314
+ transaction.commit();
315
+ spinner.succeed('Workspace 工程生成并校验通过!');
316
+ // 5. 若启用 --install,自动执行依赖安装
317
+ let installed = false;
318
+ let installError;
319
+ if (install) {
320
+ const installSpinner = createSpinner('正在安装依赖 (pnpm install)...');
321
+ installSpinner.start();
322
+ const installRes = await runPnpmInstall(targetDir, isJson);
323
+ if (installRes.success) {
324
+ installed = true;
325
+ installSpinner.succeed('依赖安装成功!');
326
+ }
327
+ else {
328
+ installed = false;
329
+ installError = installRes.error;
330
+ installSpinner.fail('依赖安装失败');
331
+ logWarn(`\n提示: 依赖安装出现异常 (${installError}),您可以稍后进入目录手动执行 "pnpm install"`);
332
+ }
333
+ }
334
+ const allCreated = [...rootRender.filesCreated, ...appRender.filesCreated];
335
+ if (isJson) {
336
+ sendJson({
337
+ command: 'workspace.create',
338
+ ok: true,
339
+ target: targetDir,
340
+ appDir,
341
+ appName: name,
342
+ title,
343
+ router,
344
+ routerMode: router ? routerMode : 'none',
345
+ format,
346
+ style,
347
+ installed,
348
+ ...(installError ? { installError } : {}),
349
+ filesCreated: allCreated,
350
+ preservedInputVerified: inspect.preservedInputEntries.length > 0
351
+ });
352
+ return;
353
+ }
354
+ logInfo(`\n✨ Workspace 已就绪: ${targetDir}`);
355
+ logInfo(`- 应用: ${name} (目录: packages/${appDir})`);
356
+ logInfo(`- 路由: ${router ? routerMode : '未启用'}`);
357
+ logInfo(`- 样式: ${style}`);
358
+ logInfo(`- 交付格式: ${format}`);
359
+ logInfo(`- 依赖安装: ${installed ? '已完成' : '未执行 (可通过 pnpm install 安装)'}`);
360
+ logInfo('\n下一步 (Next Steps):');
361
+ logInfo(` 1. cd ${target}`);
362
+ if (!installed) {
363
+ logInfo(' 2. pnpm install');
364
+ logInfo(` 3. pnpm --filter "${name}" dev\n`);
365
+ }
366
+ else {
367
+ logInfo(` 2. pnpm --filter "${name}" dev\n`);
368
+ }
369
+ }
370
+ catch (err) {
371
+ spinner.fail('Workspace 生成失败,正在回滚...');
372
+ transaction.rollback();
373
+ fail({
374
+ command: 'workspace.create',
375
+ error: `生成 Workspace 失败: ${err.message}`,
376
+ exitCode: ExitCode.GENERAL_ERROR
377
+ });
378
+ }
379
+ }
380
380
  //# sourceMappingURL=create.js.map