generator-mico-cli 0.2.31 → 0.2.33

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 (179) 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/micro-react/templates/apps/layout/config/config.dev.ts +2 -0
  137. package/generators/micro-react/templates/apps/layout/docs/feature-/345/244/264/351/203/250/344/270/200/347/272/247/345/257/274/350/210/252/345/270/203/345/261/200.md +166 -0
  138. package/generators/micro-react/templates/apps/layout/src/app.tsx +20 -2
  139. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +36 -0
  140. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +6 -0
  141. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +8 -2
  142. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +128 -11
  143. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +22 -0
  144. package/generators/micro-react/templates/apps/layout/src/hooks/useFilteredMenuItems.ts +29 -0
  145. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +73 -0
  146. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +51 -4
  147. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +25 -18
  148. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +4 -0
  149. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +4 -0
  150. package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +1 -0
  151. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +20 -0
  152. package/generators/subapp-h5/ignore-list.json +1 -0
  153. package/generators/subapp-h5/index.js +424 -0
  154. package/generators/subapp-h5/meta.json +10 -0
  155. package/generators/subapp-h5/templates/.env +1 -0
  156. package/generators/subapp-h5/templates/.stylelintrc.js +22 -0
  157. package/generators/subapp-h5/templates/config/config.dev.ts +7 -0
  158. package/generators/subapp-h5/templates/config/config.prod.development.ts +7 -0
  159. package/generators/subapp-h5/templates/config/config.prod.production.ts +10 -0
  160. package/generators/subapp-h5/templates/config/config.prod.testing.ts +7 -0
  161. package/generators/subapp-h5/templates/config/config.prod.ts +7 -0
  162. package/generators/subapp-h5/templates/config/config.ts +6 -0
  163. package/generators/subapp-h5/templates/config/routes.ts +13 -0
  164. package/generators/subapp-h5/templates/eslint.config.ts +12 -0
  165. package/generators/subapp-h5/templates/mock/user.ts +34 -0
  166. package/generators/subapp-h5/templates/package.json +42 -0
  167. package/generators/subapp-h5/templates/src/app.tsx +14 -0
  168. package/generators/subapp-h5/templates/src/assets/yay.jpg +0 -0
  169. package/generators/subapp-h5/templates/src/intl.ts +37 -0
  170. package/generators/subapp-h5/templates/src/layouts/index.tsx +10 -0
  171. package/generators/subapp-h5/templates/src/pages/index.tsx +22 -0
  172. package/generators/subapp-h5/templates/src/services/user.ts +38 -0
  173. package/generators/subapp-h5/templates/tailwind.config.js +16 -0
  174. package/generators/subapp-h5/templates/tailwind.css +7 -0
  175. package/generators/subapp-h5/templates/tsconfig.json +3 -0
  176. package/generators/subapp-h5/templates/typings.d.ts +1 -0
  177. package/lib/setup-multica-desktop.js +154 -0
  178. package/lib/utils.js +25 -0
  179. package/package.json +1 -1
@@ -0,0 +1,424 @@
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
+ detectPackageScope,
13
+ setupErrorHandlers,
14
+ createLogger,
15
+ loadMicorc,
16
+ } = require('../../lib/utils');
17
+
18
+ const IGNORE_LIST = require('./ignore-list.json');
19
+
20
+ setupErrorHandlers();
21
+
22
+ module.exports = class extends Generator {
23
+ initializing() {
24
+ this.monorepoRoot = process.cwd();
25
+ this.logger = createLogger(this);
26
+
27
+ this.isDryRun = this.options.dryRun || process.env.MICO_DRY_RUN === '1';
28
+ this.skipInstall =
29
+ this.options.skipInstall === true || this.options['skip-install'] === true;
30
+ this.skipEndMessage = this.options.skipEndMessage === true;
31
+ this.composedFromH5React = this.options.composedFromH5React === true;
32
+
33
+ const { config: rcConfig, configPath } = loadMicorc(this.monorepoRoot);
34
+ this.rcConfig = rcConfig;
35
+ if (configPath) {
36
+ this.logger.verbose('Loaded config from:', configPath);
37
+ this.logger.verbose('Config:', JSON.stringify(rcConfig, null, 2));
38
+ }
39
+
40
+ const appsDir = path.join(this.monorepoRoot, 'apps');
41
+ const workspaceFile = path.join(this.monorepoRoot, 'pnpm-workspace.yaml');
42
+
43
+ // 联动 h5-react 时,父生成器刚把 apps/.gitkeep 等写入 mem-fs,尚未 commit 到磁盘,
44
+ // 仅用 fs.existsSync(apps) 会误判。允许:磁盘已有 apps/,或 mem-fs 中已有 apps 下占位文件。
45
+ const appsGitkeepInStore = this.destinationPath('apps/.gitkeep');
46
+ const hasAppsOnDisk = fs.existsSync(appsDir);
47
+ const hasAppsInMemFs =
48
+ typeof this.fs?.exists === 'function' && this.fs.exists(appsGitkeepInStore);
49
+ if (!hasAppsOnDisk && !hasAppsInMemFs) {
50
+ console.error('');
51
+ console.error('❌ Error: apps directory not found.');
52
+ console.error('');
53
+ console.error(
54
+ ' This generator must be run from an H5 monorepo root that contains an "apps" directory.',
55
+ );
56
+ console.error(` Current directory: ${this.monorepoRoot}`);
57
+ console.error('');
58
+ console.error(' Usage:');
59
+ console.error(' cd /path/to/your-h5-monorepo');
60
+ console.error(' mico create subapp-h5');
61
+ console.error('');
62
+ process.exit(1);
63
+ }
64
+
65
+ if (!fs.existsSync(workspaceFile)) {
66
+ console.warn('');
67
+ console.warn('⚠️ Warning: pnpm-workspace.yaml not found.');
68
+ console.warn(' Make sure you are in the correct monorepo.');
69
+ console.warn('');
70
+ }
71
+ }
72
+
73
+ async prompting() {
74
+ try {
75
+ const compose = this.options.composeAnswers;
76
+ if (compose && typeof compose === 'object') {
77
+ const appName = toKebab(compose.appName);
78
+ if (!appName) {
79
+ console.error('');
80
+ console.error('❌ Error: composeAnswers.appName is required.');
81
+ console.error('');
82
+ process.exit(1);
83
+ }
84
+ const destDir = path.join(this.monorepoRoot, 'apps', appName);
85
+ if (fs.existsSync(destDir)) {
86
+ console.error('');
87
+ console.error(`❌ Error: Target already exists: apps/${appName}`);
88
+ console.error('');
89
+ process.exit(1);
90
+ }
91
+ this.answers = {
92
+ appName,
93
+ packageScope: compose.packageScope,
94
+ devPort: String(compose.devPort ?? '8000'),
95
+ author: compose.author || '',
96
+ };
97
+ if (!this.answers.packageScope || !this.answers.packageScope.startsWith('@')) {
98
+ console.error('');
99
+ console.error('❌ Error: composeAnswers.packageScope must be a scoped name (e.g. @my-org).');
100
+ console.error('');
101
+ process.exit(1);
102
+ }
103
+ } else {
104
+ const detectedScope = detectPackageScope(this.monorepoRoot);
105
+ const rc = this.rcConfig || {};
106
+
107
+ this.answers = await this.prompt([
108
+ {
109
+ type: 'input',
110
+ name: 'appName',
111
+ message: 'Sub app name',
112
+ default: rc.defaultSubappName || 'app',
113
+ filter: (input) => toKebab(input),
114
+ validate: (input) => {
115
+ const value = toKebab(input);
116
+ if (!value) return 'App name is required';
117
+ const destDir = path.join(this.monorepoRoot, 'apps', value);
118
+ if (fs.existsSync(destDir)) {
119
+ return `Target already exists: apps/${value}`;
120
+ }
121
+ return true;
122
+ },
123
+ },
124
+ {
125
+ type: 'input',
126
+ name: 'packageScope',
127
+ message: 'Package scope',
128
+ default: rc.packageScope || detectedScope || '@my-project',
129
+ validate: (input) => {
130
+ if (!input) return 'Package scope is required';
131
+ if (!input.startsWith('@')) return 'Package scope must start with @';
132
+ return true;
133
+ },
134
+ },
135
+ {
136
+ type: 'input',
137
+ name: 'devPort',
138
+ message: 'Dev server port (standalone pnpm dev in this app)',
139
+ default: rc.devPort || '8000',
140
+ validate: (input) => {
141
+ const port = Number(input);
142
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
143
+ return 'Port must be an integer between 1024 and 65535';
144
+ }
145
+ return true;
146
+ },
147
+ },
148
+ {
149
+ type: 'input',
150
+ name: 'author',
151
+ message: 'Author (optional)',
152
+ default: rc.author || '',
153
+ },
154
+ ]);
155
+ }
156
+
157
+ this.appName = toKebab(this.answers.appName);
158
+ this.appNamePascal = toPascal(this.appName);
159
+ this.packageScope = this.answers.packageScope;
160
+ this.devPort = String(this.answers.devPort);
161
+ this.author = this.answers.author || '';
162
+ this.templateDir = this.templatePath();
163
+ this.destDir = path.join(this.monorepoRoot, 'apps', this.appName);
164
+ } catch (error) {
165
+ console.error('');
166
+ console.error('❌ Error during prompting:');
167
+ console.error(` ${error.message}`);
168
+ console.error('');
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ async writing() {
174
+ try {
175
+ if (!fs.existsSync(this.templateDir)) {
176
+ console.error('');
177
+ console.error('❌ Error: Template directory not found.');
178
+ console.error(` Expected: ${this.templateDir}`);
179
+ console.error('');
180
+ process.exit(1);
181
+ }
182
+
183
+ this.logger.verbose('Template directory:', this.templateDir);
184
+ this.logger.verbose('Destination directory:', this.destDir);
185
+
186
+ const templateData = {
187
+ appName: this.appName,
188
+ AppName: this.appNamePascal,
189
+ packageScope: this.packageScope,
190
+ devPort: this.devPort,
191
+ author: this.author,
192
+ };
193
+
194
+ this.logger.verbose('Template data:', JSON.stringify(templateData, null, 2));
195
+
196
+ const files = collectFiles(this.templateDir, this.templateDir, IGNORE_LIST);
197
+
198
+ if (files.length === 0) {
199
+ console.error('');
200
+ console.error('❌ Error: No template files found.');
201
+ console.error(` Template directory: ${this.templateDir}`);
202
+ console.error('');
203
+ process.exit(1);
204
+ }
205
+
206
+ if (this.isDryRun) {
207
+ this.log('');
208
+ this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
209
+ this.log('');
210
+ this.log(` Sub-app: ${this.appName}`);
211
+ this.log(` Scope: ${this.packageScope}`);
212
+ this.log(` Destination: apps/${this.appName}`);
213
+ this.log('');
214
+ this.log(' Would create the following files:');
215
+ this.log('');
216
+
217
+ let templateCount = 0;
218
+ let copyCount = 0;
219
+
220
+ for (const relPath of files) {
221
+ const destRelPath = transformDestPath(relPath);
222
+ const isTemplate = isTemplateFile(relPath) || path.basename(relPath) === '.env';
223
+ const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
224
+ this.log(` ${tag} apps/${this.appName}/${destRelPath}`);
225
+
226
+ if (isTemplate) {
227
+ templateCount++;
228
+ } else {
229
+ copyCount++;
230
+ }
231
+ }
232
+
233
+ this.log('');
234
+ this.log(
235
+ ` Total: ${files.length} files (${templateCount} templates, ${copyCount} static)`,
236
+ );
237
+ this.log('');
238
+ this.log(' Run without --dry-run to actually create these files.');
239
+ this.log('');
240
+
241
+ this._skipInstall = true;
242
+ return;
243
+ }
244
+
245
+ this.log('');
246
+ this.log(`📦 Creating sub-app: ${this.appName}`);
247
+ this.log(` Destination: apps/${this.appName}`);
248
+ this.log('');
249
+
250
+ this.logger.verbose(`Processing ${files.length} files...`);
251
+
252
+ let templateCount = 0;
253
+ let copyCount = 0;
254
+
255
+ for (const relPath of files) {
256
+ const srcPath = path.join(this.templateDir, relPath);
257
+ const destRelPath = transformDestPath(relPath);
258
+ const destPath = path.join(this.destDir, destRelPath);
259
+
260
+ if (isTemplateFile(relPath) || path.basename(relPath) === '.env') {
261
+ this.logger.file('template', destRelPath);
262
+ this.fs.copyTpl(srcPath, destPath, templateData);
263
+ templateCount++;
264
+ } else {
265
+ this.logger.file('copy', destRelPath);
266
+ this.fs.copy(srcPath, destPath);
267
+ copyCount++;
268
+ }
269
+ }
270
+
271
+ this.logger.verbose(`Processed: ${templateCount} templates, ${copyCount} copied`);
272
+
273
+ this._updateDevPreset();
274
+ this._updatePageConfig();
275
+ } catch (error) {
276
+ console.error('');
277
+ console.error('❌ Error during file generation:');
278
+ console.error(` ${error.message}`);
279
+ console.error('');
280
+ process.exit(1);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * 读取目标根目录文件:优先 mem-fs(与父生成器联动时尚未落盘),否则读磁盘。
286
+ * @param {string} absolutePath
287
+ * @returns {string|null}
288
+ */
289
+ _readDestinationText(absolutePath) {
290
+ if (this.fs.exists(absolutePath)) {
291
+ const buf = this.fs.read(absolutePath);
292
+ return Buffer.isBuffer(buf) ? buf.toString('utf8') : String(buf);
293
+ }
294
+ if (fs.existsSync(absolutePath)) {
295
+ return fs.readFileSync(absolutePath, 'utf8');
296
+ }
297
+ return null;
298
+ }
299
+
300
+ /**
301
+ * 写入目标根目录文件(走 mem-fs,最终由 Yeoman 统一落盘)。
302
+ * @param {string} absolutePath
303
+ * @param {string} content
304
+ */
305
+ _writeDestinationText(absolutePath, content) {
306
+ this.fs.write(absolutePath, content);
307
+ }
308
+
309
+ _updateDevPreset() {
310
+ const presetPath = this.destinationPath('dev.preset.json');
311
+ const raw = this._readDestinationText(presetPath);
312
+ if (raw == null) {
313
+ this.logger.verbose('dev.preset.json not found, skipping preset update');
314
+ return;
315
+ }
316
+
317
+ try {
318
+ const preset = JSON.parse(raw);
319
+ const fullApps = preset.presets?.full?.apps;
320
+
321
+ if (!Array.isArray(fullApps)) {
322
+ this.logger.verbose('dev.preset.json has no full.apps array, skipping');
323
+ return;
324
+ }
325
+
326
+ if (fullApps.includes(this.appName)) {
327
+ this.logger.verbose(`"${this.appName}" already in full preset, skipping`);
328
+ return;
329
+ }
330
+
331
+ fullApps.push(this.appName);
332
+ this._writeDestinationText(presetPath, `${JSON.stringify(preset, null, 2)}\n`);
333
+
334
+ this.log(` 📝 已将 "${this.appName}" 添加到 dev.preset.json 的 full 预设中`);
335
+ } catch (e) {
336
+ console.warn(` ⚠️ 更新 dev.preset.json 失败: ${e.message}`);
337
+ }
338
+ }
339
+
340
+ _updatePageConfig() {
341
+ const pageConfigPath = this.destinationPath('page.config.ts');
342
+ const content = this._readDestinationText(pageConfigPath);
343
+ if (content == null) {
344
+ this.logger.verbose('page.config.ts not found, skipping');
345
+ return;
346
+ }
347
+
348
+ try {
349
+ if (content.includes(`${this.appName}:`)) {
350
+ this.logger.verbose(`page.config.ts already has ${this.appName}, skipping`);
351
+ return;
352
+ }
353
+
354
+ const keyLine = ` ${this.appName}: true`;
355
+ const lastBraceIdx = content.lastIndexOf('};');
356
+ if (lastBraceIdx === -1) {
357
+ this.logger.verbose('page.config.ts: no closing }; found, skipping');
358
+ return;
359
+ }
360
+
361
+ const before = content.slice(0, lastBraceIdx).trimEnd();
362
+ const after = content.slice(lastBraceIdx);
363
+ let newBefore;
364
+ if (before.endsWith('{')) {
365
+ newBefore = `${before}\n${keyLine}\n`;
366
+ } else {
367
+ newBefore = `${before},\n${keyLine}\n`;
368
+ }
369
+
370
+ this._writeDestinationText(pageConfigPath, newBefore + after);
371
+ this.log(` 📝 已将 "${this.appName}" 写入 page.config.ts`);
372
+ } catch (e) {
373
+ console.warn(` ⚠️ 更新 page.config.ts 失败: ${e.message}`);
374
+ }
375
+ }
376
+
377
+ install() {
378
+ if (this._skipInstall || this.skipInstall) return;
379
+
380
+ this.log('');
381
+ this.log('📦 正在安装依赖...');
382
+ try {
383
+ this.spawnCommandSync('pnpm', ['install', '--filter', this.appName], {
384
+ cwd: this.monorepoRoot,
385
+ });
386
+ } catch (e) {
387
+ this._installFailed = true;
388
+ this.log('');
389
+ this.log('⚠️ 依赖安装失败,但子应用文件已全部生成。');
390
+ this.logger.verbose('Install error:', e.message);
391
+ }
392
+ }
393
+
394
+ end() {
395
+ if (this._skipInstall || this.skipEndMessage) return;
396
+
397
+ this.log('');
398
+
399
+ if (this._installFailed) {
400
+ this.log('⚠️ 子应用文件已创建,但依赖安装未完成。');
401
+ this.log('');
402
+ this.log(' 请手动安装依赖:');
403
+ this.log('');
404
+ this.log(' pnpm install');
405
+ this.log('');
406
+ this.log(' 如果安装仍然失败,可尝试:');
407
+ this.log(' pnpm install --network-concurrency 4');
408
+ } else {
409
+ this.log('✅ 子应用创建成功!');
410
+ }
411
+
412
+ this.log('');
413
+ this.log(' 后续步骤:');
414
+ this.log('');
415
+ this.log(` cd apps/${this.appName}`);
416
+ this.log(' pnpm dev');
417
+ this.log('');
418
+ if (!this.composedFromH5React) {
419
+ this.log(' 继续在 monorepo 根目录新增子应用:');
420
+ this.log(' mico create subapp-h5');
421
+ this.log('');
422
+ }
423
+ }
424
+ };
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "subapp-h5",
3
+ "description": "在 H5 Monorepo 的 apps/ 下新增 Umi 4 子应用",
4
+ "usage": "cd <h5-monorepo-root> && mico create subapp-h5",
5
+ "features": [
6
+ "写入 page.config.ts 与 dev.preset.json",
7
+ "Tailwind + @umijs/plugins 模板",
8
+ "与仓库 packageScope / workspace 包对齐"
9
+ ]
10
+ }
@@ -0,0 +1 @@
1
+ PORT=<%= devPort %>
@@ -0,0 +1,22 @@
1
+ module.exports = {
2
+ extends: [require.resolve('umi/stylelint')],
3
+ rules: {
4
+ 'at-rule-no-unknown': [
5
+ true,
6
+ {
7
+ ignoreAtRules: [
8
+ 'tailwind',
9
+ 'apply',
10
+ 'layer',
11
+ 'config',
12
+ 'plugin',
13
+ 'theme',
14
+ 'utility',
15
+ 'source',
16
+ 'custom-variant',
17
+ ],
18
+ },
19
+ ],
20
+ },
21
+ ignoreFiles: ["dist/**/*", "node_modules/**/*"],
22
+ };
@@ -0,0 +1,7 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createDevConfig } from '<%= packageScope %>/umi-config/config.dev';
4
+
5
+ export default createDevConfig({
6
+ // custom config
7
+ });
@@ -0,0 +1,7 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createDevelopmentConfig } from '<%= packageScope %>/umi-config/config.prod.development';
4
+
5
+ export default createDevelopmentConfig({
6
+ // custom config
7
+ });
@@ -0,0 +1,10 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createProductionConfig } from '<%= packageScope %>/umi-config/config.prod.production';
4
+
5
+ export default createProductionConfig(
6
+ { appName: '<%= appName %>' },
7
+ {
8
+ // custom config
9
+ },
10
+ );
@@ -0,0 +1,7 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createTestingConfig } from '<%= packageScope %>/umi-config/config.prod.testing';
4
+
5
+ export default createTestingConfig({
6
+ // custom config
7
+ });
@@ -0,0 +1,7 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createProConfig } from '<%= packageScope %>/umi-config/config.prod';
4
+
5
+ export default createProConfig({
6
+ // custom config
7
+ });
@@ -0,0 +1,6 @@
1
+ // https://umijs.org/config/
2
+
3
+ import { createBaseConfig } from '<%= packageScope %>/umi-config/config';
4
+ import routes from './routes';
5
+
6
+ export default createBaseConfig({ routes });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @name 基础路由配置
3
+ * @description 这里只配置静态的基础路由,动态路由从 window.__MICO_MENUS__ 获取
4
+ * @doc https://umijs.org/docs/guides/routes
5
+ * @note Umi Max 会自动使用 src/layouts/index.tsx 作为全局布局,无需显式配置
6
+ */
7
+ export default [
8
+ {
9
+ path: '/',
10
+ component: 'index',
11
+ name: '首页',
12
+ },
13
+ ];
@@ -0,0 +1,12 @@
1
+ import baseConfig from '<%= packageScope %>/eslint/react';
2
+ import { defineConfig } from 'eslint/config';
3
+ export default defineConfig([
4
+ ...baseConfig,
5
+ {
6
+ languageOptions: {
7
+ parserOptions: {
8
+ tsconfigRootDir: import.meta.dirname,
9
+ },
10
+ },
11
+ },
12
+ ]);
@@ -0,0 +1,34 @@
1
+ import mockjs from 'mockjs';
2
+ import { z } from 'zod';
3
+ import { defineMock } from 'umi';
4
+ import { baseUrl, tryProxy, validateBody } from '<%= packageScope %>/utils/mock';
5
+
6
+ export default defineMock({
7
+ [`POST ${baseUrl}/user`]: async (req, res) => {
8
+ // 参数校验
9
+ const schema = z.object({
10
+ email: z.string(),
11
+ })
12
+ if (!validateBody(req.body, schema, res)) return;
13
+
14
+ if (await tryProxy(req, res)) return;
15
+
16
+ setTimeout(() => {
17
+ res.json(mockjs.mock({
18
+ 'code': 200,
19
+ 'message': '@cword(10, 20)',
20
+ 'content': {
21
+ 'token': {
22
+ 'accessToken': '@guid()',
23
+ 'refreshToken': '@guid()',
24
+ },
25
+ 'thirdProfile': {
26
+ 'name': '@cname()',
27
+ 'email': '@email()',
28
+ 'phone': /^1(3|5|8|7|9)\d{9}$/,
29
+ },
30
+ },
31
+ }));
32
+ }, 500);
33
+ },
34
+ });
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "<%= appName %>",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "author": "<%= author %>",
6
+ "scripts": {
7
+ "dev": "umi dev",
8
+ "build": "npm run build:production",
9
+ "build:development": "cross-env UMI_ENV=development umi build",
10
+ "build:production": "cross-env UMI_ENV=production umi build",
11
+ "build:testing": "cross-env UMI_ENV=testing umi build",
12
+ "postinstall": "umi setup",
13
+ "setup": "umi setup",
14
+ "start": "npm run dev",
15
+ "lint": "eslint && stylelint \"**/*.{css,less}\"",
16
+ "lint:fix": "eslint --fix && stylelint \"**/*.{css,less}\" --fix"
17
+ },
18
+ "dependencies": {
19
+ "<%= packageScope %>/common-intl": "workspace:^",
20
+ "<%= packageScope %>/components": "workspace:^",
21
+ "<%= packageScope %>/constant": "workspace:^",
22
+ "<%= packageScope %>/deeplink": "workspace:^",
23
+ "<%= packageScope %>/domain": "workspace:^",
24
+ "<%= packageScope %>/js-bridge": "workspace:^",
25
+ "<%= packageScope %>/request": "workspace:^",
26
+ "<%= packageScope %>/umi-config": "workspace:^",
27
+ "<%= packageScope %>/utils": "workspace:^",
28
+ "react": "catalog:",
29
+ "react-dom": "catalog:",
30
+ "umi": "catalog:"
31
+ },
32
+ "devDependencies": {
33
+ "@common-web/sentry": "catalog:",
34
+ "<%= packageScope %>/eslint": "workspace:^",
35
+ "@types/react": "catalog:",
36
+ "@types/react-dom": "catalog:",
37
+ "@umijs/plugins": "catalog:",
38
+ "cross-env": "catalog:",
39
+ "tailwindcss": "catalog:",
40
+ "tailwindcss-safe-area": "catalog:"
41
+ }
42
+ }
@@ -0,0 +1,14 @@
1
+ import { fetchMultilingualData } from '@/intl';
2
+
3
+ export function render(oldRender: () => void) {
4
+ /**
5
+ * 在应用渲染前加载多语言数据,可根据需要自己调整逻辑
6
+ */
7
+ fetchMultilingualData()
8
+ .then(oldRender)
9
+ .catch((error: Error) => {
10
+ console.error('获取多语言文案失败', error);
11
+ // 即使失败也继续渲染,使用兜底文案
12
+ oldRender();
13
+ });
14
+ }
@@ -0,0 +1,37 @@
1
+ import { addIntl, init } from '<%= packageScope %>/common-intl';
2
+
3
+ const { fetchMultilingualData, i18n } = init({
4
+ tag: '<%= appName %>',
5
+ indexedDBParams: { dbName: 'i18n_<%= appName %>' },
6
+ });
7
+
8
+ /**
9
+ * 1. 调用方法
10
+ * fetchMultilingualData();
11
+ *
12
+ * 2. 使用文案
13
+ * import intl from '@/intl';
14
+ * intl.common_hello();
15
+ * intl.common_welcome_aa('张三');
16
+ */
17
+ const intl = addIntl(
18
+ {
19
+ // ============ 示例文案(可删除) ============
20
+ common_hello: () =>
21
+ i18n({
22
+ key: 'common_hello',
23
+ defaultMessage: '你好',
24
+ }),
25
+ common_welcome_aa: (name: string) =>
26
+ i18n({
27
+ key: 'common_welcome_aa',
28
+ interpolations: [name],
29
+ defaultMessage: `欢迎, ${name}`,
30
+ }),
31
+ },
32
+ i18n,
33
+ );
34
+
35
+ export { fetchMultilingualData };
36
+
37
+ export default intl;
@@ -0,0 +1,10 @@
1
+ import { SentryErrorBoundary } from '@common-web/sentry';
2
+ import { Outlet } from 'umi';
3
+
4
+ export default function Layout() {
5
+ return (
6
+ <SentryErrorBoundary>
7
+ <Outlet />
8
+ </SentryErrorBoundary>
9
+ );
10
+ }