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.
- package/README.md +15 -9
- package/bin/pandax.js +374 -39
- package/package.json +3 -2
- package/skills/pandax-frontend-architecture/SKILL.md +1432 -1239
- package/templates/pandax-monorepo/apps/base/package.json +5 -2
- package/templates/pandax-monorepo/apps/base/src/views/home/index.vue +5 -0
- package/templates/pandax-monorepo/apps/base/uno.config.ts +2 -0
- package/templates/pandax-monorepo/apps/base/vite.config.ts +4 -2
- package/templates/pandax-monorepo/apps/main/package.json +2 -1
- package/templates/pandax-monorepo/apps/main/uno.config.ts +2 -0
- package/templates/pandax-monorepo/apps/main/vite.config.ts +1 -1
- package/templates/pandax-monorepo/package.json +1 -1
- package/templates/pandax-monorepo/packages/components/package.json +58 -5
- package/templates/pandax-monorepo/packages/components/src/button/Prop.ts +8 -0
- package/templates/pandax-monorepo/packages/components/src/button/Type.ts +4 -0
- package/templates/pandax-monorepo/packages/components/src/button/index.module.scss +3 -0
- package/templates/pandax-monorepo/packages/components/src/button/index.ts +11 -0
- package/templates/pandax-monorepo/packages/components/src/button/index.vue +74 -0
- package/templates/pandax-monorepo/packages/components/src/card/Prop.ts +6 -0
- package/templates/pandax-monorepo/packages/components/src/card/Type.ts +3 -0
- package/templates/pandax-monorepo/packages/components/src/card/index.module.scss +7 -0
- package/templates/pandax-monorepo/packages/components/src/card/index.ts +11 -0
- package/templates/pandax-monorepo/packages/components/src/card/index.vue +78 -0
- package/templates/pandax-monorepo/packages/components/src/composables/use-locale/Type.ts +6 -0
- package/templates/pandax-monorepo/packages/components/src/composables/use-locale/index.ts +35 -0
- package/templates/pandax-monorepo/packages/components/src/index.ts +23 -2
- package/templates/pandax-monorepo/packages/components/src/install.ts +23 -0
- package/templates/pandax-monorepo/packages/components/src/locales/en-US.ts +9 -0
- package/templates/pandax-monorepo/packages/components/src/locales/index.ts +10 -0
- package/templates/pandax-monorepo/packages/components/src/locales/zh-CN.ts +9 -0
- package/templates/pandax-monorepo/packages/components/src/resolver.ts +25 -0
- package/templates/pandax-monorepo/packages/components/src/vue-i18n.ts +15 -0
- package/templates/pandax-monorepo/packages/components/tsconfig.json +18 -0
- package/templates/pandax-monorepo/packages/components/vite.config.ts +32 -0
- package/templates/pandax-monorepo/pnpm-lock.yaml +5376 -0
- package/templates/pandax-monorepo/pnpm-workspace.yaml +3 -0
- package/templates/pandax-monorepo/turbo.json +1 -1
- package/templates/ui-lib/.editorconfig +16 -0
- package/templates/ui-lib/.husky/commit-msg +2 -0
- package/templates/ui-lib/.husky/pre-commit +2 -0
- package/templates/ui-lib/.prettierrc.json +28 -0
- package/templates/ui-lib/README.md +49 -0
- package/templates/ui-lib/commitlint.config.js +38 -0
- package/templates/ui-lib/env.d.ts +7 -0
- package/templates/ui-lib/eslint.config.js +60 -0
- package/templates/ui-lib/package.json +73 -0
- package/templates/ui-lib/playground/App.vue +13 -0
- package/templates/ui-lib/playground/index.html +12 -0
- package/templates/ui-lib/playground/main.ts +11 -0
- package/templates/ui-lib/playground/vite.config.ts +12 -0
- package/templates/ui-lib/src/button/Prop.ts +5 -0
- package/templates/ui-lib/src/button/Type.ts +3 -0
- package/templates/ui-lib/src/button/index.module.scss +3 -0
- package/templates/ui-lib/src/button/index.ts +11 -0
- package/templates/ui-lib/src/button/index.vue +34 -0
- package/templates/ui-lib/src/card/Prop.ts +4 -0
- package/templates/ui-lib/src/card/Type.ts +1 -0
- package/templates/ui-lib/src/card/index.module.scss +3 -0
- package/templates/ui-lib/src/card/index.ts +11 -0
- package/templates/ui-lib/src/card/index.vue +21 -0
- package/templates/ui-lib/src/composables/use-locale/Type.ts +6 -0
- package/templates/ui-lib/src/composables/use-locale/index.ts +35 -0
- package/templates/ui-lib/src/index.ts +22 -0
- package/templates/ui-lib/src/install.ts +23 -0
- package/templates/ui-lib/src/locales/en-US.ts +9 -0
- package/templates/ui-lib/src/locales/index.ts +10 -0
- package/templates/ui-lib/src/locales/zh-CN.ts +9 -0
- package/templates/ui-lib/src/resolver.ts +25 -0
- package/templates/ui-lib/src/vue-i18n.ts +15 -0
- package/templates/ui-lib/tsconfig.json +23 -0
- package/templates/ui-lib/tsconfig.node.json +10 -0
- package/templates/ui-lib/uno.config.ts +5 -0
- package/templates/ui-lib/vite.config.ts +27 -0
- package/templates/pandax-monorepo/packages/components/src/PandaButton.vue +0 -15
- 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
|
|
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
|
|
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} —
|
|
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
|
-
|
|
360
|
-
|
|
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
|
-
|
|
381
|
-
|
|
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
|
|
400
|
-
npx ${PKG.name} --init
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|