generator-mico-cli 0.2.30 → 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 (183) hide show
  1. package/README.md +146 -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/README.md +34 -0
  137. package/generators/micro-react/index.js +2 -0
  138. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +21 -1
  139. package/generators/micro-react/templates/apps/layout/config/config.ts +0 -15
  140. package/generators/micro-react/templates/apps/layout/docs/arch-/346/227/245/345/277/227/344/270/216/345/270/270/351/207/217.md +16 -8
  141. package/generators/micro-react/templates/apps/layout/docs/feat-/346/236/204/345/273/272define/344/270/216/345/205/215/350/256/244/350/257/201/345/210/235/345/247/213/346/200/201.md +49 -3
  142. package/generators/micro-react/templates/apps/layout/docs/feature-/350/217/234/345/215/225/346/235/203/351/231/220/346/216/247/345/210/266.md +3 -1
  143. package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/346/235/203/351/231/220/346/227/245/345/277/227.md +4 -4
  144. package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -4
  145. package/generators/micro-react/templates/apps/layout/src/common/auth/auth-check-path.ts +14 -0
  146. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +1 -0
  147. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +51 -18
  148. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +8 -5
  149. package/generators/micro-react/templates/package.json +1 -1
  150. package/generators/subapp-h5/ignore-list.json +1 -0
  151. package/generators/subapp-h5/index.js +424 -0
  152. package/generators/subapp-h5/meta.json +10 -0
  153. package/generators/subapp-h5/templates/.env +1 -0
  154. package/generators/subapp-h5/templates/.stylelintrc.js +22 -0
  155. package/generators/subapp-h5/templates/config/config.dev.ts +7 -0
  156. package/generators/subapp-h5/templates/config/config.prod.development.ts +7 -0
  157. package/generators/subapp-h5/templates/config/config.prod.production.ts +10 -0
  158. package/generators/subapp-h5/templates/config/config.prod.testing.ts +7 -0
  159. package/generators/subapp-h5/templates/config/config.prod.ts +7 -0
  160. package/generators/subapp-h5/templates/config/config.ts +6 -0
  161. package/generators/subapp-h5/templates/config/routes.ts +13 -0
  162. package/generators/subapp-h5/templates/eslint.config.ts +12 -0
  163. package/generators/subapp-h5/templates/mock/user.ts +34 -0
  164. package/generators/subapp-h5/templates/package.json +42 -0
  165. package/generators/subapp-h5/templates/src/app.tsx +14 -0
  166. package/generators/subapp-h5/templates/src/assets/yay.jpg +0 -0
  167. package/generators/subapp-h5/templates/src/intl.ts +37 -0
  168. package/generators/subapp-h5/templates/src/layouts/index.tsx +10 -0
  169. package/generators/subapp-h5/templates/src/pages/index.tsx +22 -0
  170. package/generators/subapp-h5/templates/src/services/user.ts +38 -0
  171. package/generators/subapp-h5/templates/tailwind.config.js +16 -0
  172. package/generators/subapp-h5/templates/tailwind.css +7 -0
  173. package/generators/subapp-h5/templates/tsconfig.json +3 -0
  174. package/generators/subapp-h5/templates/typings.d.ts +1 -0
  175. package/generators/subapp-react/README.md +43 -0
  176. package/generators/subapp-react/index.js +2 -0
  177. package/generators/subapp-react/templates/homepage/README.md +5 -1
  178. package/generators/subapp-react/templates/homepage/config/config.dev.ts +20 -0
  179. package/generators/subapp-react/templates/homepage/config/config.ts +0 -15
  180. package/generators/subapp-react/templates/homepage/src/common/logger.ts +50 -18
  181. package/generators/subapp-umd/README.md +37 -0
  182. package/lib/setup-multica-desktop.js +154 -0
  183. package/package.json +1 -1
@@ -6,6 +6,7 @@ import { clearStoredTenantCode } from './tenant';
6
6
 
7
7
  // 导出 STORAGE_KEYS 常量
8
8
  export { STORAGE_KEYS } from '../../constants';
9
+ export { resolveAuthCheckPath } from './auth-check-path';
9
10
 
10
11
  export function logout(): void {
11
12
  clearStoredTokens();
@@ -1,37 +1,70 @@
1
1
  /**
2
2
  * 日志工具
3
- * 仅在开发环境下输出日志,生产环境自动静默
3
+ * - 开发环境:全量输出
4
+ * - 生产环境:默认仅 `error` 带前缀输出;可在**首屏 URL** 带 query 打开全量 `log` / `info` / `warn`(与 dev 一致)
4
5
  *
5
- * 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部
6
+ * 生产开启方式:首次进入页面时地址栏带 `?debug=1`(或 `debug=true` / `yes`),
7
+ * 在**本 JS 模块加载时刻**解析一次并固定,后续路由或 query 变更**不会**再切换日志级别(需整页刷新带参 URL)。
8
+ *
9
+ * **安全与合规**:`debug` 仅用于临时排障;全量日志可能在控制台暴露业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;若接入日志采集需自行脱敏。
10
+ *
11
+ * 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部(在开启详细日志时)
6
12
  */
7
13
 
8
14
  const isDev = process.env.NODE_ENV === 'development';
9
15
 
10
- // 空操作函数(生产环境使用)
11
- const noop = (): void => {};
16
+ /** Query 参数名;勿随意改名以免已分享的排障链接失效 */
17
+ export const DEBUG_LOGS_QUERY_KEY = 'debug';
12
18
 
13
- /**
14
- * 创建带前缀的日志函数
15
- * 使用 bind 绑定前缀,保持控制台显示正确的调用位置
16
- */
17
- const createLogger = (prefix: string) => {
18
- const formattedPrefix = `[${prefix}]`;
19
- if (!isDev) {
20
- // 生产环境返回空操作,但 error 仍然输出(便于生产排查)
21
- return {
22
- log: noop,
23
- info: noop,
24
- warn: noop,
25
- error: console.error.bind(console, new Error(formattedPrefix)),
26
- };
19
+ /** 模块初始化时读取一次 `location.search`,之后不变 */
20
+ function readVerboseDebugQueryOnce(): boolean {
21
+ if (typeof window === 'undefined') {
22
+ return false;
23
+ }
24
+ try {
25
+ const v = new URLSearchParams(window.location.search).get(DEBUG_LOGS_QUERY_KEY);
26
+ if (v == null || v === '') {
27
+ return false;
28
+ }
29
+ const normalized = v.toLowerCase();
30
+ return normalized === '1' || normalized === 'true' || normalized === 'yes';
31
+ } catch {
32
+ return false;
27
33
  }
34
+ }
35
+
36
+ /** 开发环境,或生产环境首屏 URL 已带 `debug` 开关(模块加载时确定);供业务侧按需分支 */
37
+ export const isLayoutDebugLogsEnabled: boolean = isDev || readVerboseDebugQueryOnce();
38
+
39
+ const noop = (): void => {};
28
40
 
41
+ function createVerboseHandlers(formattedPrefix: string) {
29
42
  return {
30
43
  log: console.log.bind(console, formattedPrefix),
31
44
  info: console.info.bind(console, formattedPrefix),
32
45
  warn: console.warn.bind(console, formattedPrefix),
33
46
  error: console.error.bind(console, formattedPrefix),
34
47
  };
48
+ }
49
+
50
+ function createQuietHandlers(formattedPrefix: string) {
51
+ return {
52
+ log: noop,
53
+ info: noop,
54
+ warn: noop,
55
+ error: console.error.bind(console, formattedPrefix),
56
+ };
57
+ }
58
+
59
+ /**
60
+ * 创建带前缀的日志函数
61
+ * 使用 bind 绑定前缀,保持控制台显示正确的调用位置(在开启详细日志时)
62
+ */
63
+ const createLogger = (prefix: string) => {
64
+ const formattedPrefix = `[${prefix}]`;
65
+ return isLayoutDebugLogsEnabled
66
+ ? createVerboseHandlers(formattedPrefix)
67
+ : createQuietHandlers(formattedPrefix);
35
68
  };
36
69
 
37
70
  // 预定义的日志实例
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { request as rawRequest } from '@umijs/max';
6
+ import { resolveAuthCheckPath } from '@/common/auth/auth-check-path';
6
7
  import {
7
8
  maybePersistTokens,
8
9
  setStoredAuthToken,
@@ -59,14 +60,16 @@ const performSsoRedirect = (): void => {
59
60
  export const handleAuthFailureRedirect = (): void => {
60
61
  if (typeof window === 'undefined') return;
61
62
 
62
- // 如果当前路由在免认证列表中,不触发 SSO 重定向
63
- if (isNoAuthRoute(window.location.pathname)) {
64
- console.log('[SSO] 当前路由在免认证列表中,跳过 SSO 重定向');
63
+ const authCheckPath = resolveAuthCheckPath(window.location.pathname);
64
+
65
+ // getInitialState 一致:用 authCheckPath 判断,避免「/ + defaultPath」首屏仍按 / 误判为免认证
66
+ if (isNoAuthRoute(authCheckPath)) {
67
+ console.log('[SSO] 当前路由在免认证列表中,跳过 SSO 重定向', { authCheckPath });
65
68
  return;
66
69
  }
67
70
 
68
- if (isPageAuthFree(window.location.pathname)) {
69
- console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向');
71
+ if (isPageAuthFree(authCheckPath)) {
72
+ console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向', { authCheckPath });
70
73
  return;
71
74
  }
72
75
 
@@ -32,7 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@commitlint/cli": "^19.5.0",
34
34
  "@commitlint/config-conventional": "^19.5.0",
35
- "@common-web/sentry": "0.0.7",
35
+ "@common-web/sentry": "0.0.8",
36
36
  "@sentry/webpack-plugin": "^4.9.1",
37
37
  "@typescript-eslint/eslint-plugin": "^8.54.0",
38
38
  "@typescript-eslint/parser": "^8.54.0",
@@ -0,0 +1 @@
1
+ ["node_modules", "dist", ".umi", ".umi-production", ".turbo"]
@@ -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
+ ];