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.
- package/README.md +87 -0
- package/bin/index.js +33 -0
- package/lib/commands/create.js +212 -0
- package/lib/commands/init.js +205 -0
- package/lib/commands/update.js +89 -0
- package/lib/utils/file.js +74 -0
- package/lib/utils/logger.js +22 -0
- package/package.json +36 -0
- package/templates/_package.json +32 -0
- package/templates/app/.env.development +2 -0
- package/templates/app/.env.production +2 -0
- package/templates/app/.env.staging +2 -0
- package/templates/app/_package.json +27 -0
- package/templates/app/index.html +14 -0
- package/templates/app/src/App.vue +28 -0
- package/templates/app/src/main.js +14 -0
- package/templates/app/src/router.js +21 -0
- package/templates/app/src/stores.js +20 -0
- package/templates/app/src/views/About.vue +30 -0
- package/templates/app/src/views/Home.vue +54 -0
- package/templates/app/vite.config.js +34 -0
- package/templates/packages/bridge/package.json +12 -0
- package/templates/packages/bridge/src/index.js +96 -0
- package/templates/packages/request/package.json +13 -0
- package/templates/packages/request/src/config.js +5 -0
- package/templates/packages/request/src/index.js +29 -0
- package/templates/packages/request/src/request.js +123 -0
- package/templates/packages/sensors/package.json +15 -0
- package/templates/packages/sensors/src/index.js +19 -0
- package/templates/packages/ui/package.json +15 -0
- package/templates/packages/ui/src/index.js +44 -0
- package/templates/packages/utils/package.json +12 -0
- package/templates/packages/utils/src/index.js +31 -0
- package/templates/packages/vconsole/package.json +14 -0
- package/templates/packages/vconsole/src/index.js +9 -0
- package/templates/pnpm-workspace.yaml +3 -0
- package/templates/postcss.config.js +14 -0
- package/templates/scripts/create-app.js +355 -0
- package/templates/turbo.json +23 -0
- 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
|
+
}
|