generator-mico-cli 0.2.31 → 0.2.32

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 (162) hide show
  1. package/README.md +145 -18
  2. package/bin/mico.js +76 -0
  3. package/generators/h5-react/ignore-list.json +1 -0
  4. package/generators/h5-react/index.js +349 -0
  5. package/generators/h5-react/meta.json +11 -0
  6. package/generators/h5-react/templates/.commitlintrc.js +7 -0
  7. package/generators/h5-react/templates/.cursor/rules/cicd-deploy.mdc +104 -0
  8. package/generators/h5-react/templates/.cursor/rules/common-intl.mdc +42 -0
  9. package/generators/h5-react/templates/.cursor/rules/git-hooks.mdc +40 -0
  10. package/generators/h5-react/templates/.cursor/rules/internal-packages.mdc +46 -0
  11. package/generators/h5-react/templates/.cursor/rules/monorepo.mdc +64 -0
  12. package/generators/h5-react/templates/.cursor/rules/package-json.mdc +52 -0
  13. package/generators/h5-react/templates/.cursor/rules/tailwind-umi.mdc +60 -0
  14. package/generators/h5-react/templates/.cursor/rules/umi-app.mdc +74 -0
  15. package/generators/h5-react/templates/.cursor/rules/umi-config.mdc +86 -0
  16. package/generators/h5-react/templates/.cursor/rules/umi-mock.mdc +80 -0
  17. package/generators/h5-react/templates/.cursor/rules/workspace-request.mdc +52 -0
  18. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/SKILL.md +213 -0
  19. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/evals/evals.json +23 -0
  20. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/cursor-rule-template.md +60 -0
  21. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-1-scanning.md +102 -0
  22. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-2-context-analysis.md +102 -0
  23. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-3-pattern-extraction.md +105 -0
  24. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-4-module-mapping.md +65 -0
  25. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-5-glossary.md +63 -0
  26. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/DEV_PATTERNS.tpl.md +77 -0
  27. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/GLOSSARY.tpl.md +17 -0
  28. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/MODULE_MAP.tpl.md +45 -0
  29. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/PROJECT_CONTEXT.tpl.md +155 -0
  30. package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/update-mode.md +116 -0
  31. package/generators/h5-react/templates/.env.development +5 -0
  32. package/generators/h5-react/templates/.env.production +5 -0
  33. package/generators/h5-react/templates/.env.testing +5 -0
  34. package/generators/h5-react/templates/.husky/commit-msg +2 -0
  35. package/generators/h5-react/templates/.husky/pre-commit +2 -0
  36. package/generators/h5-react/templates/.lintstagedrc.js +8 -0
  37. package/generators/h5-react/templates/.prettierrc.json +7 -0
  38. package/generators/h5-react/templates/CICD/before_build.sh +76 -0
  39. package/generators/h5-react/templates/CICD/start_dev.sh +54 -0
  40. package/generators/h5-react/templates/CICD/start_local.sh +30 -0
  41. package/generators/h5-react/templates/CICD/start_prod.sh +53 -0
  42. package/generators/h5-react/templates/CICD/start_test.sh +55 -0
  43. package/generators/h5-react/templates/CICD/wangsu_fresh_dev.sh +19 -0
  44. package/generators/h5-react/templates/CICD/wangsu_fresh_prod.sh +19 -0
  45. package/generators/h5-react/templates/CICD/wangsu_fresh_test.sh +19 -0
  46. package/generators/h5-react/templates/README.md +301 -0
  47. package/generators/h5-react/templates/_gitignore +30 -0
  48. package/generators/h5-react/templates/_npmrc +6 -0
  49. package/generators/h5-react/templates/apps/.gitkeep +0 -0
  50. package/generators/h5-react/templates/dev.preset.json +10 -0
  51. package/generators/h5-react/templates/package.json +56 -0
  52. package/generators/h5-react/templates/packages/common-intl/README.md +180 -0
  53. package/generators/h5-react/templates/packages/common-intl/eslint.config.ts +12 -0
  54. package/generators/h5-react/templates/packages/common-intl/package.json +31 -0
  55. package/generators/h5-react/templates/packages/common-intl/src/index.ts +3 -0
  56. package/generators/h5-react/templates/packages/common-intl/src/intl.ts +100 -0
  57. package/generators/h5-react/templates/packages/common-intl/tsconfig.json +3 -0
  58. package/generators/h5-react/templates/packages/components/eslint.config.ts +12 -0
  59. package/generators/h5-react/templates/packages/components/package.json +32 -0
  60. package/generators/h5-react/templates/packages/components/src/Layout/ImmersiveHeader.tsx +126 -0
  61. package/generators/h5-react/templates/packages/components/src/Layout/LayoutFooter.tsx +72 -0
  62. package/generators/h5-react/templates/packages/components/src/Layout/index.tsx +121 -0
  63. package/generators/h5-react/templates/packages/components/src/assets/image/back.png +0 -0
  64. package/generators/h5-react/templates/packages/components/src/index.ts +0 -0
  65. package/generators/h5-react/templates/packages/components/tsconfig.json +13 -0
  66. package/generators/h5-react/templates/packages/components/typings.d.ts +1 -0
  67. package/generators/h5-react/templates/packages/constant/eslint.config.ts +12 -0
  68. package/generators/h5-react/templates/packages/constant/package.json +19 -0
  69. package/generators/h5-react/templates/packages/constant/src/index.ts +0 -0
  70. package/generators/h5-react/templates/packages/constant/src/member.ts +8 -0
  71. package/generators/h5-react/templates/packages/constant/tsconfig.json +3 -0
  72. package/generators/h5-react/templates/packages/deeplink/eslint.config.ts +12 -0
  73. package/generators/h5-react/templates/packages/deeplink/package.json +18 -0
  74. package/generators/h5-react/templates/packages/deeplink/src/index.ts +7 -0
  75. package/generators/h5-react/templates/packages/deeplink/tsconfig.json +3 -0
  76. package/generators/h5-react/templates/packages/domain/eslint.config.ts +12 -0
  77. package/generators/h5-react/templates/packages/domain/package.json +18 -0
  78. package/generators/h5-react/templates/packages/domain/src/index.ts +29 -0
  79. package/generators/h5-react/templates/packages/domain/tsconfig.json +3 -0
  80. package/generators/h5-react/templates/packages/domain/types.d.ts +11 -0
  81. package/generators/h5-react/templates/packages/eslint/eslint.config.base.ts +36 -0
  82. package/generators/h5-react/templates/packages/eslint/eslint.config.react.ts +33 -0
  83. package/generators/h5-react/templates/packages/eslint/package.json +22 -0
  84. package/generators/h5-react/templates/packages/js-bridge/eslint.config.ts +17 -0
  85. package/generators/h5-react/templates/packages/js-bridge/package.json +23 -0
  86. package/generators/h5-react/templates/packages/js-bridge/src/call.ts +126 -0
  87. package/generators/h5-react/templates/packages/js-bridge/src/closeH5Page.ts +9 -0
  88. package/generators/h5-react/templates/packages/js-bridge/src/getUserInfo.ts +96 -0
  89. package/generators/h5-react/templates/packages/js-bridge/src/index.ts +15 -0
  90. package/generators/h5-react/templates/packages/js-bridge/tsconfig.json +3 -0
  91. package/generators/h5-react/templates/packages/js-bridge/type.d.ts +24 -0
  92. package/generators/h5-react/templates/packages/request/axios.d.ts +42 -0
  93. package/generators/h5-react/templates/packages/request/eslint.config.ts +17 -0
  94. package/generators/h5-react/templates/packages/request/package.json +22 -0
  95. package/generators/h5-react/templates/packages/request/src/index.ts +165 -0
  96. package/generators/h5-react/templates/packages/request/src/interceptors.ts +126 -0
  97. package/generators/h5-react/templates/packages/request/src/types.ts +101 -0
  98. package/generators/h5-react/templates/packages/request/src/url-resolver.ts +66 -0
  99. package/generators/h5-react/templates/packages/request/src/utils.ts +12 -0
  100. package/generators/h5-react/templates/packages/request/tsconfig.json +3 -0
  101. package/generators/h5-react/templates/packages/request/umi.d.ts +94 -0
  102. package/generators/h5-react/templates/packages/typescript/package.json +11 -0
  103. package/generators/h5-react/templates/packages/typescript/tsconfig.base.json +23 -0
  104. package/generators/h5-react/templates/packages/typescript/tsconfig.react.json +7 -0
  105. package/generators/h5-react/templates/packages/umi-config/eslint.config.ts +12 -0
  106. package/generators/h5-react/templates/packages/umi-config/package.json +31 -0
  107. package/generators/h5-react/templates/packages/umi-config/src/config.dev.ts +34 -0
  108. package/generators/h5-react/templates/packages/umi-config/src/config.prod.development.ts +17 -0
  109. package/generators/h5-react/templates/packages/umi-config/src/config.prod.production.ts +42 -0
  110. package/generators/h5-react/templates/packages/umi-config/src/config.prod.testing.ts +17 -0
  111. package/generators/h5-react/templates/packages/umi-config/src/config.prod.ts +56 -0
  112. package/generators/h5-react/templates/packages/umi-config/src/config.ts +86 -0
  113. package/generators/h5-react/templates/packages/umi-config/src/index.ts +25 -0
  114. package/generators/h5-react/templates/packages/umi-config/src/plugins/apply-sentry-plugin.ts +57 -0
  115. package/generators/h5-react/templates/packages/umi-config/src/type.d.ts +3 -0
  116. package/generators/h5-react/templates/packages/umi-config/tsconfig.json +3 -0
  117. package/generators/h5-react/templates/packages/utils/eslint.config.ts +12 -0
  118. package/generators/h5-react/templates/packages/utils/package.json +27 -0
  119. package/generators/h5-react/templates/packages/utils/src/date.ts +21 -0
  120. package/generators/h5-react/templates/packages/utils/src/env.ts +40 -0
  121. package/generators/h5-react/templates/packages/utils/src/index.ts +3 -0
  122. package/generators/h5-react/templates/packages/utils/src/md5.ts +17 -0
  123. package/generators/h5-react/templates/packages/utils/src/mock.ts +83 -0
  124. package/generators/h5-react/templates/packages/utils/src/number.ts +23 -0
  125. package/generators/h5-react/templates/packages/utils/src/tailwind.ts +12 -0
  126. package/generators/h5-react/templates/packages/utils/src/url.ts +19 -0
  127. package/generators/h5-react/templates/packages/utils/tsconfig.json +9 -0
  128. package/generators/h5-react/templates/page.config.ts +1 -0
  129. package/generators/h5-react/templates/pnpm-workspace.yaml +17 -0
  130. package/generators/h5-react/templates/scripts/collect-dist.js +78 -0
  131. package/generators/h5-react/templates/scripts/dev-preset.js +265 -0
  132. package/generators/h5-react/templates/scripts/dev-preset.schema.json +39 -0
  133. package/generators/h5-react/templates/scripts/dev.js +133 -0
  134. package/generators/h5-react/templates/scripts/gateway.ts +241 -0
  135. package/generators/h5-react/templates/turbo.json +86 -0
  136. package/generators/subapp-h5/ignore-list.json +1 -0
  137. package/generators/subapp-h5/index.js +424 -0
  138. package/generators/subapp-h5/meta.json +10 -0
  139. package/generators/subapp-h5/templates/.env +1 -0
  140. package/generators/subapp-h5/templates/.stylelintrc.js +22 -0
  141. package/generators/subapp-h5/templates/config/config.dev.ts +7 -0
  142. package/generators/subapp-h5/templates/config/config.prod.development.ts +7 -0
  143. package/generators/subapp-h5/templates/config/config.prod.production.ts +10 -0
  144. package/generators/subapp-h5/templates/config/config.prod.testing.ts +7 -0
  145. package/generators/subapp-h5/templates/config/config.prod.ts +7 -0
  146. package/generators/subapp-h5/templates/config/config.ts +6 -0
  147. package/generators/subapp-h5/templates/config/routes.ts +13 -0
  148. package/generators/subapp-h5/templates/eslint.config.ts +12 -0
  149. package/generators/subapp-h5/templates/mock/user.ts +34 -0
  150. package/generators/subapp-h5/templates/package.json +42 -0
  151. package/generators/subapp-h5/templates/src/app.tsx +14 -0
  152. package/generators/subapp-h5/templates/src/assets/yay.jpg +0 -0
  153. package/generators/subapp-h5/templates/src/intl.ts +37 -0
  154. package/generators/subapp-h5/templates/src/layouts/index.tsx +10 -0
  155. package/generators/subapp-h5/templates/src/pages/index.tsx +22 -0
  156. package/generators/subapp-h5/templates/src/services/user.ts +38 -0
  157. package/generators/subapp-h5/templates/tailwind.config.js +16 -0
  158. package/generators/subapp-h5/templates/tailwind.css +7 -0
  159. package/generators/subapp-h5/templates/tsconfig.json +3 -0
  160. package/generators/subapp-h5/templates/typings.d.ts +1 -0
  161. package/lib/setup-multica-desktop.js +154 -0
  162. package/package.json +1 -1
@@ -0,0 +1,349 @@
1
+ 'use strict';
2
+
3
+ const Generator = require('yeoman-generator');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const {
7
+ toKebab,
8
+ toPascal,
9
+ collectFiles,
10
+ transformDestPath,
11
+ isTemplateFile,
12
+ setupErrorHandlers,
13
+ createLogger,
14
+ loadMicorc,
15
+ } = require('../../lib/utils');
16
+
17
+ const IGNORE_LIST = require('./ignore-list.json');
18
+
19
+ setupErrorHandlers();
20
+
21
+ module.exports = class extends Generator {
22
+ initializing() {
23
+ this.projectRoot = process.cwd();
24
+ this.logger = createLogger(this);
25
+
26
+ this.isDryRun = this.options.dryRun || process.env.MICO_DRY_RUN === '1';
27
+
28
+ const { config: rcConfig, configPath } = loadMicorc(this.projectRoot);
29
+ this.rcConfig = rcConfig;
30
+ if (configPath) {
31
+ this.logger.verbose('Loaded config from:', configPath);
32
+ this.logger.verbose('Config:', JSON.stringify(rcConfig, null, 2));
33
+ }
34
+
35
+ const workspaceFile = path.join(this.projectRoot, 'pnpm-workspace.yaml');
36
+ if (fs.existsSync(workspaceFile) && !this.options.force) {
37
+ console.error('');
38
+ console.error('❌ Error: Current directory is already a monorepo.');
39
+ console.error('');
40
+ console.error(' A pnpm-workspace.yaml file already exists.');
41
+ console.error(
42
+ ' Please run this command in an empty directory or specify a new project name.',
43
+ );
44
+ console.error('');
45
+ console.error(' Usage:');
46
+ console.error(' mkdir my-h5 && cd my-h5');
47
+ console.error(' mico create h5-react');
48
+ console.error('');
49
+ console.error(' To regenerate in an existing monorepo, use --force:');
50
+ console.error(' mico create h5-react --force');
51
+ console.error('');
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ async prompting() {
57
+ try {
58
+ const rc = this.rcConfig || {};
59
+
60
+ this.answers = await this.prompt([
61
+ {
62
+ type: 'input',
63
+ name: 'projectName',
64
+ message: 'Project name',
65
+ default: rc.projectName || path.basename(this.projectRoot),
66
+ filter: (input) => toKebab(input),
67
+ validate: (input) => {
68
+ const value = toKebab(input);
69
+ if (!value) return 'Project name is required';
70
+ return true;
71
+ },
72
+ },
73
+ {
74
+ type: 'input',
75
+ name: 'packageScope',
76
+ message: 'Package scope (e.g., @my-project)',
77
+ default: (answers) => rc.packageScope || `@${toKebab(answers.projectName)}`,
78
+ validate: (input) => {
79
+ if (!input) return 'Package scope is required';
80
+ if (!input.startsWith('@')) return 'Package scope must start with @';
81
+ return true;
82
+ },
83
+ },
84
+ {
85
+ type: 'input',
86
+ name: 'cdnPrefix',
87
+ message: `CDN path prefix (用于区分不同业务线的 CDN 目录)
88
+ 示例:CDN 完整路径 = https://cdn.example.com/<prefix>/<projectName>/<version>/
89
+ - 留空: https://cdn.example.com/my-project/1.0.0/
90
+ - 输入 "portal": https://cdn.example.com/portal/my-project/1.0.0/
91
+ - 输入 "admin/v2": https://cdn.example.com/admin/v2/my-project/1.0.0/
92
+ Prefix`,
93
+ default: rc.cdnPrefix || '',
94
+ filter: (input) => {
95
+ return input.trim().replace(/^\/+|\/+$/g, '');
96
+ },
97
+ },
98
+ {
99
+ type: 'input',
100
+ name: 'author',
101
+ message: 'Author',
102
+ default: rc.author || 'Your Name <email@example.com>',
103
+ },
104
+ {
105
+ type: 'input',
106
+ name: 'initialSubAppName',
107
+ message: 'First sub-app name (directory under apps/)',
108
+ default: rc.initialSubAppName || 'app',
109
+ filter: (input) => toKebab(input),
110
+ validate: (input) => {
111
+ const value = toKebab(input);
112
+ if (!value) return 'Sub-app name is required';
113
+ const destDir = path.join(this.projectRoot, 'apps', value);
114
+ if (fs.existsSync(destDir)) {
115
+ return `Target already exists: apps/${value}`;
116
+ }
117
+ return true;
118
+ },
119
+ },
120
+ {
121
+ type: 'input',
122
+ name: 'initialSubAppDevPort',
123
+ message: 'First sub-app dev port (standalone pnpm dev in that app)',
124
+ default: rc.initialSubAppDevPort || '8000',
125
+ validate: (input) => {
126
+ const port = Number(input);
127
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
128
+ return 'Port must be an integer between 1024 and 65535';
129
+ }
130
+ return true;
131
+ },
132
+ },
133
+ ]);
134
+
135
+ this.projectName = toKebab(this.answers.projectName);
136
+ this.ProjectName = toPascal(this.projectName);
137
+ this.packageScope = this.answers.packageScope;
138
+ this.cdnPrefix = this.answers.cdnPrefix;
139
+ this.author = this.answers.author;
140
+ this.initialSubAppName = toKebab(this.answers.initialSubAppName);
141
+ this.initialSubAppDevPort = String(this.answers.initialSubAppDevPort);
142
+ this.templateDir = this.templatePath();
143
+ this.destDir = this.projectRoot;
144
+ } catch (error) {
145
+ console.error('');
146
+ console.error('❌ Error during prompting:');
147
+ console.error(` ${error.message}`);
148
+ console.error('');
149
+ process.exit(1);
150
+ }
151
+ }
152
+
153
+ async writing() {
154
+ try {
155
+ if (!fs.existsSync(this.templateDir)) {
156
+ console.error('');
157
+ console.error('❌ Error: Template directory not found.');
158
+ console.error(` Expected: ${this.templateDir}`);
159
+ console.error('');
160
+ process.exit(1);
161
+ }
162
+
163
+ this.logger.verbose('Template directory:', this.templateDir);
164
+ this.logger.verbose('Destination directory:', this.destDir);
165
+
166
+ const cdnPrefixPath = this.cdnPrefix ? `${this.cdnPrefix}/` : '';
167
+
168
+ const templateData = {
169
+ projectName: this.projectName,
170
+ ProjectName: this.ProjectName,
171
+ packageScope: this.packageScope,
172
+ author: this.author,
173
+ cdnPrefix: this.cdnPrefix,
174
+ cdnPrefixPath,
175
+ };
176
+
177
+ this.logger.verbose('Template data:', JSON.stringify(templateData, null, 2));
178
+
179
+ const files = collectFiles(this.templateDir, this.templateDir, IGNORE_LIST);
180
+
181
+ if (files.length === 0) {
182
+ console.error('');
183
+ console.error('❌ Error: No template files found.');
184
+ console.error(` Template directory: ${this.templateDir}`);
185
+ console.error('');
186
+ process.exit(1);
187
+ }
188
+
189
+ if (this.isDryRun) {
190
+ this.log('');
191
+ this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
192
+ this.log('');
193
+ this.log(` Project: ${this.projectName}`);
194
+ this.log(` Scope: ${this.packageScope}`);
195
+ this.log(` CDN prefix: ${this.cdnPrefix || '(empty)'}`);
196
+ this.log(` First sub-app: ${this.initialSubAppName} (via subapp-h5)`);
197
+ this.log(` Destination: ${this.destDir}`);
198
+ this.log('');
199
+ this.log(' Would create the following files:');
200
+ this.log('');
201
+
202
+ let templateCount = 0;
203
+ let copyCount = 0;
204
+
205
+ for (const relPath of files) {
206
+ const destRelPath = transformDestPath(relPath);
207
+ const isTemplate = isTemplateFile(relPath);
208
+ const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
209
+ this.log(` ${tag} ${destRelPath}`);
210
+
211
+ if (isTemplate) {
212
+ templateCount++;
213
+ } else {
214
+ copyCount++;
215
+ }
216
+ }
217
+
218
+ this.log('');
219
+ this.log(
220
+ ` Total: ${files.length} files (${templateCount} templates, ${copyCount} static)`,
221
+ );
222
+ this.log('');
223
+ this.log(
224
+ ` Then would run generator subapp-h5 → apps/${this.initialSubAppName}/ (not executed in dry-run).`,
225
+ );
226
+ this.log('');
227
+ this.log(' Run without --dry-run to actually create these files.');
228
+ this.log('');
229
+
230
+ this._skipInstall = true;
231
+ return;
232
+ }
233
+
234
+ this.log('');
235
+ this.log(`📦 Creating H5 monorepo: ${this.projectName}`);
236
+ this.log(` Scope: ${this.packageScope}`);
237
+ this.log('');
238
+
239
+ this.logger.verbose(`Processing ${files.length} files...`);
240
+
241
+ let templateCount = 0;
242
+ let copyCount = 0;
243
+
244
+ for (const relPath of files) {
245
+ const srcPath = path.join(this.templateDir, relPath);
246
+ const destRelPath = transformDestPath(relPath);
247
+ const destPath = path.join(this.destDir, destRelPath);
248
+
249
+ if (isTemplateFile(relPath)) {
250
+ this.logger.file('template', destRelPath);
251
+ this.fs.copyTpl(srcPath, destPath, templateData);
252
+ templateCount++;
253
+ } else {
254
+ this.logger.file('copy', destRelPath);
255
+ this.fs.copy(srcPath, destPath);
256
+ copyCount++;
257
+ }
258
+ }
259
+
260
+ this.logger.verbose(`Processed: ${templateCount} templates, ${copyCount} copied`);
261
+
262
+ // Yeoman 此阶段文件尚在 mem-fs,尚未落盘;subapp-h5 的 initializing 会用 fs 检查 apps/,
263
+ // 且其更新根配置时会读磁盘。先保证磁盘上存在 apps/,子应用再通过 this.fs 读写根目录文件。
264
+ const appsDirOnDisk = path.join(this.destDir, 'apps');
265
+ fs.mkdirSync(appsDirOnDisk, { recursive: true });
266
+
267
+ const subappGen = path.resolve(__dirname, '../subapp-h5/index.js');
268
+ this.log('');
269
+ this.log(`📦 联动创建首个子应用: apps/${this.initialSubAppName}`);
270
+ this.log('');
271
+ await this.composeWith(subappGen, {
272
+ composedFromH5React: true,
273
+ skipInstall: true,
274
+ skipEndMessage: true,
275
+ composeAnswers: {
276
+ appName: this.initialSubAppName,
277
+ packageScope: this.packageScope,
278
+ devPort: this.initialSubAppDevPort,
279
+ author: this.author,
280
+ },
281
+ });
282
+ } catch (error) {
283
+ console.error('');
284
+ console.error('❌ Error during file generation:');
285
+ console.error(` ${error.message}`);
286
+ console.error('');
287
+ process.exit(1);
288
+ }
289
+ }
290
+
291
+ install() {
292
+ if (this._skipInstall) return;
293
+
294
+ const gitDir = path.join(this.destDir, '.git');
295
+ if (!fs.existsSync(gitDir)) {
296
+ this.log('');
297
+ this.log('🔧 初始化 Git 仓库...');
298
+ this.spawnCommandSync('git', ['init'], {
299
+ cwd: this.destDir,
300
+ });
301
+ }
302
+
303
+ this.log('');
304
+ this.log('📦 正在安装依赖...');
305
+ try {
306
+ this.spawnCommandSync('pnpm', ['install'], {
307
+ cwd: this.destDir,
308
+ });
309
+ } catch (e) {
310
+ this._installFailed = true;
311
+ this.log('');
312
+ this.log('⚠️ 依赖安装失败,但项目文件已全部生成。');
313
+ this.logger.verbose('Install error:', e.message);
314
+ return;
315
+ }
316
+ }
317
+
318
+ end() {
319
+ if (this._skipInstall) return;
320
+
321
+ this.log('');
322
+
323
+ if (this._installFailed) {
324
+ this.log('⚠️ 项目文件已创建,但依赖安装未完成。');
325
+ this.log('');
326
+ this.log(' 请手动安装依赖:');
327
+ this.log('');
328
+ this.log(' pnpm install');
329
+ this.log('');
330
+ this.log(' 如果安装仍然失败,可尝试:');
331
+ this.log(' pnpm install --network-concurrency 4');
332
+ } else {
333
+ this.log('✅ 项目创建成功!');
334
+ }
335
+
336
+ this.log('');
337
+ this.log(' 后续步骤:');
338
+ this.log('');
339
+ this.log(' # 网关开发(单端口代理多应用)');
340
+ this.log(' pnpm dev:gateway');
341
+ this.log('');
342
+ this.log(' # 或按 dev 预设并行启动(见 dev.preset.json)');
343
+ this.log(' pnpm dev:preset');
344
+ this.log('');
345
+ this.log(' # 新增更多子应用(可在 monorepo 根目录多次执行)');
346
+ this.log(' mico create subapp-h5');
347
+ this.log('');
348
+ }
349
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "h5-react",
3
+ "description": "创建 H5 Monorepo(Umi 4 + 网关 + dev 预设),并联动生成首个子应用",
4
+ "usage": "mkdir my-h5 && cd my-h5 && mico create h5-react",
5
+ "features": [
6
+ "Turborepo + pnpm Workspace + catalog",
7
+ "开发网关 page.config.ts、dev.preset 多应用并行",
8
+ "首个子应用由 subapp-h5 生成(不重复维护子应用模板)",
9
+ "可多次 mico create subapp-h5 扩展业务应用"
10
+ ]
11
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional'],
3
+ rules: {
4
+ 'header-max-length': [0, 'always', 10000],
5
+ 'body-max-line-length': [0, 'always', Infinity],
6
+ },
7
+ };
@@ -0,0 +1,104 @@
1
+ ---
2
+ description: CI/CD 与部署 - Jenkins 构建流程、环境配置、CDN 发布(H5 Monorepo)
3
+ globs: ["CICD/**"]
4
+ ---
5
+
6
+ # CI/CD 与部署
7
+
8
+ ## 构建架构
9
+
10
+ ```
11
+ ┌─────────────────────────────────────────────────────────────────┐
12
+ │ Jenkins │
13
+ │ ┌─────────────────────────────────────────────────────────────┐│
14
+ │ │ 根据环境执行对应脚本: ││
15
+ │ │ ││
16
+ │ │ 开发环境 → CICD/start_dev.sh → build:development ││
17
+ │ │ 测试环境 → CICD/start_test.sh → build:testing ││
18
+ │ │ 生产环境 → CICD/start_prod.sh → build:production ││
19
+ │ │ 本地构建 → CICD/start_local.sh → build:local ││
20
+ │ └─────────────────────────────────────────────────────────────┘│
21
+ │ ↓ │
22
+ │ ┌─────────────────────────────────────────────────────────────┐│
23
+ │ │ Turborepo 构建后 collect-dist:根目录 dist/<子应用>/ ││
24
+ │ │ 上传 CDN:cdn-portal[-env].micoplatform.com/<%= cdnPrefixPath %><%= projectName %>/ ││
25
+ │ └─────────────────────────────────────────────────────────────┘│
26
+ └─────────────────────────────────────────────────────────────────┘
27
+ ```
28
+
29
+ ## CICD 脚本
30
+
31
+ | 脚本 | 环境 | 构建命令 | CDN 路径 |
32
+ |------|------|---------|---------|
33
+ | `start_dev.sh` | 开发 | `build:development` | `cdn-portal-dev.micoplatform.com` |
34
+ | `start_test.sh` | 测试 | `build:testing` | `cdn-portal-test.micoplatform.com` |
35
+ | `start_prod.sh` | 生产 | `build:production` | `cdn-portal.micoplatform.com` |
36
+ | `start_local.sh` | 本地 | `build:local` | `./`(相对路径) |
37
+
38
+ ## 脚本执行流程(摘要)
39
+
40
+ ```bash
41
+ # 1. 安装 Node / pnpm(见各脚本内 nvm 版本)
42
+ pnpm install
43
+
44
+ # 2. 版本号
45
+ VERSION=$(node -p "require('./package.json').version")
46
+
47
+ # 3. 静态资源公共路径(与创建项目时填写的 CDN prefix 一致)
48
+ export CDN_PUBLIC_PATH="https://cdn-portal[-env].micoplatform.com/<%= cdnPrefixPath %><%= projectName %>/${VERSION}/"
49
+
50
+ # 4. 构建(根 package.json 中 turbo + collect-dist)
51
+ pnpm run build:[environment]
52
+ ```
53
+
54
+ ## CDN 路径结构
55
+
56
+ ```
57
+ cdn-portal.micoplatform.com/
58
+ <% if (cdnPrefix) { %>└── <%= cdnPrefix %>/
59
+ └── <%= projectName %>/
60
+ <% } else { %>└── <%= projectName %>/
61
+ <% } %> └── {version}/
62
+ └── dist/
63
+ ├── <子应用 A>/ # Umi 应用,与 apps/ 下目录名对应
64
+ ├── <子应用 B>/
65
+ └── ...
66
+ ```
67
+
68
+ 创建仓库时 **`mico create h5-react`** 会提示 **CDN path prefix**(可选);留空则路径中无额外业务线目录段。
69
+
70
+ ## 环境变量
71
+
72
+ ### CDN_PUBLIC_PATH
73
+
74
+ - 由 `CICD/start_*.sh` 在构建前导出,并在 `turbo.json` 的 `globalEnv` 中声明,主要用于:CDN 上传脚本 / 网宿刷新路径 / 与 Sentry **`urlPrefix`** 对齐的人读路径标记。
75
+ - **不再**作为 Umi 构建期 `publicPath` / `cssPublicPath` 的输入。子应用 `createProConfig` 现固定输出 **`publicPath: 'auto'`** + **`cssPublicPath: './'`**:JS publicPath 由 webpack 在运行时按 HTML 加载位置解析,CSS `url()` 走相对路径,避免与 qiankun slave 默认开启的 `runtimePublicPath` 冲突。
76
+ - 各环境域名见上表;Sentry sourcemap 的 `urlPrefix`(`<%= cdnPrefixPath %><%= projectName %>/<version>/<appName>`)由 **`createProductionConfig`** 内部按 `appName` 拼接,与 `CDN_PUBLIC_PATH` 在路径语义上一致。
77
+
78
+ ### SENTRY_AUTH_TOKEN
79
+
80
+ - 生产/测试流水线若需上传 Source Map,在 Jenkins 或本地构建环境中注入 **`SENTRY_AUTH_TOKEN`**(**`turbo.json`** 的 **`globalEnv`** 已声明,避免 Turbo 缓存忽略该变量)。
81
+
82
+ ### 版本号
83
+
84
+ - 从根 **`package.json`** 的 **`version`** 读取,用于 CDN 路径中的版本隔离;**`packages/umi-config/src/plugins/apply-sentry-plugin.ts`** 中 Sentry **`release.name`** 与该文件读取的同一个 **`version`** 一致。
85
+
86
+ ## 构建产物(根目录)
87
+
88
+ `pnpm build` 成功后,`scripts/collect-dist.js` 将各 **`apps/<名>/dist`** 汇总为根目录 **`dist/<名>/`**,与 CDN 上按子应用划分的目录一致。
89
+
90
+ ## 本地模拟
91
+
92
+ ```bash
93
+ sh CICD/start_test.sh
94
+ sh CICD/start_prod.sh
95
+ sh CICD/start_local.sh
96
+ ```
97
+
98
+ ## 注意事项
99
+
100
+ 1. **CDN prefix**:与 **`micro-react`** 生成器语义相同,仅影响路径中是否包含业务线前缀。
101
+ 2. **网宿刷新**:见 `wangsu_fresh_*.sh`。
102
+ 3. **Turborepo**:增量与 `before_build.sh` 中 `TURBO_FILTER` 等逻辑以脚本为准。
103
+ 4. **Sentry sourcemap 已不在 `scripts/`**:上传逻辑封装在 **`packages/umi-config/src/plugins/apply-sentry-plugin.ts`**,由 **`createProductionConfig`** 自动调用;CICD 只需注入 **`SENTRY_AUTH_TOKEN`** 即可,无需手工改子应用 `config.prod.ts`。
104
+ 5. **环境变量 `.env*` 集中在仓库根**:`.env`、`.env.development`、`.env.testing`、`.env.production` 维护 `UMI_APP_API_BASE_URL` / `UMI_APP_CDN_BASE_URL` / `UMI_APP_LANG_BASE_URL` / `UMI_APP_ENV` 等;CICD 脚本在调用 `pnpm build:[env]` 之前导出 `CDN_PUBLIC_PATH`,由根 `package.json` 中的 `dotenv-cli` 链注入到子应用进程。
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: <%= packageScope %>/common-intl 包实现与在 Umi 应用中的接入约定
3
+ globs: "packages/common-intl/**/*.{ts,tsx}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # `<%= packageScope %>/common-intl`(`packages/common-intl`)
8
+
9
+ - 包名 **`<%= packageScope %>/common-intl`**;应用依赖写 **`workspace:^`**,说明见 **`packages/common-intl/README.md`**
10
+ - 本包是 **`@common-web/common-intl`** 的**项目级封装**(re-export + 预配置)。`src/index.ts` 通过 `export * from '@common-web/common-intl'` 透传上游全部 API(`initIntl`、`addIntl`、`configureLocale`、`i18n` 等),同时导出本地的 **`init`** 函数
11
+ - 包依赖 **`@common-web/common-intl`**(上游库)、**`<%= packageScope %>/domain`**(`langBaseUrl`)、**`<%= packageScope %>/request`**(`request`)、**`<%= packageScope %>/js-bridge`**(`getUserInfo` 取语言)
12
+ - **无构建步骤**:`package.json` 的 `exports` 直接指向 **`./src/index.ts`**(源码 TS),无 vite / 无 dist
13
+
14
+ ## `init()` 函数(`src/intl.ts`)
15
+
16
+ - **`init(params)`** 是项目级便捷入口;接收 `IInitIntlParams`(`app_name` 可选,默认 `<%= projectName %>`,**`tag`**、**`indexedDBParams`** 由调用方传入),返回 `{ fetchMultilingualData, i18n }`
17
+ - 内部 `initIntl()` 即时执行;**`configureLocale`** 与 `requestInstance` 绑定**延迟到** `fetchMultilingualData()` 首次调用:会先经 **`<%= packageScope %>/js-bridge` 的 `getUserInfo()`** 拉到客户端语言(失败兜底 `'en'`),再 `configureLocale({ getLocale: () => lang, setLocale })`
18
+ - `requestInstance` 自动包装 **`<%= packageScope %>/request` 的 `request`**,并强制 **`skipRequestInterceptors: true`**,避免业务请求拦截器影响多语言拉取
19
+ - `messageInstance` 默认走 `console.error / console.warn`,业务需要替换为 toast 时可在子应用层重写
20
+ - 返回的 `fetchMultilingualData()` 已预置 `requestInstance`、`messageInstance`、`lang`、`localeRequestUrl`,子应用调用时**无需再传任何参数**
21
+
22
+ ## 子应用接入(`subapp-h5` 模板)
23
+
24
+ - **`src/intl.ts`**:`import { addIntl, init } from '<%= packageScope %>/common-intl'`;用 **`init({ tag, indexedDBParams })`** 取得 `{ fetchMultilingualData, i18n }`,再用 **`addIntl({ ... }, i18n)`** 定义文案对象并 `export default`;同时 `export { fetchMultilingualData }`
25
+ - **`src/app.tsx`** 的 **`render`** 仅从 **`@/intl`** 调用 **`fetchMultilingualData()`**,再 **`oldRender()`**(失败同样 **`oldRender()`**)
26
+ - 业务组件 **`import intl from '@/intl'`**,通过 `intl.xxx()` 使用文案;勿在子应用里再从包根导入一份业务 `intl`
27
+
28
+ ## 微前端
29
+
30
+ - 主应用 **`initIntl`** 拉取后写入全局(`window.__MICO_COMMON_INTL_TRANSLATIONS__`);子应用只使用共享文案(见 **`packages/common-intl/README.md`** 微前端模式),勿重复初始化
31
+ - 若子应用需要**独立 tag**,直接依赖 `@common-web/common-intl` 在子应用内自行调用 `initIntl`,不经过本中转包
32
+
33
+ ## JSBridge 与语言
34
+
35
+ - `init()` 中通过 **`getUserInfo()`** 拿 `lang`,需要客户端实现 `getUserInfo` JSBridge;本地/测试环境下 **`<%= packageScope %>/js-bridge`** 的 `getUserInfo` 在 `isDebug` 下会回退 fetch 远程登录接口(仅供联调,不要在线上启用
36
+
37
+ ## 其他
38
+
39
+ - 占位符为 **`%AA%`、`%BB%`**…,与 **`interpolations`** 顺序对应
40
+ - 修改包内代码后无需单独构建;依赖该包的应用重新 dev / build 即可生效
41
+
42
+ 应用层与 **`<%= packageScope %>/request`**、**`<%= packageScope %>/utils`** 的配合见 **`.cursor/rules/umi-app.mdc`**。
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Husky、lint-staged、commitlint 与提交前检查流程(<%= ProjectName %>)
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Git 钩子与提交规范
7
+
8
+ ## Husky
9
+
10
+ - `pnpm install` 后由根 `prepare` 脚本安装 Husky(`"prepare": "husky"`)
11
+ - **pre-commit**:执行 `pnpm run lint-staged`,随后 **`git add .`**(把格式化结果重新暂存)
12
+ - **commit-msg**:调 `pnpm exec commitlint --edit "$1"` 校验提交说明
13
+
14
+ ## lint-staged
15
+
16
+ - **统一在仓库根 `.lintstagedrc.js` 维护**(CommonJS);当前规则:
17
+
18
+ ```js
19
+ module.exports = {
20
+ 'apps/*/**/*.{js,jsx,ts,tsx}': 'eslint --flag v10_config_lookup_from_file --fix',
21
+ 'apps/*/**/*.{css,less}': 'stylelint --fix',
22
+ 'packages/!(eslint)/**/*.{js,jsx,ts,tsx}': 'eslint --flag v10_config_lookup_from_file --fix',
23
+ };
24
+ ```
25
+
26
+ - 根 `package.json` 的 `lint-staged` 脚本直接使用 **`lint-staged`**(**不再**用 `turbo run lint-staged --concurrency=1`)
27
+ - **`--flag v10_config_lookup_from_file`**:让 ESLint 9 在传入文件路径时按**文件所在目录**解析 flat config,从而命中各包根目录下的 `eslint.config.ts`,避免误用根目录配置
28
+ - `apps/*/**/*.{css,less}` 走 stylelint;**packages/* 不跑 stylelint**(共享包不写样式)
29
+ - **`packages/!(eslint)`**:排除 `packages/eslint`(共享 ESLint 配置自身改动不需要再跑一遍)
30
+ - **已废弃**:每个 `packages/<x>/.lintstagedrc`、各子应用 `.lintstagedrc`、根 `lint-staged` 调 turbo 的旧写法;新增包**不需要**再放 `.lintstagedrc`,集中在根的 glob 即可命中
31
+
32
+ ## commitlint
33
+
34
+ - 配置:`.commitlintrc.js`,继承 **`@commitlint/config-conventional`**
35
+ - 提交信息需符合 **Conventional Commits**(如 `feat:`、`fix:`、`chore:`)
36
+
37
+ ```text
38
+ ✅ feat(apps/<子应用目录>): add login form
39
+ ❌ update stuff
40
+ ```
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: packages/* 内部包的模块格式、exports 与命名空间(<%= packageScope %>)
3
+ globs: "packages/**/*.{ts,tsx,mjs,js,json}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # `packages/*` 内部包
8
+
9
+ ## 通用约定
10
+
11
+ - 包名一律使用 **`<%= packageScope %>/*`**;`package.json` 多数声明 **`"type": "module"`**,入口与导出按 ESM 约定
12
+ - 对外 API 通过 **`exports`** 暴露子路径(避免应用深链 `../../`);应用只 `import` 已导出条目
13
+ - 子应用引用本仓库包统一写 **`workspace:^`**;共享包变更后,依赖它的应用需重新 dev / build 才能看到效果
14
+ - 多数包还导出 **`./*`** 通配(如 `<%= packageScope %>/utils/tailwind`、`<%= packageScope %>/js-bridge/closeH5Page`),可按子路径直接 `import`,无需在 `index.ts` 里 re-export
15
+
16
+ ```json
17
+ // 示例(<%= packageScope %>/utils):直接按子路径导出
18
+ "exports": {
19
+ ".": "./src/index.ts",
20
+ "./*": "./src/*.ts"
21
+ }
22
+ ```
23
+
24
+ ## 工具与基线
25
+
26
+ - **`<%= packageScope %>/eslint`**:ESLint 9 flat config 共享配置;**`exports`** 中 **`.`** → `eslint.config.base.ts`(推荐 + TS + Prettier,并 ignore `mock/**`、`tailwind.config.*`、`postcss.config.*`、`src/.umi*` 等);**`./react`** 在 base 上叠加 React / Hooks / Refresh。子应用 / 子包的 `eslint.config.ts` 都 `import` 此处的导出,并补 **`parserOptions.tsconfigRootDir: import.meta.dirname`**
27
+ - **`<%= packageScope %>/typescript`**:导出 **`.`** → `tsconfig.base.json`、**`./react`** → `tsconfig.react.json`;多数子包 `tsconfig.json` 直接 `extends` 此包,Umi 子应用与 **`packages/components`** 等含 Umi 类型的包则 `extends "./src/.umi/tsconfig.json"`
28
+ - **`<%= packageScope %>/utils`**:通用工具(`tailwind` 的 **`cn` / `cva` / `VariantProps`**、`env` 平台与环境判断、`url`、`number`、`date`、`md5`,以及 **`mock`**:开发 Mock 复用的 **`baseUrl`** / **`tryProxy`** 远程透传 / **`validateBody`** zod 入参校验工具);按 **`./<文件名>`** 子路径引用即可
29
+
30
+ ## 运行时基础
31
+
32
+ - **`<%= packageScope %>/domain`**:导出 **`apiBaseUrl`**、**`cdnBaseUrl`**、**`langBaseUrl`** 等;优先读 **`window.__MICO_CONFIG__`**,否则回退 **`UMI_APP_API_BASE_URL`** / **`UMI_APP_CDN_BASE_URL`** / **`UMI_APP_LANG_BASE_URL`**(来自仓库根 `.env.<env>`)。无业务 UI,被 **`request`**、**`common-intl`**、**`js-bridge`** 等消费
33
+ - **`<%= packageScope %>/js-bridge`**:原生 H5 通信能力。**`canUseJSBridge`**(`isAndroid` 用 `window.Omigo.post`、`isIOS` 用 `window.MCAIJsBridge.post`)+ **`callJSBridgeMethods<D, T>(method, payload, options?)`**(含 postID 路由、`AndroidJSBridgeCallback` 全局桥、Loading 防抖)。子路径示例 **`./getUserInfo`**、**`./closeH5Page`**;类型与全局声明在 **`type.d.ts`**。开发期 `getUserInfo` 在 `isDebug` 下回退 fetch 远程登录接口,便于本地联调;线上**不要**开放此回退分支
34
+ - **`<%= packageScope %>/request`**:基于 Umi **`request`** 的统一封装。**`src/index.ts`** 导出 **`request`**、**`withTemporaryInterceptors`**、**`registerRequestInterceptor` / `registerResponseInterceptor`** 与 **`UnifiedRequestOptions`** 等类型;**`src/url-resolver.ts`** 使用 `domain` 的 `apiBaseUrl` 拼接相对路径;模块加载时已注入默认拦截器(默认请求头 + 业务 `code` 校验,可在 **`buildDefaultHeaders`** 内放开 `Authorization`)。`isShowLoading` **默认 `true`**,与请求池配合做并发显隐。详见 **`.cursor/rules/workspace-request.mdc`**
35
+ - **`<%= packageScope %>/common-intl`**:**`@common-web/common-intl`** 的项目级封装(re-export 上游全部 API + 预配置的 **`init()`** 函数);依赖 **`@common-web/common-intl`**、**`domain`**、**`request`**、**`js-bridge`**(`init()` 内部用 **`getUserInfo()`** 取 `lang`)。无构建步骤,`exports` 直接指向源码 TS。详见 **`.cursor/rules/common-intl.mdc`**
36
+
37
+ ## UI 与业务复用
38
+
39
+ - **`<%= packageScope %>/components`**:项目级 UI 组件库;当前内置 **`Layout`**(普通 / 沉浸式两种 + 头部安全区)、**`ImmersiveHeader`**(默认头:返回按钮通过 `closeH5Page` 走 JSBridge)、**`LayoutFooter`**(fixed/非 fixed + `pb-safe-or-4`)。依赖 **`<%= packageScope %>/js-bridge`** 与 **`<%= packageScope %>/utils`**;`peerDependencies` 上 React / React-DOM 写 `catalog:`。包目录有 `tailwind.config.js` 扫描入口由各子应用追加(见 **`.cursor/rules/tailwind-umi.mdc`**)。**资源**通过 **`./assets/*`** 子路径导出,例如 `import backIcon from '<%= packageScope %>/components/assets/image/back.png'`
40
+ - **`<%= packageScope %>/constant`**:业务常量集中地(如示例 **`MEMBER_CONSTANT`**);`exports` 同时支持 **`.`** 与 **`./<文件>`**,建议按业务域拆分文件(如 `member.ts`),不要把所有枚举塞进 `index.ts`
41
+ - **`<%= packageScope %>/deeplink`**:客户端跳转链接的语义层;当前提供 **`togoHome`** 等占位实现,业务侧需按客户端 deeplink 协议改写(默认值含 `<%= projectName %>://router/home` 占位)
42
+ - **`<%= packageScope %>/umi-config`**:子应用 Umi 配置工厂集合(`createBaseConfig` / `createDevConfig` / `createProConfig` / `createDevelopmentConfig` / `createTestingConfig` / `createProductionConfig`)+ 内部 **`plugins/apply-sentry-plugin.ts`**。**所有子应用 `config/*.ts` 均委托到本包**,**不要**在子应用里再抄基础配置或 Sentry 上传逻辑。完整设计见 **`.cursor/rules/umi-config.mdc`**
43
+
44
+ ## 与 Mock / 服务类型的配合
45
+
46
+ 子应用 **`mock/`** 不在 **`packages/`**,但与 **`request`**、**`domain`** 的 URL 规则强相关;公共逻辑(`baseUrl`、`tryProxy`)来自 **`<%= packageScope %>/utils/mock`**,约定见 **`.cursor/rules/umi-mock.mdc`**。请求/响应类型就近定义在 **`apps/<子应用>/src/services/`** 下对应模块中。