pandax 0.0.1 → 0.0.2-beta.0

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 (75) hide show
  1. package/README.md +15 -9
  2. package/bin/pandax.js +374 -39
  3. package/package.json +3 -2
  4. package/skills/pandax-frontend-architecture/SKILL.md +1432 -1239
  5. package/templates/pandax-monorepo/apps/base/package.json +5 -2
  6. package/templates/pandax-monorepo/apps/base/src/views/home/index.vue +5 -0
  7. package/templates/pandax-monorepo/apps/base/uno.config.ts +2 -0
  8. package/templates/pandax-monorepo/apps/base/vite.config.ts +4 -2
  9. package/templates/pandax-monorepo/apps/main/package.json +2 -1
  10. package/templates/pandax-monorepo/apps/main/uno.config.ts +2 -0
  11. package/templates/pandax-monorepo/apps/main/vite.config.ts +1 -1
  12. package/templates/pandax-monorepo/package.json +1 -1
  13. package/templates/pandax-monorepo/packages/components/package.json +58 -5
  14. package/templates/pandax-monorepo/packages/components/src/button/Prop.ts +8 -0
  15. package/templates/pandax-monorepo/packages/components/src/button/Type.ts +4 -0
  16. package/templates/pandax-monorepo/packages/components/src/button/index.module.scss +3 -0
  17. package/templates/pandax-monorepo/packages/components/src/button/index.ts +11 -0
  18. package/templates/pandax-monorepo/packages/components/src/button/index.vue +74 -0
  19. package/templates/pandax-monorepo/packages/components/src/card/Prop.ts +6 -0
  20. package/templates/pandax-monorepo/packages/components/src/card/Type.ts +3 -0
  21. package/templates/pandax-monorepo/packages/components/src/card/index.module.scss +7 -0
  22. package/templates/pandax-monorepo/packages/components/src/card/index.ts +11 -0
  23. package/templates/pandax-monorepo/packages/components/src/card/index.vue +78 -0
  24. package/templates/pandax-monorepo/packages/components/src/composables/use-locale/Type.ts +6 -0
  25. package/templates/pandax-monorepo/packages/components/src/composables/use-locale/index.ts +35 -0
  26. package/templates/pandax-monorepo/packages/components/src/index.ts +23 -2
  27. package/templates/pandax-monorepo/packages/components/src/install.ts +23 -0
  28. package/templates/pandax-monorepo/packages/components/src/locales/en-US.ts +9 -0
  29. package/templates/pandax-monorepo/packages/components/src/locales/index.ts +10 -0
  30. package/templates/pandax-monorepo/packages/components/src/locales/zh-CN.ts +9 -0
  31. package/templates/pandax-monorepo/packages/components/src/resolver.ts +25 -0
  32. package/templates/pandax-monorepo/packages/components/src/vue-i18n.ts +15 -0
  33. package/templates/pandax-monorepo/packages/components/tsconfig.json +18 -0
  34. package/templates/pandax-monorepo/packages/components/vite.config.ts +32 -0
  35. package/templates/pandax-monorepo/pnpm-lock.yaml +5376 -0
  36. package/templates/pandax-monorepo/pnpm-workspace.yaml +3 -0
  37. package/templates/pandax-monorepo/turbo.json +1 -1
  38. package/templates/ui-lib/.editorconfig +16 -0
  39. package/templates/ui-lib/.husky/commit-msg +2 -0
  40. package/templates/ui-lib/.husky/pre-commit +2 -0
  41. package/templates/ui-lib/.prettierrc.json +28 -0
  42. package/templates/ui-lib/README.md +49 -0
  43. package/templates/ui-lib/commitlint.config.js +38 -0
  44. package/templates/ui-lib/env.d.ts +7 -0
  45. package/templates/ui-lib/eslint.config.js +60 -0
  46. package/templates/ui-lib/package.json +73 -0
  47. package/templates/ui-lib/playground/App.vue +13 -0
  48. package/templates/ui-lib/playground/index.html +12 -0
  49. package/templates/ui-lib/playground/main.ts +11 -0
  50. package/templates/ui-lib/playground/vite.config.ts +12 -0
  51. package/templates/ui-lib/src/button/Prop.ts +5 -0
  52. package/templates/ui-lib/src/button/Type.ts +3 -0
  53. package/templates/ui-lib/src/button/index.module.scss +3 -0
  54. package/templates/ui-lib/src/button/index.ts +11 -0
  55. package/templates/ui-lib/src/button/index.vue +34 -0
  56. package/templates/ui-lib/src/card/Prop.ts +4 -0
  57. package/templates/ui-lib/src/card/Type.ts +1 -0
  58. package/templates/ui-lib/src/card/index.module.scss +3 -0
  59. package/templates/ui-lib/src/card/index.ts +11 -0
  60. package/templates/ui-lib/src/card/index.vue +21 -0
  61. package/templates/ui-lib/src/composables/use-locale/Type.ts +6 -0
  62. package/templates/ui-lib/src/composables/use-locale/index.ts +35 -0
  63. package/templates/ui-lib/src/index.ts +22 -0
  64. package/templates/ui-lib/src/install.ts +23 -0
  65. package/templates/ui-lib/src/locales/en-US.ts +9 -0
  66. package/templates/ui-lib/src/locales/index.ts +10 -0
  67. package/templates/ui-lib/src/locales/zh-CN.ts +9 -0
  68. package/templates/ui-lib/src/resolver.ts +25 -0
  69. package/templates/ui-lib/src/vue-i18n.ts +15 -0
  70. package/templates/ui-lib/tsconfig.json +23 -0
  71. package/templates/ui-lib/tsconfig.node.json +10 -0
  72. package/templates/ui-lib/uno.config.ts +5 -0
  73. package/templates/ui-lib/vite.config.ts +27 -0
  74. package/templates/pandax-monorepo/packages/components/src/PandaButton.vue +0 -15
  75. package/templates/pandax-monorepo/packages/components/src/PandaCard.vue +0 -16
package/README.md CHANGED
@@ -24,11 +24,25 @@ npx pandax --tool codebuddy
24
24
  ## 初始化项目模板
25
25
 
26
26
  ```bash
27
- npx pandax --init vue3 # 初始化 Vue3 单应用模板
27
+ npx pandax --init # 交互式选择项目类型
28
+ npx pandax --init ui-lib # 初始化独立组件库(交互式配置)
29
+ npx pandax --init ui-lib --name my-ui --prefix A --no-i18n # 全参数跳过交互
28
30
  npx pandax --init pandax-monorepo # 初始化 Monorepo + 微前端模板
29
31
  npx pandax --init vue3 --with-skills # 初始化模板并安装 skills
30
32
  ```
31
33
 
34
+ ### ui-lib 模式 CLI 参数
35
+
36
+ | 参数 | 说明 | 默认值 |
37
+ |------|------|--------|
38
+ | `--name <pkg>` | npm 包名(小写,`^[a-z][a-z0-9-]*$`) | `my-ui-lib` |
39
+ | `--prefix <Pascal>` | 组件前缀(PascalCase) | `My` |
40
+ | `--i18n` / `--no-i18n` | 启用/关闭国际化 | 启用 |
41
+ | `--element-plus` / `--no-element-plus` | 启用/关闭 Element Plus | 启用 |
42
+ | `--pm <pnpm\|npm\|yarn>` | 包管理器 | `pnpm` |
43
+
44
+ 初始化后的组件库支持:独立 npm 发布、Monorepo 源码引用、组件与 CSS 按需引入、国际化(详见 [pandax-frontend-architecture](skills/pandax-frontend-architecture/SKILL.md))。
45
+
32
46
  ## 卸载
33
47
 
34
48
  ```bash
@@ -43,7 +57,6 @@ npx pandax --uninstall
43
57
  |-------|------|
44
58
  | [pandax-frontend-architecture](skills/pandax-frontend-architecture/SKILL.md) | 设计前端项目架构与技术方案 |
45
59
  | [pandax-frontend-component-review](skills/pandax-frontend-component-review/SKILL.md) | 审查 Vue/React 组件代码 |
46
- | [pandax-frontend-chinese-copywriting](skills/pandax-frontend-chinese-copywriting/SKILL.md) | 检查并修正项目中的中文文案排版 |
47
60
  | [pandax-frontend-api-mock](skills/pandax-frontend-api-mock/SKILL.md) | 规范 API 对接与 Mock 数据管理 |
48
61
  | [pandax-frontend-requirement](skills/pandax-frontend-requirement/SKILL.md) | 梳理前端需求并输出可执行方案 |
49
62
  | [pandax-frontend-standards](skills/pandax-frontend-standards/SKILL.md) | 统一引用前端规范文档 |
@@ -71,13 +84,6 @@ npx pandax --uninstall
71
84
  node bin/pandax.js --tool trae
72
85
  ```
73
86
 
74
- ### 发布到 npm
75
-
76
- ```bash
77
- npm login
78
- npm publish --access public
79
- ```
80
-
81
87
  ## 参考
82
88
 
83
89
  - [superpowers-zh](https://www.npmjs.com/package/superpowers-zh)
package/bin/pandax.js CHANGED
@@ -19,6 +19,9 @@ const SKILLS_SRC = resolve(__dirname, '..', 'skills');
19
19
  const TEMPLATES_SRC = resolve(__dirname, '..', 'templates');
20
20
  const PROJECT_DIR = process.cwd();
21
21
 
22
+ // 跳过这些目录/文件,避免复制 node_modules 等构建产物
23
+ const SKIP_ENTRIES = new Set(['.DS_Store', 'node_modules', '.git', 'dist', '.turbo']);
24
+
22
25
  // 手动递归复制,避免 Windows npx 缓存 junction 问题
23
26
  function copyDirSync(src, dest) {
24
27
  let realSrc = src;
@@ -27,7 +30,7 @@ function copyDirSync(src, dest) {
27
30
  mkdirSync(dest, { recursive: true });
28
31
  const entries = readdirSync(realSrc, { withFileTypes: true });
29
32
  for (const entry of entries) {
30
- if (entry.name === '.DS_Store') continue;
33
+ if (SKIP_ENTRIES.has(entry.name)) continue;
31
34
  const srcPath = join(realSrc, entry.name);
32
35
  const destPath = join(dest, entry.name);
33
36
  let stat;
@@ -61,6 +64,76 @@ function removeSync(p) {
61
64
  }
62
65
  }
63
66
 
67
+ const readline = require('readline');
68
+
69
+ function isInteractive() {
70
+ return process.stdin.isTTY === true;
71
+ }
72
+
73
+ function ask(question, defaultVal) {
74
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
75
+ return new Promise(resolve => {
76
+ const hint = defaultVal ? ` (${defaultVal})` : '';
77
+ rl.question(` ? ${question}${hint}: `, ans => {
78
+ rl.close();
79
+ resolve((ans.trim() || defaultVal || '').trim());
80
+ });
81
+ });
82
+ }
83
+
84
+ async function askBool(question, defaultVal) {
85
+ const raw = await ask(question, defaultVal ? 'y' : 'n');
86
+ return /^(y|yes|true|1)$/i.test(raw);
87
+ }
88
+
89
+ const PKG_NAME_RE = /^[a-z][a-z0-9-]*$/;
90
+ const PREFIX_RE = /^[A-Z][a-zA-Z0-9]*$/;
91
+
92
+ async function collectUiLibConfig(flags) {
93
+ let name = flags.name;
94
+ if (!name) {
95
+ let attempts = 0;
96
+ do {
97
+ name = await ask('npm 包名', 'my-ui-lib');
98
+ attempts++;
99
+ if (!PKG_NAME_RE.test(name) && attempts >= 3) {
100
+ name = 'my-ui-lib';
101
+ break;
102
+ }
103
+ } while (!PKG_NAME_RE.test(name));
104
+ } else if (!PKG_NAME_RE.test(name)) {
105
+ console.error(` ❌ 包名非法: ${name}(需匹配 ${PKG_NAME_RE})`);
106
+ process.exit(1);
107
+ }
108
+ let prefix = flags.prefix || await ask('组件前缀 (PascalCase)', 'My');
109
+ if (!PREFIX_RE.test(prefix)) prefix = 'My';
110
+ const i18n = flags.i18n ?? await askBool('启用 i18n?', true);
111
+ const ep = flags.elementPlus ?? await askBool('启用 Element Plus?', true);
112
+ const pm = flags.pm || await ask('包管理器 (pnpm/npm/yarn)', 'pnpm');
113
+ return { name, prefix, i18n, elementPlus: ep, pm };
114
+ }
115
+
116
+ async function collectUiLibConfigSafe(flags) {
117
+ if (isInteractive()) return collectUiLibConfig(flags);
118
+ return {
119
+ name: flags.name || 'my-ui-lib',
120
+ prefix: flags.prefix || 'My',
121
+ i18n: flags.i18n ?? true,
122
+ elementPlus: flags.elementPlus ?? true,
123
+ pm: flags.pm || 'pnpm',
124
+ };
125
+ }
126
+
127
+ async function pickProjectType() {
128
+ const templates = listTemplates();
129
+ console.log('\n 选择项目类型:');
130
+ templates.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
131
+ const idx = await ask('输入序号', '1');
132
+ const i = parseInt(idx, 10) - 1;
133
+ if (i >= 0 && i < templates.length) return templates[i];
134
+ return templates[0];
135
+ }
136
+
64
137
  function countDirs(dir) {
65
138
  if (!existsSync(dir)) return 0;
66
139
  return readdirSync(dir, { withFileTypes: true }).filter(e => e.isDirectory()).length;
@@ -330,6 +403,160 @@ function copyTemplateSync(templateName, destDir) {
330
403
  copyDirSync(src, destDir);
331
404
  }
332
405
 
406
+ const { extname } = require('path');
407
+
408
+ const TEXT_EXTS = new Set([
409
+ '.json', '.ts', '.tsx', '.vue', '.md', '.scss', '.css',
410
+ '.yaml', '.yml', '.js', '.cjs', '.mjs', '.html',
411
+ ]);
412
+
413
+ function isTextFile(filePath) {
414
+ return TEXT_EXTS.has(extname(filePath).toLowerCase());
415
+ }
416
+
417
+ function buildPlaceholders(config) {
418
+ return {
419
+ __PACKAGE_NAME__: config.name,
420
+ __COMPONENT_PREFIX__: config.prefix,
421
+ __SCOPE_NAME__: config.name.replace(/^@/, '').split('/').pop(),
422
+ };
423
+ }
424
+
425
+ function renderContent(content, config) {
426
+ const placeholders = buildPlaceholders(config);
427
+ let out = content;
428
+ for (const [k, v] of Object.entries(placeholders)) {
429
+ out = out.split(k).join(v);
430
+ }
431
+ return out;
432
+ }
433
+
434
+ // 复制 ui-lib 模板并对文本文件做占位符替换
435
+ function copyTemplateWithRender(templateName, destDir, config) {
436
+ const src = resolve(TEMPLATES_SRC, templateName);
437
+ if (!existsSync(src)) throw new Error(`模板不存在: ${templateName}`);
438
+ copyDirSyncWithRender(src, destDir, config);
439
+ }
440
+
441
+ function copyDirSyncWithRender(src, dest, config) {
442
+ let realSrc = src;
443
+ try { realSrc = realpathSync(src); } catch {}
444
+ mkdirSync(dest, { recursive: true });
445
+ for (const entry of readdirSync(realSrc, { withFileTypes: true })) {
446
+ if (SKIP_ENTRIES.has(entry.name)) continue;
447
+ const srcPath = join(realSrc, entry.name);
448
+ const destPath = join(dest, entry.name);
449
+ let stat;
450
+ try { stat = lstatSync(srcPath); } catch { continue; }
451
+ if (stat.isSymbolicLink()) {
452
+ try {
453
+ const real = realpathSync(srcPath);
454
+ const realStat = lstatSync(real);
455
+ if (realStat.isDirectory()) copyDirSyncWithRender(real, destPath, config);
456
+ else copyFileWithRender(real, destPath, config);
457
+ } catch {}
458
+ } else if (stat.isDirectory()) {
459
+ copyDirSyncWithRender(srcPath, destPath, config);
460
+ } else if (stat.isFile()) {
461
+ copyFileWithRender(srcPath, destPath, config);
462
+ }
463
+ }
464
+ }
465
+
466
+ function copyFileWithRender(srcPath, destPath, config) {
467
+ if (isTextFile(srcPath)) {
468
+ const content = readFileSync(srcPath, 'utf8');
469
+ writeFileSync(destPath, renderContent(content, config), 'utf8');
470
+ } else {
471
+ copyFileSync(srcPath, destPath);
472
+ }
473
+ }
474
+
475
+ function applyConditional(srcDir, config) {
476
+ if (!config.i18n) {
477
+ removeSync(resolve(srcDir, 'src/locales'));
478
+ removeSync(resolve(srcDir, 'src/composables/use-locale'));
479
+ removeSync(resolve(srcDir, 'src/vue-i18n.ts'));
480
+ cleanupI18nRefs(srcDir);
481
+ }
482
+ if (!config.elementPlus) {
483
+ cleanupElementPlusRefs(srcDir);
484
+ }
485
+ }
486
+
487
+ // 清理 i18n 引用:从 index.ts/install.ts 移除 i18n 相关 import 与调用
488
+ function cleanupI18nRefs(srcDir) {
489
+ const indexPath = resolve(srcDir, 'src/index.ts');
490
+ if (existsSync(indexPath)) {
491
+ let content = readFileSync(indexPath, 'utf8');
492
+ content = content
493
+ .replace(/import\s+\{[^}]*useLocale[^}]*\}\s+from\s+'[^']*composables\/use-locale';?\n?/g, '')
494
+ .replace(/import\s+\{[^}]*provideLocale[^}]*\}\s+from[^;]*;\n?/g, '')
495
+ .replace(/import\s+\{[^}]*createTranslate[^}]*\}\s+from[^;]*;\n?/g, '')
496
+ .replace(/import\s+\{[^}]*useVueI18nLocale[^}]*\}\s+from\s+'[^']*vue-i18n';?\n?/g, '')
497
+ .replace(/import\s+\{\s*messages\s*\}\s+from\s+'[^']*locales';?\n?/g, '')
498
+ .replace(/\/\/ 组合式函数[\s\S]*?(?=\n\/\/ )/g, '')
499
+ .replace(/\/\/ 词条[\s\S]*?(?=\n\/\/ )/g, '')
500
+ .replace(/export\s+\{[^}]*useLocale[^}]*\};\n?/g, '')
501
+ .replace(/export\s+\{[^}]*messages[^}]*\};\n?/g, '')
502
+ .replace(/export\s+type\s+\{[^}]*Locale[^}]*\}\s+from\s+'[^']*locales';?\n?/g, '')
503
+ .replace(/export\s+type\s+\{[^}]*LocaleContext[^}]*\}\s+from[^;]*;\n?/g, '')
504
+ .replace(/provideLocale\([^)]*\);?\n?/g, '');
505
+ writeFileSync(indexPath, content, 'utf8');
506
+ }
507
+ const installPath = resolve(srcDir, 'src/install.ts');
508
+ if (existsSync(installPath)) {
509
+ let content = readFileSync(installPath, 'utf8');
510
+ content = content
511
+ // 兼容假设形态:import { provideLocale, messages } from ...; provideLocale(...)
512
+ .replace(/import\s+\{[^}]*provideLocale[^}]*messages[^}]*\}\s+from[^;]*;\n?/g, '')
513
+ .replace(/provideLocale\([^)]*\);?\n?/g, '')
514
+ // 实际模板:use-locale / locales / localeKey / createTranslate / Locale
515
+ .replace(/import\s+\{[^}]*\}\s+from\s+'[^']*composables\/use-locale';?\n?/g, '')
516
+ .replace(/import\s+type\s+\{\s*Locale[^}]*\}\s+from\s+'[^']*locales';?\n?/g, '')
517
+ .replace(/^\s*locale\?\s*:\s*Locale;?\s*\n/mg, '')
518
+ .replace(/const\s+locale\s*=\s*options\.locale[^;]*;?\n?/g, '')
519
+ .replace(/app\.provide\([\s\S]*?\}\);?\n?/g, '');
520
+ writeFileSync(installPath, content, 'utf8');
521
+ }
522
+ // 清理组件 vue 中的 useLocale 引用
523
+ for (const comp of ['button', 'card']) {
524
+ const vuePath = resolve(srcDir, `src/${comp}/index.vue`);
525
+ if (existsSync(vuePath)) {
526
+ let content = readFileSync(vuePath, 'utf8');
527
+ content = content
528
+ .replace(/import\s+\{\s*useLocale\s*\}\s+from\s+'[^']*composables\/use-locale';?\n?/g, '')
529
+ .replace(/const\s+\{\s*t\s*\}\s*=\s*useLocale\(\);?\n?/g, '')
530
+ .replace(/\{\{\s*t\([^)]*\)\s*\}\}/g, '');
531
+ writeFileSync(vuePath, content, 'utf8');
532
+ }
533
+ }
534
+ }
535
+
536
+ // 清理 Element Plus 引用:peerDependencies 移除、external 移除、示例改原生 button
537
+ function cleanupElementPlusRefs(srcDir) {
538
+ const pkgPath = resolve(srcDir, 'package.json');
539
+ if (existsSync(pkgPath)) {
540
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
541
+ if (pkg.peerDependencies) delete pkg.peerDependencies['element-plus'];
542
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
543
+ }
544
+ const vitePath = resolve(srcDir, 'vite.config.ts');
545
+ if (existsSync(vitePath)) {
546
+ let content = readFileSync(vitePath, 'utf8');
547
+ content = content.replace(/'element-plus',?\s*/g, '');
548
+ writeFileSync(vitePath, content, 'utf8');
549
+ }
550
+ // 示例组件改用原生 button
551
+ const btnVue = resolve(srcDir, 'src/button/index.vue');
552
+ if (existsSync(btnVue)) {
553
+ let content = readFileSync(btnVue, 'utf8');
554
+ content = content
555
+ .replace(/<el-button[\s\S]*?>([\s\S]*?)<\/el-button>/g, '<button :class="$style.button" :disabled="props.disabled" @click="emit(\'click\', $event)">$1</button>');
556
+ writeFileSync(btnVue, content, 'utf8');
557
+ }
558
+ }
559
+
333
560
  function mergeFileIfExists(filePath, content) {
334
561
  if (existsSync(filePath)) {
335
562
  const existing = readFileSync(filePath, 'utf8');
@@ -341,8 +568,8 @@ function mergeFileIfExists(filePath, content) {
341
568
  return true;
342
569
  }
343
570
 
344
- function init(templateName, withSkills) {
345
- console.log(`\n ${PKG.name} v${PKG.version} — 初始化 ${templateName} 模板\n`);
571
+ async function init(templateName, withSkills, flags) {
572
+ console.log(`\n ${PKG.name} v${PKG.version} — 初始化\n`);
346
573
 
347
574
  if (!existsSync(TEMPLATES_SRC)) {
348
575
  console.error(' ❌ 错误:templates 源目录不存在。');
@@ -355,9 +582,14 @@ function init(templateName, withSkills) {
355
582
  process.exit(1);
356
583
  }
357
584
 
585
+ // 无参数时交互式选择项目类型
358
586
  if (!templateName) {
359
- console.error(` ❌ --init 需要指定模板名。可用模板: ${templates.join(', ')}\n`);
360
- process.exit(1);
587
+ if (isInteractive()) {
588
+ templateName = await pickProjectType();
589
+ } else {
590
+ console.error(` ❌ --init 需要指定模板名。可用模板: ${templates.join(', ')}\n`);
591
+ process.exit(1);
592
+ }
361
593
  }
362
594
 
363
595
  if (!templates.includes(templateName)) {
@@ -377,8 +609,42 @@ function init(templateName, withSkills) {
377
609
  process.exit(1);
378
610
  }
379
611
 
380
- copyTemplateSync(templateName, PROJECT_DIR);
381
- console.log(` ✅ ${templateName} 模板已初始化到: ${PROJECT_DIR}\n`);
612
+ // 目标非空且含 package.json 时报错(ui-lib 等需空目录)
613
+ const targetPkg = resolve(PROJECT_DIR, 'package.json');
614
+ if (templateName === 'ui-lib' && existsSync(targetPkg)) {
615
+ console.error(' ❌ 目标目录已存在 package.json,请换空目录初始化 ui-lib。\n');
616
+ process.exit(1);
617
+ }
618
+
619
+ if (templateName === 'ui-lib') {
620
+ const config = await collectUiLibConfigSafe(flags || {});
621
+ copyTemplateWithRender('ui-lib', PROJECT_DIR, config);
622
+ applyConditional(PROJECT_DIR, config);
623
+ console.log(` ✅ ui-lib 模板已初始化到: ${PROJECT_DIR}\n`);
624
+ console.log(` 包名: ${config.name}`);
625
+ console.log(` 组件前缀: ${config.prefix}(生成 ${config.prefix}Button / ${config.prefix}Card)`);
626
+ console.log(` i18n: ${config.i18n ? '启用' : '未启用'}`);
627
+ console.log(` Element Plus: ${config.elementPlus ? '启用' : '未启用'}\n`);
628
+ console.log(' 下一步:');
629
+ console.log(` cd ${PROJECT_DIR}`);
630
+ console.log(` ${config.pm} install`);
631
+ console.log(` ${config.pm} dev # 启动 playground 调试`);
632
+ console.log(` ${config.pm} build # 构建组件库`);
633
+ console.log(` ${config.pm} lint # 代码检查\n`);
634
+ } else if (templateName === 'pandax-monorepo') {
635
+ const wsConfig = await collectMonorepoConfigSafe(flags || {});
636
+ copyTemplateSync('pandax-monorepo', PROJECT_DIR);
637
+ applyMonorepoPlaceholders(PROJECT_DIR, wsConfig);
638
+ console.log(` ✅ pandax-monorepo 模板已初始化到: ${PROJECT_DIR}\n`);
639
+ if (wsConfig.includeComponents) {
640
+ console.log(` 组件库包名: ${wsConfig.componentPkg}`);
641
+ console.log(` 组件前缀: ${wsConfig.prefix}`);
642
+ }
643
+ console.log(`\n 下一步: cd ${PROJECT_DIR} && pnpm install && pnpm dev\n`);
644
+ } else {
645
+ copyTemplateSync(templateName, PROJECT_DIR);
646
+ console.log(` ✅ ${templateName} 模板已初始化到: ${PROJECT_DIR}\n`);
647
+ }
382
648
 
383
649
  if (withSkills) {
384
650
  console.log(` 继续安装 skills...\n`);
@@ -389,6 +655,35 @@ function init(templateName, withSkills) {
389
655
  }
390
656
  }
391
657
 
658
+ async function collectMonorepoConfigSafe(flags) {
659
+ const projectName = flags.name || (isInteractive() ? await ask('monorepo 项目名', 'pandax-monorepo') : 'pandax-monorepo');
660
+ const includeComponents = flags.monorepoComponents ?? (isInteractive() ? await askBool('是否包含组件库 package?', true) : true);
661
+ const componentPkg = includeComponents
662
+ ? (flags.componentPkg || (isInteractive() ? await ask('组件库包名', 'pandax-components') : 'pandax-components'))
663
+ : 'pandax-components';
664
+ const prefix = flags.prefix || (isInteractive() ? await ask('组件前缀 (PascalCase)', 'Panda') : 'Panda');
665
+ return { projectName, includeComponents, componentPkg, prefix };
666
+ }
667
+
668
+ function applyMonorepoPlaceholders(destDir, config) {
669
+ // 替换根 package.json name
670
+ const rootPkg = resolve(destDir, 'package.json');
671
+ if (existsSync(rootPkg)) {
672
+ const pkg = JSON.parse(readFileSync(rootPkg, 'utf8'));
673
+ pkg.name = config.projectName;
674
+ writeFileSync(rootPkg, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
675
+ }
676
+ // 替换组件库 package name 与组件前缀
677
+ if (config.includeComponents) {
678
+ const compPkg = resolve(destDir, 'packages/components/package.json');
679
+ if (existsSync(compPkg)) {
680
+ const pkg = JSON.parse(readFileSync(compPkg, 'utf8'));
681
+ pkg.name = config.componentPkg;
682
+ writeFileSync(compPkg, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
683
+ }
684
+ }
685
+ }
686
+
392
687
  function showHelp() {
393
688
  console.log(`
394
689
  ${PKG.name} v${PKG.version} — Pandax 全栈 AI 编程技能魔盒
@@ -396,12 +691,23 @@ function showHelp() {
396
691
  用法:
397
692
  npx ${PKG.name} 项目级:自动检测工具并安装 skills
398
693
  npx ${PKG.name} --tool trae 指定工具安装 skills
399
- npx ${PKG.name} --init vue3 初始化 Vue3 项目模板
400
- npx ${PKG.name} --init vue3 --with-skills 初始化 Vue3 模板并安装 skills
694
+ npx ${PKG.name} --init 交互式选择项目模板
695
+ npx ${PKG.name} --init ui-lib 初始化独立组件库(交互式配置)
696
+ npx ${PKG.name} --init ui-lib --name my-ui --prefix A --no-i18n
697
+ npx ${PKG.name} --init pandax-monorepo 初始化 Monorepo + 微前端模板
698
+ npx ${PKG.name} --init vue3 初始化 Vue3 单应用模板
699
+ npx ${PKG.name} --init vue3 --with-skills 初始化模板并安装 skills
401
700
  npx ${PKG.name} --uninstall 卸载当前项目 skills
402
701
  npx ${PKG.name} --help 显示帮助
403
702
  npx ${PKG.name} --version 显示版本
404
703
 
704
+ ui-lib 参数:
705
+ --name <pkg> npm 包名(小写,^[a-z][a-z0-9-]*$)
706
+ --prefix <Pascal> 组件前缀(PascalCase)
707
+ --i18n / --no-i18n 启用/关闭 i18n(默认启用)
708
+ --element-plus / --no-element-plus 启用/关闭 Element Plus(默认启用)
709
+ --pm <pnpm|npm|yarn> 包管理器(默认 pnpm)
710
+
405
711
  支持的工具名:
406
712
  ${Object.keys(TOOL_ALIASES).join(', ')}
407
713
 
@@ -550,43 +856,72 @@ const initIdx = args.findIndex(a => a === '--init' || a === '-i');
550
856
  const uninstallIdx = args.findIndex(a => a === '--uninstall' || a === '-u');
551
857
  const forceIdx = args.findIndex(a => a === '--force' || a === '-f');
552
858
  const withSkillsIdx = args.findIndex(a => a === '--with-skills');
859
+
860
+ // ui-lib 专用参数
861
+ const nameIdx = args.findIndex(a => a === '--name');
862
+ const prefixIdx = args.findIndex(a => a === '--prefix');
863
+ const pmIdx = args.findIndex(a => a === '--pm');
864
+ const hasNoI18n = args.includes('--no-i18n');
865
+ const hasI18n = args.includes('--i18n');
866
+ const hasNoEp = args.includes('--no-element-plus');
867
+ const hasEp = args.includes('--element-plus');
868
+
553
869
  const force = forceIdx !== -1;
554
870
  const withSkills = withSkillsIdx !== -1;
555
871
 
556
872
  const KNOWN_FLAGS = new Set([
557
873
  '--help', '-h', '--version', '-v', '--uninstall', '-u', '--force', '-f',
558
- '--tool', '-t', '--init', '-i', '--with-skills'
874
+ '--tool', '-t', '--init', '-i', '--with-skills',
875
+ '--name', '--prefix', '--pm', '--no-i18n', '--i18n',
876
+ '--no-element-plus', '--element-plus',
559
877
  ]);
560
878
 
561
- if (helpIdx !== -1) {
562
- showHelp();
563
- } else if (versionIdx !== -1) {
564
- console.log(PKG.version);
565
- } else if (uninstallIdx !== -1) {
566
- uninstall();
567
- } else if (initIdx !== -1) {
568
- const templateArg = args[initIdx + 1];
569
- init(templateArg, withSkills);
570
- } else if (toolIdx !== -1) {
571
- const toolArg = args[toolIdx + 1];
572
- if (!toolArg) {
573
- console.error(' ❌ --tool 需要指定工具名,例如: --tool trae\n');
574
- showHelp();
575
- process.exit(1);
576
- }
577
- const toolName = TOOL_ALIASES[toolArg.toLowerCase()];
578
- if (!toolName) {
579
- console.error(` ❌ 未知工具: ${toolArg}`);
580
- console.error(` 支持的工具: ${Object.keys(TOOL_ALIASES).join(', ')}\n`);
581
- process.exit(1);
582
- }
583
- install(toolName, force);
584
- } else {
585
- const unknown = args.find(a => a.startsWith('-') && !KNOWN_FLAGS.has(a));
586
- if (unknown) {
587
- console.warn(` 未知参数: ${unknown}\n`);
879
+ function parseFlags() {
880
+ const flags = {};
881
+ if (nameIdx !== -1) flags.name = args[nameIdx + 1];
882
+ if (prefixIdx !== -1) flags.prefix = args[prefixIdx + 1];
883
+ if (pmIdx !== -1) flags.pm = args[pmIdx + 1];
884
+ if (hasI18n) flags.i18n = true;
885
+ if (hasNoI18n) flags.i18n = false;
886
+ if (hasEp) flags.elementPlus = true;
887
+ if (hasNoEp) flags.elementPlus = false;
888
+ return flags;
889
+ }
890
+
891
+ async function main() {
892
+ if (helpIdx !== -1) {
588
893
  showHelp();
589
- process.exit(1);
894
+ } else if (versionIdx !== -1) {
895
+ console.log(PKG.version);
896
+ } else if (uninstallIdx !== -1) {
897
+ uninstall();
898
+ } else if (initIdx !== -1) {
899
+ const templateArg = args[initIdx + 1];
900
+ const flags = parseFlags();
901
+ await init(templateArg, withSkills, flags);
902
+ } else if (toolIdx !== -1) {
903
+ const toolArg = args[toolIdx + 1];
904
+ if (!toolArg) {
905
+ console.error(' ❌ --tool 需要指定工具名,例如: --tool trae\n');
906
+ showHelp();
907
+ process.exit(1);
908
+ }
909
+ const toolName = TOOL_ALIASES[toolArg.toLowerCase()];
910
+ if (!toolName) {
911
+ console.error(` ❌ 未知工具: ${toolArg}`);
912
+ console.error(` 支持的工具: ${Object.keys(TOOL_ALIASES).join(', ')}\n`);
913
+ process.exit(1);
914
+ }
915
+ install(toolName, force);
916
+ } else {
917
+ const unknown = args.find(a => a.startsWith('-') && !KNOWN_FLAGS.has(a));
918
+ if (unknown) {
919
+ console.warn(` 未知参数: ${unknown}\n`);
920
+ showHelp();
921
+ process.exit(1);
922
+ }
923
+ install(undefined, force);
590
924
  }
591
- install(undefined, force);
592
925
  }
926
+
927
+ main();
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "pandax",
3
- "version": "0.0.1",
3
+ "version": "0.0.2-beta.0",
4
4
  "description": "Pandax 全栈 AI 编程技能魔盒,支持通过 npx 安装 skills 到 Trae/Cursor/CodeBuddy 等工具",
5
5
  "bin": {
6
6
  "pandax": "bin/pandax.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node bin/pandax.js --help"
9
+ "test": "node bin/pandax.js --help",
10
+ "verify": "node scripts/verify-init.js"
10
11
  },
11
12
  "keywords": [
12
13
  "pandax",