create-csch5-monorepo 1.0.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 (40) hide show
  1. package/README.md +87 -0
  2. package/bin/index.js +33 -0
  3. package/lib/commands/create.js +212 -0
  4. package/lib/commands/init.js +205 -0
  5. package/lib/commands/update.js +89 -0
  6. package/lib/utils/file.js +74 -0
  7. package/lib/utils/logger.js +22 -0
  8. package/package.json +36 -0
  9. package/templates/_package.json +32 -0
  10. package/templates/app/.env.development +2 -0
  11. package/templates/app/.env.production +2 -0
  12. package/templates/app/.env.staging +2 -0
  13. package/templates/app/_package.json +27 -0
  14. package/templates/app/index.html +14 -0
  15. package/templates/app/src/App.vue +28 -0
  16. package/templates/app/src/main.js +14 -0
  17. package/templates/app/src/router.js +21 -0
  18. package/templates/app/src/stores.js +20 -0
  19. package/templates/app/src/views/About.vue +30 -0
  20. package/templates/app/src/views/Home.vue +54 -0
  21. package/templates/app/vite.config.js +34 -0
  22. package/templates/packages/bridge/package.json +12 -0
  23. package/templates/packages/bridge/src/index.js +96 -0
  24. package/templates/packages/request/package.json +13 -0
  25. package/templates/packages/request/src/config.js +5 -0
  26. package/templates/packages/request/src/index.js +29 -0
  27. package/templates/packages/request/src/request.js +123 -0
  28. package/templates/packages/sensors/package.json +15 -0
  29. package/templates/packages/sensors/src/index.js +19 -0
  30. package/templates/packages/ui/package.json +15 -0
  31. package/templates/packages/ui/src/index.js +44 -0
  32. package/templates/packages/utils/package.json +12 -0
  33. package/templates/packages/utils/src/index.js +31 -0
  34. package/templates/packages/vconsole/package.json +14 -0
  35. package/templates/packages/vconsole/src/index.js +9 -0
  36. package/templates/pnpm-workspace.yaml +3 -0
  37. package/templates/postcss.config.js +14 -0
  38. package/templates/scripts/create-app.js +355 -0
  39. package/templates/turbo.json +23 -0
  40. package/templates/vite-plugin-root-env.js +31 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # create-my-monorepo
2
+
3
+ 创建移动端 H5 Monorepo 项目的脚手架工具。
4
+
5
+ ## 使用方法
6
+
7
+ ### 创建新项目
8
+
9
+ ```bash
10
+ npx create-my-monorepo my-project
11
+ ```
12
+
13
+ ### 交互式选项
14
+
15
+ - 项目名称
16
+ - 开发服务器端口
17
+ - 是否包含神策埋点
18
+ - 是否包含 VConsole 调试工具
19
+
20
+ ### 在现有目录初始化
21
+
22
+ ```bash
23
+ mkdir my-project && cd my-project
24
+ npx create-my-monorepo init
25
+ ```
26
+
27
+ ### 更新共享包
28
+
29
+ 在项目根目录运行:
30
+
31
+ ```bash
32
+ npx create-my-monorepo update
33
+ ```
34
+
35
+ 这会更新 `packages/`、`scripts/` 和配置文件,但不会影响 `apps/` 下的项目代码。
36
+
37
+ ## 生成的项目结构
38
+
39
+ ```
40
+ my-project/
41
+ ├── apps/
42
+ │ └── my-project/ # 你的子项目
43
+ │ ├── src/
44
+ │ │ ├── views/ # 页面组件
45
+ │ │ ├── router.js # 路由配置
46
+ │ │ ├── stores.js # 状态管理
47
+ │ │ ├── App.vue # 根组件
48
+ │ │ └── main.js # 入口文件
49
+ │ ├── index.html
50
+ │ ├── vite.config.js
51
+ │ └── package.json
52
+ ├── packages/ # 共享包
53
+ │ ├── bridge/ # JSBridge 桥接
54
+ │ ├── request/ # Axios 封装
55
+ │ ├── sensors/ # 神策埋点
56
+ │ ├── ui/ # UI 组件
57
+ │ ├── utils/ # 工具函数
58
+ │ └── vconsole/ # 调试工具
59
+ ├── scripts/ # 脚本
60
+ │ └── create-app.js # 创建新子项目
61
+ ├── vite-plugin-root-env.js # 环境变量插件
62
+ ├── postcss.config.js # PostCSS 配置
63
+ ├── turbo.json # Turborepo 配置
64
+ ├── pnpm-workspace.yaml # workspace 配置
65
+ ├── package.json
66
+ └── .monoreporc # 版本信息
67
+ ```
68
+
69
+ ## 下一步
70
+
71
+ ```bash
72
+ cd my-project
73
+ pnpm install
74
+ pnpm --filter my-project dev
75
+ ```
76
+
77
+ ## 技术栈
78
+
79
+ - Vue 3.5 + Vue Router 5 + Pinia 3
80
+ - Vite 6
81
+ - Turborepo 2.3
82
+ - pnpm 9.15
83
+ - 移动端适配(amfe-flexible + postcss-pxtorem)
84
+
85
+ ## License
86
+
87
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { create } from '../lib/commands/create.js';
5
+ import { update } from '../lib/commands/update.js';
6
+ import { init } from '../lib/commands/init.js';
7
+ import pc from 'picocolors';
8
+
9
+ program
10
+ .name('create-csch5-monorepo')
11
+ .description('Create a mobile H5 monorepo project')
12
+ .version('1.0.0');
13
+
14
+ program
15
+ .command('create <project-name>')
16
+ .description('Create a new monorepo project')
17
+ .option('-p, --port <port>', 'Development server port', '3000')
18
+ .option('--no-sensors', 'Skip sensors analytics')
19
+ .option('--no-vconsole', 'Skip vconsole debugger')
20
+ .action(create);
21
+
22
+ program
23
+ .command('update')
24
+ .description('Update shared packages and configurations')
25
+ .option('--check', 'Only check for updates')
26
+ .action(update);
27
+
28
+ program
29
+ .command('init')
30
+ .description('Initialize monorepo in current directory')
31
+ .action(init);
32
+
33
+ program.parse();
@@ -0,0 +1,212 @@
1
+ import prompts from 'prompts';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import ora from 'ora';
5
+ import { logger } from '../utils/logger.js';
6
+ import { getTemplateDir, copyDir, renderTemplate, isDirEmpty } from '../utils/file.js';
7
+
8
+ export async function create(projectName, options) {
9
+ const cwd = process.cwd();
10
+ const targetDir = path.resolve(cwd, projectName);
11
+
12
+ // 检查目录
13
+ if (fs.existsSync(targetDir)) {
14
+ if (!await isDirEmpty(targetDir)) {
15
+ const { overwrite } = await prompts({
16
+ type: 'confirm',
17
+ name: 'overwrite',
18
+ message: `目录 ${projectName} 已存在,是否覆盖?`,
19
+ initial: false
20
+ });
21
+
22
+ if (!overwrite) {
23
+ logger.info('操作已取消');
24
+ return;
25
+ }
26
+
27
+ await fs.emptyDir(targetDir);
28
+ }
29
+ }
30
+
31
+ // 交互式询问
32
+ const questions = [
33
+ {
34
+ type: options.port ? null : 'text',
35
+ name: 'port',
36
+ message: '开发服务器端口',
37
+ initial: '3000'
38
+ },
39
+ {
40
+ type: options.sensors === false ? null : 'confirm',
41
+ name: 'sensors',
42
+ message: '是否包含神策埋点?',
43
+ initial: true
44
+ },
45
+ {
46
+ type: options.vconsole === false ? null : 'confirm',
47
+ name: 'vconsole',
48
+ message: '是否包含 VConsole 调试工具?',
49
+ initial: true
50
+ }
51
+ ].filter(q => q.type !== null);
52
+
53
+ let answers = { port: options.port, sensors: true, vconsole: true };
54
+
55
+ if (questions.length > 0) {
56
+ answers = await prompts(questions);
57
+ if (answers.port === undefined && options.port) answers.port = options.port;
58
+ }
59
+
60
+ const spinner = ora('正在创建项目...').start();
61
+
62
+ try {
63
+ // 创建目录
64
+ await fs.ensureDir(targetDir);
65
+ await fs.ensureDir(path.join(targetDir, 'apps', projectName));
66
+
67
+ // 复制共享包
68
+ spinner.text = '复制共享包...';
69
+ const templateDir = getTemplateDir();
70
+ await copyDir(
71
+ path.join(templateDir, 'packages'),
72
+ path.join(targetDir, 'packages')
73
+ );
74
+
75
+ // 复制 scripts
76
+ spinner.text = '复制脚本...';
77
+ await copyDir(
78
+ path.join(templateDir, 'scripts'),
79
+ path.join(targetDir, 'scripts')
80
+ );
81
+
82
+ // 渲染配置文件
83
+ spinner.text = '生成配置文件...';
84
+ const vars = {
85
+ projectName,
86
+ port: answers.port || options.port || '3000',
87
+ sensors: answers.sensors,
88
+ vconsole: answers.vconsole
89
+ };
90
+
91
+ await renderTemplate(
92
+ path.join(templateDir, '_package.json'),
93
+ path.join(targetDir, 'package.json'),
94
+ vars
95
+ );
96
+
97
+ await renderTemplate(
98
+ path.join(templateDir, 'pnpm-workspace.yaml'),
99
+ path.join(targetDir, 'pnpm-workspace.yaml'),
100
+ vars
101
+ );
102
+
103
+ await fs.copy(
104
+ path.join(templateDir, 'turbo.json'),
105
+ path.join(targetDir, 'turbo.json')
106
+ );
107
+
108
+ await fs.copy(
109
+ path.join(templateDir, 'postcss.config.js'),
110
+ path.join(targetDir, 'postcss.config.js')
111
+ );
112
+
113
+ await fs.copy(
114
+ path.join(templateDir, 'vite-plugin-root-env.js'),
115
+ path.join(targetDir, 'vite-plugin-root-env.js')
116
+ );
117
+
118
+ // 生成子项目
119
+ spinner.text = '生成子项目...';
120
+ await generateApp(targetDir, projectName, vars);
121
+
122
+ // 写入版本信息
123
+ await fs.writeJson(path.join(targetDir, '.monoreporc'), {
124
+ version: '1.0.0',
125
+ createdAt: new Date().toISOString(),
126
+ apps: [projectName]
127
+ }, { spaces: 2 });
128
+
129
+ spinner.succeed('项目创建成功!');
130
+
131
+ // 输出后续步骤
132
+ console.log();
133
+ logger.success(`已创建项目 ${projectName}`);
134
+ console.log();
135
+ logger.dim(' 下一步:');
136
+ console.log();
137
+ logger.dim(` cd ${projectName}`);
138
+ logger.dim(' pnpm install');
139
+ logger.dim(` pnpm --filter ${projectName} dev`);
140
+ console.log();
141
+
142
+ } catch (err) {
143
+ spinner.fail('创建失败');
144
+ logger.error(err.message);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 生成子项目
151
+ */
152
+ async function generateApp(targetDir, projectName, vars) {
153
+ const appDir = path.join(targetDir, 'apps', projectName);
154
+ const templateDir = getTemplateDir();
155
+ const appTemplateDir = path.join(templateDir, 'app');
156
+
157
+ // package.json
158
+ await renderTemplate(
159
+ path.join(appTemplateDir, '_package.json'),
160
+ path.join(appDir, 'package.json'),
161
+ vars
162
+ );
163
+
164
+ // vite.config.js
165
+ await renderTemplate(
166
+ path.join(appTemplateDir, 'vite.config.js'),
167
+ path.join(appDir, 'vite.config.js'),
168
+ vars
169
+ );
170
+
171
+ // index.html
172
+ await renderTemplate(
173
+ path.join(appTemplateDir, 'index.html'),
174
+ path.join(appDir, 'index.html'),
175
+ vars
176
+ );
177
+
178
+ // 环境变量
179
+ await fs.ensureDir(path.join(appDir, 'src'));
180
+ await renderTemplate(
181
+ path.join(appTemplateDir, '.env.development'),
182
+ path.join(appDir, '.env.development'),
183
+ vars
184
+ );
185
+ await renderTemplate(
186
+ path.join(appTemplateDir, '.env.staging'),
187
+ path.join(appDir, '.env.staging'),
188
+ vars
189
+ );
190
+ await renderTemplate(
191
+ path.join(appTemplateDir, '.env.production'),
192
+ path.join(appDir, '.env.production'),
193
+ vars
194
+ );
195
+
196
+ // src 目录
197
+ const srcFiles = ['main.js', 'App.vue', 'router.js', 'stores.js'];
198
+ for (const file of srcFiles) {
199
+ await renderTemplate(
200
+ path.join(appTemplateDir, 'src', file),
201
+ path.join(appDir, 'src', file),
202
+ vars
203
+ );
204
+ }
205
+
206
+ // views
207
+ await fs.ensureDir(path.join(appDir, 'src', 'views'));
208
+ await fs.copy(
209
+ path.join(appTemplateDir, 'src', 'views'),
210
+ path.join(appDir, 'src', 'views')
211
+ );
212
+ }
@@ -0,0 +1,205 @@
1
+ import prompts from 'prompts';
2
+ import path from 'path';
3
+ import fs from 'fs-extra';
4
+ import ora from 'ora';
5
+ import { logger } from '../utils/logger.js';
6
+ import { getTemplateDir, copyDir, renderTemplate, isMonorepoRoot, isDirEmpty } from '../utils/file.js';
7
+
8
+ export async function init() {
9
+ const cwd = process.cwd();
10
+
11
+ // 检查是否已经是 monorepo
12
+ if (isMonorepoRoot(cwd)) {
13
+ logger.error('当前目录已是 monorepo 项目');
14
+ process.exit(1);
15
+ }
16
+
17
+ // 检查目录是否为空
18
+ if (!await isDirEmpty(cwd)) {
19
+ const { confirm } = await prompts({
20
+ type: 'confirm',
21
+ name: 'confirm',
22
+ message: '当前目录不为空,是否继续初始化?',
23
+ initial: false
24
+ });
25
+
26
+ if (!confirm) {
27
+ logger.info('操作已取消');
28
+ return;
29
+ }
30
+ }
31
+
32
+ // 询问项目名
33
+ const { projectName } = await prompts({
34
+ type: 'text',
35
+ name: 'projectName',
36
+ message: '第一个子项目名称',
37
+ initial: path.basename(cwd)
38
+ });
39
+
40
+ if (!projectName) {
41
+ logger.info('操作已取消');
42
+ return;
43
+ }
44
+
45
+ // 其他询问
46
+ const answers = await prompts([
47
+ {
48
+ type: 'text',
49
+ name: 'port',
50
+ message: '开发服务器端口',
51
+ initial: '3000'
52
+ },
53
+ {
54
+ type: 'confirm',
55
+ name: 'sensors',
56
+ message: '是否包含神策埋点?',
57
+ initial: true
58
+ },
59
+ {
60
+ type: 'confirm',
61
+ name: 'vconsole',
62
+ message: '是否包含 VConsole 调试工具?',
63
+ initial: true
64
+ }
65
+ ]);
66
+
67
+ const spinner = ora('正在初始化...').start();
68
+
69
+ try {
70
+ const templateDir = getTemplateDir();
71
+ const vars = {
72
+ projectName,
73
+ port: answers.port,
74
+ sensors: answers.sensors,
75
+ vconsole: answers.vconsole
76
+ };
77
+
78
+ // 复制 packages
79
+ spinner.text = '复制共享包...';
80
+ await copyDir(
81
+ path.join(templateDir, 'packages'),
82
+ path.join(cwd, 'packages')
83
+ );
84
+
85
+ // 复制 scripts
86
+ spinner.text = '复制脚本...';
87
+ await copyDir(
88
+ path.join(templateDir, 'scripts'),
89
+ path.join(cwd, 'scripts')
90
+ );
91
+
92
+ // 渲染配置文件
93
+ spinner.text = '生成配置文件...';
94
+ await renderTemplate(
95
+ path.join(templateDir, '_package.json'),
96
+ path.join(cwd, 'package.json'),
97
+ vars
98
+ );
99
+
100
+ await renderTemplate(
101
+ path.join(templateDir, 'pnpm-workspace.yaml'),
102
+ path.join(cwd, 'pnpm-workspace.yaml'),
103
+ vars
104
+ );
105
+
106
+ await fs.copy(
107
+ path.join(templateDir, 'turbo.json'),
108
+ path.join(cwd, 'turbo.json')
109
+ );
110
+
111
+ await fs.copy(
112
+ path.join(templateDir, 'postcss.config.js'),
113
+ path.join(cwd, 'postcss.config.js')
114
+ );
115
+
116
+ await fs.copy(
117
+ path.join(templateDir, 'vite-plugin-root-env.js'),
118
+ path.join(cwd, 'vite-plugin-root-env.js')
119
+ );
120
+
121
+ // 生成子项目
122
+ spinner.text = '生成子项目...';
123
+ await fs.ensureDir(path.join(cwd, 'apps', projectName));
124
+ await generateApp(cwd, projectName, vars);
125
+
126
+ // 写入版本信息
127
+ await fs.writeJson(path.join(cwd, '.monoreporc'), {
128
+ version: '1.0.0',
129
+ createdAt: new Date().toISOString(),
130
+ apps: [projectName]
131
+ }, { spaces: 2 });
132
+
133
+ spinner.succeed('初始化完成!');
134
+
135
+ console.log();
136
+ logger.success('Monorepo 项目初始化成功');
137
+ console.log();
138
+ logger.dim(' 下一步:');
139
+ console.log();
140
+ logger.dim(' pnpm install');
141
+ logger.dim(` pnpm --filter ${projectName} dev`);
142
+ console.log();
143
+
144
+ } catch (err) {
145
+ spinner.fail('初始化失败');
146
+ logger.error(err.message);
147
+ process.exit(1);
148
+ }
149
+ }
150
+
151
+ async function generateApp(targetDir, projectName, vars) {
152
+ const appDir = path.join(targetDir, 'apps', projectName);
153
+ const templateDir = getTemplateDir();
154
+ const appTemplateDir = path.join(templateDir, 'app');
155
+
156
+ await renderTemplate(
157
+ path.join(appTemplateDir, '_package.json'),
158
+ path.join(appDir, 'package.json'),
159
+ vars
160
+ );
161
+
162
+ await renderTemplate(
163
+ path.join(appTemplateDir, 'vite.config.js'),
164
+ path.join(appDir, 'vite.config.js'),
165
+ vars
166
+ );
167
+
168
+ await renderTemplate(
169
+ path.join(appTemplateDir, 'index.html'),
170
+ path.join(appDir, 'index.html'),
171
+ vars
172
+ );
173
+
174
+ await fs.ensureDir(path.join(appDir, 'src'));
175
+ await renderTemplate(
176
+ path.join(appTemplateDir, '.env.development'),
177
+ path.join(appDir, '.env.development'),
178
+ vars
179
+ );
180
+ await renderTemplate(
181
+ path.join(appTemplateDir, '.env.staging'),
182
+ path.join(appDir, '.env.staging'),
183
+ vars
184
+ );
185
+ await renderTemplate(
186
+ path.join(appTemplateDir, '.env.production'),
187
+ path.join(appDir, '.env.production'),
188
+ vars
189
+ );
190
+
191
+ const srcFiles = ['main.js', 'App.vue', 'router.js', 'stores.js'];
192
+ for (const file of srcFiles) {
193
+ await renderTemplate(
194
+ path.join(appTemplateDir, 'src', file),
195
+ path.join(appDir, 'src', file),
196
+ vars
197
+ );
198
+ }
199
+
200
+ await fs.ensureDir(path.join(appDir, 'src', 'views'));
201
+ await fs.copy(
202
+ path.join(appTemplateDir, 'src', 'views'),
203
+ path.join(appDir, 'src', 'views')
204
+ );
205
+ }
@@ -0,0 +1,89 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ora from 'ora';
4
+ import { logger } from '../utils/logger.js';
5
+ import { getTemplateDir, copyDir, isMonorepoRoot } from '../utils/file.js';
6
+
7
+ const CURRENT_VERSION = '1.0.0';
8
+
9
+ export async function update(options) {
10
+ const cwd = process.cwd();
11
+
12
+ // 检查是否在 monorepo 根目录
13
+ if (!isMonorepoRoot(cwd)) {
14
+ logger.error('请在 monorepo 项目根目录执行此命令');
15
+ process.exit(1);
16
+ }
17
+
18
+ // 读取版本信息
19
+ const rcPath = path.join(cwd, '.monoreporc');
20
+ let rc = { version: '0.0.0', apps: [] };
21
+
22
+ if (await fs.exists(rcPath)) {
23
+ rc = await fs.readJson(rcPath);
24
+ }
25
+
26
+ // 检查更新
27
+ if (options.check) {
28
+ if (rc.version === CURRENT_VERSION) {
29
+ logger.success('当前已是最新版本');
30
+ } else {
31
+ logger.info(`发现新版本: ${CURRENT_VERSION} (当前: ${rc.version})`);
32
+ logger.dim('运行 npx create-my-monorepo update 来更新');
33
+ }
34
+ return;
35
+ }
36
+
37
+ const spinner = ora('正在更新共享包...').start();
38
+
39
+ try {
40
+ const templateDir = getTemplateDir();
41
+
42
+ // 更新 packages
43
+ spinner.text = '更新 packages/...';
44
+ await copyDir(
45
+ path.join(templateDir, 'packages'),
46
+ path.join(cwd, 'packages')
47
+ );
48
+
49
+ // 更新 scripts
50
+ spinner.text = '更新 scripts/...';
51
+ await copyDir(
52
+ path.join(templateDir, 'scripts'),
53
+ path.join(cwd, 'scripts')
54
+ );
55
+
56
+ // 更新配置文件
57
+ spinner.text = '更新配置文件...';
58
+ await fs.copy(
59
+ path.join(templateDir, 'vite-plugin-root-env.js'),
60
+ path.join(cwd, 'vite-plugin-root-env.js')
61
+ );
62
+
63
+ await fs.copy(
64
+ path.join(templateDir, 'postcss.config.js'),
65
+ path.join(cwd, 'postcss.config.js')
66
+ );
67
+
68
+ await fs.copy(
69
+ path.join(templateDir, 'turbo.json'),
70
+ path.join(cwd, 'turbo.json')
71
+ );
72
+
73
+ // 更新版本信息
74
+ rc.version = CURRENT_VERSION;
75
+ rc.updatedAt = new Date().toISOString();
76
+ await fs.writeJson(rcPath, rc, { spaces: 2 });
77
+
78
+ spinner.succeed('更新完成!');
79
+
80
+ console.log();
81
+ logger.success(`已更新到版本 ${CURRENT_VERSION}`);
82
+ logger.dim('请运行 pnpm install 安装可能新增的依赖');
83
+
84
+ } catch (err) {
85
+ spinner.fail('更新失败');
86
+ logger.error(err.message);
87
+ process.exit(1);
88
+ }
89
+ }
@@ -0,0 +1,74 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ /**
9
+ * 获取模板目录路径
10
+ */
11
+ export function getTemplateDir() {
12
+ return path.resolve(__dirname, '../../templates');
13
+ }
14
+
15
+ /**
16
+ * 检查目录是否为空
17
+ */
18
+ export function isDirEmpty(dir) {
19
+ if (!fs.existsSync(dir)) return true;
20
+ const files = fs.readdirSync(dir);
21
+ return files.length === 0;
22
+ }
23
+
24
+ /**
25
+ * 复制目录,支持过滤
26
+ */
27
+ export async function copyDir(src, dest, options = {}) {
28
+ const { exclude = [] } = options;
29
+
30
+ await fs.ensureDir(dest);
31
+ const entries = await fs.readdir(src, { withFileTypes: true });
32
+
33
+ for (const entry of entries) {
34
+ const srcPath = path.join(src, entry.name);
35
+ const destPath = path.join(dest, entry.name);
36
+
37
+ // 检查是否在排除列表中
38
+ if (exclude.some(pattern => entry.name.match(pattern))) {
39
+ continue;
40
+ }
41
+
42
+ if (entry.isDirectory()) {
43
+ await copyDir(srcPath, destPath, options);
44
+ } else {
45
+ await fs.copy(srcPath, destPath);
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 读取并渲染模板文件
52
+ */
53
+ export async function renderTemplate(src, dest, vars = {}) {
54
+ let content = await fs.readFile(src, 'utf-8');
55
+
56
+ // 替换模板变量 {{varName}}
57
+ for (const [key, value] of Object.entries(vars)) {
58
+ const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
59
+ content = content.replace(regex, value);
60
+ }
61
+
62
+ await fs.ensureDir(path.dirname(dest));
63
+ await fs.writeFile(dest, content);
64
+ }
65
+
66
+ /**
67
+ * 检查是否在 monorepo 项目中
68
+ */
69
+ export function isMonorepoRoot(dir) {
70
+ const pnpmWorkspace = path.join(dir, 'pnpm-workspace.yaml');
71
+ const packageJson = path.join(dir, 'package.json');
72
+
73
+ return fs.existsSync(pnpmWorkspace) && fs.existsSync(packageJson);
74
+ }