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
@@ -0,0 +1,83 @@
1
+ import http from 'http';
2
+ import httpProxy from 'http-proxy';
3
+ import { ZodSchema } from 'zod';
4
+
5
+ export const baseUrl = (() => {
6
+ const apiBaseUrl = process.env.UMI_APP_API_BASE_URL ?? '';
7
+ if (apiBaseUrl.endsWith('/')) {
8
+ return apiBaseUrl.slice(0, -1);
9
+ }
10
+ return apiBaseUrl;
11
+ })();
12
+
13
+ const isHttpProxy = (url: string | null) => {
14
+ if (!url) {
15
+ return false;
16
+ }
17
+ return /^https?:\/\//.test(url);
18
+ };
19
+
20
+ const proxyRequest = async (
21
+ remote: string,
22
+ req: http.IncomingMessage,
23
+ res: http.ServerResponse,
24
+ ) => {
25
+ const body = req.body as Record<string, unknown>;
26
+ const proxy = httpProxy.createProxyServer();
27
+ proxy.on('proxyReq', (proxyReq, req) => {
28
+ if (!body) {
29
+ return;
30
+ }
31
+ const bodyJson = JSON.stringify(body);
32
+ proxyReq.setHeader('Content-Type', req.headers['content-type'] ?? '');
33
+ proxyReq.setHeader(
34
+ 'Content-Length',
35
+ Buffer.byteLength(bodyJson).toString(),
36
+ );
37
+ proxyReq.write(bodyJson);
38
+ });
39
+ req.url = req.url!.replace(baseUrl, '');
40
+ proxy.web(req, res, {
41
+ target: remote,
42
+ changeOrigin: true,
43
+ });
44
+ };
45
+
46
+ /**
47
+ * 如果检测到 remote 代理参数,则透传请求并返回 true;否则返回 false 走 mock 逻辑。
48
+ */
49
+ export const tryProxy = async (
50
+ req: http.IncomingMessage,
51
+ res: http.ServerResponse,
52
+ ) => {
53
+ const referer = req.headers.referer;
54
+ if (referer) {
55
+ const url = new URL(referer);
56
+ const remote = url.searchParams.get('remote');
57
+ if (remote && isHttpProxy(remote)) {
58
+ await proxyRequest(remote, req, res);
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ };
64
+
65
+ /**
66
+ * 校验请求体,失败时自动返回 400 响应。
67
+ * 返回 true 表示校验通过,false 表示已响应错误。
68
+ */
69
+ export const validateBody = (
70
+ body: unknown,
71
+ schema: ZodSchema,
72
+ res: http.ServerResponse,
73
+ ): boolean => {
74
+ const result = schema.safeParse(body);
75
+ if (!result.success) {
76
+ const message = result.error.issues
77
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
78
+ .join('\n');
79
+ res.status(400).json({ code: 400, message });
80
+ return false;
81
+ }
82
+ return true;
83
+ };
@@ -0,0 +1,23 @@
1
+ const defaultFormatter = new Intl.NumberFormat('en-US');
2
+
3
+ /**
4
+ * 格式化数字
5
+ * @param number 数字
6
+ * @returns 格式化后的数字
7
+ */
8
+ export function formatNumber(
9
+ value: number | bigint,
10
+ options?: Intl.NumberFormatOptions,
11
+ locale: string | string[] = 'en-US',
12
+ ) {
13
+ if (value == null || (typeof value === 'number' && !Number.isFinite(value))) {
14
+ return '';
15
+ }
16
+ if (
17
+ !options &&
18
+ (locale === 'en-US' || (Array.isArray(locale) && locale[0] === 'en-US'))
19
+ ) {
20
+ return defaultFormatter.format(value);
21
+ }
22
+ return new Intl.NumberFormat(locale, options).format(value);
23
+ }
@@ -0,0 +1,12 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ export * from 'class-variance-authority';
4
+
5
+ /**
6
+ * 合并 Tailwind 类名并解决冲突
7
+ * @param inputs 类名
8
+ * @returns 合并后的类名
9
+ */
10
+ export function cn(...inputs: ClassValue[]) {
11
+ return twMerge(clsx(inputs));
12
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 获取URL参数
3
+ * @param url
4
+ * @returns URL参数对象
5
+ */
6
+ export function getURLParams(url: string = window.location.href) {
7
+ const urlObject = new URL(url);
8
+ return urlObject.searchParams;
9
+ }
10
+
11
+ /**
12
+ * 格式化URL
13
+ * @param url
14
+ * @returns 格式化后的URL
15
+ */
16
+ export function formatURL(url: string) {
17
+ const urlObject = new URL(url, location.origin);
18
+ return urlObject.toString();
19
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "<%= packageScope %>/typescript",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "<%= packageScope %>/utils/*": ["./src/*"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ export default {};
@@ -0,0 +1,17 @@
1
+ packages:
2
+ - apps/*
3
+ - packages/*
4
+
5
+ catalog:
6
+ umi: ^4
7
+ react: ^18
8
+ react-dom: ^18
9
+ "@types/react": ^18
10
+ "@types/react-dom": ^18
11
+ tailwindcss: ^3
12
+ tailwindcss-safe-area: 0.8.0
13
+ "@umijs/plugins": ^4
14
+ eslint: ^9
15
+ stylelint: ^14
16
+ cross-env: "*"
17
+ "@common-web/sentry": "^0.0.4"
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('node:fs/promises');
3
+ const path = require('node:path');
4
+
5
+ /**
6
+ * 收集所有子应用的构建产物到根目录的 dist 目录
7
+ * 将 apps/{app}/dist 移动到 dist/{app}/
8
+ */
9
+ async function collectDist() {
10
+ const rootDir = path.resolve(__dirname, '..');
11
+ const appsDir = path.join(rootDir, 'apps');
12
+ const distDir = path.join(rootDir, 'dist');
13
+
14
+ try {
15
+ // 确保根目录的 dist 目录存在
16
+ await fs.mkdir(distDir, { recursive: true });
17
+
18
+ // 读取 apps 目录下的所有子目录
19
+ const entries = await fs.readdir(appsDir, { withFileTypes: true });
20
+ const apps = entries.filter((entry) => entry.isDirectory());
21
+
22
+ if (apps.length === 0) {
23
+ console.log('未找到任何子应用');
24
+ return;
25
+ }
26
+
27
+ console.log('开始收集构建产物...\n');
28
+
29
+ for (const app of apps) {
30
+ const appName = app.name;
31
+ const appDistPath = path.join(appsDir, appName, 'dist');
32
+ const targetDistPath = path.join(distDir, appName);
33
+
34
+ try {
35
+ // 检查源 dist 目录是否存在
36
+ const stat = await fs.stat(appDistPath);
37
+ if (!stat.isDirectory()) {
38
+ console.log(`跳过 ${appName}: dist 不是目录`);
39
+ continue;
40
+ }
41
+
42
+ // 检查目标目录是否已存在,如果存在则先删除
43
+ try {
44
+ await fs.rm(targetDistPath, { recursive: true, force: true });
45
+ } catch {
46
+ // 忽略删除错误
47
+ }
48
+
49
+ // 移动 dist 目录到目标位置
50
+ await fs.rename(appDistPath, targetDistPath);
51
+
52
+ // 删除 .map 文件(sourcemap 已由 webpack 插件上传到 Sentry,无需部署到 CDN)
53
+ const files = await fs.readdir(targetDistPath);
54
+ for (const file of files) {
55
+ if (file.endsWith('.map')) {
56
+ await fs.unlink(path.join(targetDistPath, file));
57
+ console.log(` 🗑 已删除 sourcemap: ${appName}/${file}`);
58
+ }
59
+ }
60
+
61
+ console.log(`✓ ${appName}: apps/${appName}/dist -> dist/${appName}/`);
62
+ } catch (error) {
63
+ if (error.code === 'ENOENT') {
64
+ console.log(`跳过 ${appName}: 未找到 dist 目录`);
65
+ } else {
66
+ console.error(`处理 ${appName} 时出错: ${error.message}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ console.log('\n构建产物收集完成!');
72
+ } catch (error) {
73
+ console.error(`收集构建产物失败: ${error.message}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ collectDist();
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 多应用并行启动脚本
4
+ *
5
+ * 用法:
6
+ * pnpm dev:preset # 使用默认预设
7
+ * pnpm dev:preset <preset> # 使用指定预设
8
+ * pnpm dev:preset --list # 列出所有预设
9
+ * pnpm dev:preset --config # 显示配置文件路径
10
+ */
11
+ const fs = require('node:fs/promises');
12
+ const path = require('node:path');
13
+ const { spawn } = require('node:child_process');
14
+
15
+ const CONFIG_FILE = 'dev.preset.json';
16
+ const ROOT_DIR = path.resolve(__dirname, '..');
17
+ const APPS_DIR = path.join(ROOT_DIR, 'apps');
18
+
19
+ // 颜色代码
20
+ const colors = {
21
+ reset: '\x1b[0m',
22
+ cyan: '\x1b[36m',
23
+ yellow: '\x1b[33m',
24
+ green: '\x1b[32m',
25
+ red: '\x1b[31m',
26
+ gray: '\x1b[90m',
27
+ magenta: '\x1b[35m',
28
+ blue: '\x1b[34m',
29
+ };
30
+
31
+ // 为不同应用分配不同颜色
32
+ const appColors = [
33
+ '\x1b[36m', // cyan
34
+ '\x1b[33m', // yellow
35
+ '\x1b[32m', // green
36
+ '\x1b[35m', // magenta
37
+ '\x1b[34m', // blue
38
+ '\x1b[91m', // bright red
39
+ '\x1b[92m', // bright green
40
+ '\x1b[93m', // bright yellow
41
+ ];
42
+
43
+ async function loadConfig() {
44
+ const configPath = path.join(ROOT_DIR, CONFIG_FILE);
45
+ try {
46
+ const raw = await fs.readFile(configPath, 'utf8');
47
+ return JSON.parse(raw);
48
+ } catch (error) {
49
+ if (error.code === 'ENOENT') {
50
+ console.error(`${colors.red}错误: 未找到配置文件 ${CONFIG_FILE}${colors.reset}`);
51
+ console.error(`${colors.gray}请在项目根目录创建 ${CONFIG_FILE} 文件${colors.reset}`);
52
+ process.exit(1);
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ async function collectApps() {
59
+ const entries = await fs.readdir(APPS_DIR, { withFileTypes: true });
60
+ const apps = [];
61
+ for (const entry of entries) {
62
+ if (!entry.isDirectory()) continue;
63
+ const pkgPath = path.join(APPS_DIR, entry.name, 'package.json');
64
+ try {
65
+ const pkgRaw = await fs.readFile(pkgPath, 'utf8');
66
+ const pkgJson = JSON.parse(pkgRaw);
67
+ if (pkgJson?.scripts?.dev) {
68
+ apps.push(entry.name);
69
+ }
70
+ } catch {
71
+ // ignore
72
+ }
73
+ }
74
+ return apps;
75
+ }
76
+
77
+ async function resolveDotenvCli() {
78
+ const pnpmVirtualStore = path.join(ROOT_DIR, 'node_modules', '.pnpm');
79
+ let entries;
80
+ try {
81
+ entries = await fs.readdir(pnpmVirtualStore);
82
+ } catch {
83
+ throw new Error('未找到 pnpm 虚拟模块目录,请先执行 pnpm install');
84
+ }
85
+ const match = entries.find((entry) => entry.startsWith('dotenv-cli@'));
86
+ if (!match) {
87
+ throw new Error('未安装 dotenv-cli,请执行 pnpm install');
88
+ }
89
+ return path.join(pnpmVirtualStore, match, 'node_modules', 'dotenv-cli', 'cli.js');
90
+ }
91
+
92
+ function listPresets(config) {
93
+ console.log(`\n${colors.cyan}可用的预设列表:${colors.reset}\n`);
94
+
95
+ const defaultPreset = config.default;
96
+ for (const [name, preset] of Object.entries(config.presets)) {
97
+ const isDefault = name === defaultPreset;
98
+ const marker = isDefault ? ` ${colors.green}(默认)${colors.reset}` : '';
99
+ const desc = preset.description || '';
100
+ console.log(` ${colors.yellow}${name}${colors.reset}${marker}`);
101
+ if (desc) {
102
+ console.log(` ${colors.gray}${desc}${colors.reset}`);
103
+ }
104
+ console.log(` ${colors.gray}应用: ${preset.apps.join(', ')}${colors.reset}`);
105
+ console.log();
106
+ }
107
+
108
+ console.log(`${colors.gray}使用方式: pnpm dev:preset <预设名>${colors.reset}`);
109
+ console.log(`${colors.gray}配置文件: ${CONFIG_FILE}${colors.reset}\n`);
110
+ }
111
+
112
+ function printBanner(presetName, apps) {
113
+ const title = `dev:preset [${presetName}]`;
114
+ const appList = apps.join(', ');
115
+ console.log(`
116
+ ${colors.cyan}╭${'─'.repeat(Math.max(title.length, appList.length) + 10)}╮
117
+ │ 🚀 ${title.padEnd(Math.max(title.length, appList.length) + 5)}│
118
+ │ 📦 ${appList.padEnd(Math.max(title.length, appList.length) + 5)}│
119
+ ╰${'─'.repeat(Math.max(title.length, appList.length) + 10)}╯${colors.reset}
120
+ `);
121
+ }
122
+
123
+ function createPrefixedStream(appName, color, stream) {
124
+ const prefix = `${color}[${appName}]${colors.reset} `;
125
+ let buffer = '';
126
+
127
+ return (chunk) => {
128
+ buffer += chunk.toString();
129
+ const lines = buffer.split('\n');
130
+ buffer = lines.pop() || '';
131
+ for (const line of lines) {
132
+ stream.write(prefix + line + '\n');
133
+ }
134
+ };
135
+ }
136
+
137
+ async function startApp(appName, dotenvCli, color) {
138
+ const args = [
139
+ dotenvCli,
140
+ '-e', '.env',
141
+ '-e', '.env.local',
142
+ '-e', '.env.development',
143
+ '-e', '.env.development.local',
144
+ '--',
145
+ 'pnpm', '--filter', appName, 'run', 'dev',
146
+ ];
147
+
148
+ const child = spawn(process.execPath, args, {
149
+ stdio: ['inherit', 'pipe', 'pipe'],
150
+ cwd: ROOT_DIR,
151
+ });
152
+
153
+ const stdoutHandler = createPrefixedStream(appName, color, process.stdout);
154
+ const stderrHandler = createPrefixedStream(appName, color, process.stderr);
155
+
156
+ child.stdout.on('data', stdoutHandler);
157
+ child.stderr.on('data', stderrHandler);
158
+
159
+ child.on('error', (error) => {
160
+ console.error(`${color}[${appName}]${colors.reset} ${colors.red}启动失败: ${error.message}${colors.reset}`);
161
+ });
162
+
163
+ return child;
164
+ }
165
+
166
+ async function main() {
167
+ const args = process.argv.slice(2);
168
+ const config = await loadConfig();
169
+
170
+ // 处理 --list 参数
171
+ if (args.includes('--list') || args.includes('list')) {
172
+ listPresets(config);
173
+ return;
174
+ }
175
+
176
+ // 处理 --config 参数
177
+ if (args.includes('--config')) {
178
+ console.log(path.join(ROOT_DIR, CONFIG_FILE));
179
+ return;
180
+ }
181
+
182
+ // 确定要使用的预设
183
+ const presetName = args[0] || config.default;
184
+ if (!presetName) {
185
+ console.error(`${colors.red}错误: 未指定预设,且配置中未设置默认预设${colors.reset}`);
186
+ console.error(`${colors.gray}使用 --list 查看可用预设${colors.reset}`);
187
+ process.exit(1);
188
+ }
189
+
190
+ const preset = config.presets[presetName];
191
+ if (!preset) {
192
+ console.error(`${colors.red}错误: 未找到预设 "${presetName}"${colors.reset}`);
193
+ console.error(`${colors.gray}可用预设: ${Object.keys(config.presets).join(', ')}${colors.reset}`);
194
+ process.exit(1);
195
+ }
196
+
197
+ const appsToStart = preset.apps;
198
+ if (!appsToStart || appsToStart.length === 0) {
199
+ console.error(`${colors.red}错误: 预设 "${presetName}" 中未配置任何应用${colors.reset}`);
200
+ process.exit(1);
201
+ }
202
+
203
+ // 验证应用是否存在
204
+ const availableApps = await collectApps();
205
+ const invalidApps = appsToStart.filter((app) => !availableApps.includes(app));
206
+ if (invalidApps.length > 0) {
207
+ console.error(`${colors.red}错误: 以下应用不存在或没有 dev 脚本: ${invalidApps.join(', ')}${colors.reset}`);
208
+ console.error(`${colors.gray}可用应用: ${availableApps.join(', ')}${colors.reset}`);
209
+ process.exit(1);
210
+ }
211
+
212
+ // 显示启动 banner
213
+ printBanner(presetName, appsToStart);
214
+
215
+ // 启动所有应用
216
+ const dotenvCli = await resolveDotenvCli();
217
+ const children = [];
218
+
219
+ for (let i = 0; i < appsToStart.length; i++) {
220
+ const appName = appsToStart[i];
221
+ const color = appColors[i % appColors.length];
222
+ console.log(`${color}[${appName}]${colors.reset} 正在启动...`);
223
+ const child = await startApp(appName, dotenvCli, color);
224
+ children.push({ name: appName, process: child });
225
+ }
226
+
227
+ // 处理 Ctrl+C,同时终止所有子进程
228
+ const cleanup = (signal) => {
229
+ console.log(`\n${colors.yellow}正在停止所有应用...${colors.reset}`);
230
+ for (const { name, process: child } of children) {
231
+ if (!child.killed) {
232
+ console.log(`${colors.gray}停止 ${name}...${colors.reset}`);
233
+ child.kill(signal);
234
+ }
235
+ }
236
+ };
237
+
238
+ process.on('SIGINT', () => cleanup('SIGINT'));
239
+ process.on('SIGTERM', () => cleanup('SIGTERM'));
240
+
241
+ // 监听子进程退出
242
+ let exitedCount = 0;
243
+ for (const { name, process: child } of children) {
244
+ child.on('exit', (code, signal) => {
245
+ exitedCount++;
246
+ if (signal) {
247
+ console.log(`${colors.gray}[${name}] 被信号 ${signal} 终止${colors.reset}`);
248
+ } else if (code !== 0) {
249
+ console.log(`${colors.red}[${name}] 退出,代码: ${code}${colors.reset}`);
250
+ } else {
251
+ console.log(`${colors.green}[${name}] 正常退出${colors.reset}`);
252
+ }
253
+
254
+ // 所有进程都退出后,主进程也退出
255
+ if (exitedCount === children.length) {
256
+ process.exit(0);
257
+ }
258
+ });
259
+ }
260
+ }
261
+
262
+ main().catch((error) => {
263
+ console.error(`${colors.red}执行失败: ${error.message}${colors.reset}`);
264
+ process.exit(1);
265
+ });
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Dev Preset Configuration",
4
+ "description": "多应用并行启动预设配置",
5
+ "type": "object",
6
+ "required": ["presets"],
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "JSON Schema 引用"
11
+ },
12
+ "presets": {
13
+ "type": "object",
14
+ "description": "预设配置列表",
15
+ "additionalProperties": {
16
+ "type": "object",
17
+ "required": ["apps"],
18
+ "properties": {
19
+ "description": {
20
+ "type": "string",
21
+ "description": "预设描述"
22
+ },
23
+ "apps": {
24
+ "type": "array",
25
+ "description": "要启动的应用列表",
26
+ "items": {
27
+ "type": "string"
28
+ },
29
+ "minItems": 0
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "default": {
35
+ "type": "string",
36
+ "description": "默认使用的预设名称"
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('node:fs/promises');
3
+ const path = require('node:path');
4
+ const readline = require('node:readline/promises');
5
+ const { stdin: input, stdout: output } = require('node:process');
6
+ const { spawn } = require('node:child_process');
7
+
8
+ async function collectApps(appsDir) {
9
+ const entries = await fs.readdir(appsDir, { withFileTypes: true });
10
+ const apps = [];
11
+ for (const entry of entries) {
12
+ if (!entry.isDirectory()) continue;
13
+ const appName = entry.name;
14
+ const pkgPath = path.join(appsDir, appName, 'package.json');
15
+ try {
16
+ const pkgRaw = await fs.readFile(pkgPath, 'utf8');
17
+ const pkgJson = JSON.parse(pkgRaw);
18
+ if (pkgJson?.scripts?.dev) {
19
+ apps.push({ name: appName });
20
+ }
21
+ } catch {
22
+ // ignore folders without package.json
23
+ }
24
+ }
25
+ return apps;
26
+ }
27
+
28
+ async function resolveDotenvCli() {
29
+ const pnpmVirtualStore = path.resolve(
30
+ __dirname,
31
+ '..',
32
+ 'node_modules',
33
+ '.pnpm',
34
+ );
35
+ let entries;
36
+ try {
37
+ entries = await fs.readdir(pnpmVirtualStore);
38
+ } catch (_error) {
39
+ throw new Error('未找到 pnpm 虚拟模块目录,请先执行 pnpm install');
40
+ }
41
+ const match = entries.find((entry) => entry.startsWith('dotenv-cli@'));
42
+ if (!match) {
43
+ throw new Error('未安装 dotenv-cli,请执行 pnpm install 以继续');
44
+ }
45
+ const cliPath = path.join(
46
+ pnpmVirtualStore,
47
+ match,
48
+ 'node_modules',
49
+ 'dotenv-cli',
50
+ 'cli.js',
51
+ );
52
+ return cliPath;
53
+ }
54
+
55
+ async function askUser(apps) {
56
+ output.write('请选择要启动的应用:\n');
57
+ apps.forEach((app, idx) => {
58
+ output.write(` ${idx + 1}. ${app.name}\n`);
59
+ });
60
+ const rl = readline.createInterface({ input, output });
61
+ try {
62
+ while (true) {
63
+ const answer = await rl.question('请输入序号后回车: ');
64
+ const index = Number.parseInt(answer, 10);
65
+ if (!Number.isNaN(index) && index >= 1 && index <= apps.length) {
66
+ return apps[index - 1];
67
+ }
68
+ output.write('无效的选择, 请重新输入。\n');
69
+ }
70
+ } finally {
71
+ rl.close();
72
+ }
73
+ }
74
+
75
+ async function runDev(appName) {
76
+ const dotenvCli = await resolveDotenvCli();
77
+ const args = [
78
+ dotenvCli,
79
+ '-e',
80
+ '.env',
81
+ '-e',
82
+ '.env.local',
83
+ '-e',
84
+ '.env.development',
85
+ '-e',
86
+ '.env.development.local',
87
+ '--',
88
+ 'pnpm',
89
+ '--filter',
90
+ appName,
91
+ 'run',
92
+ 'dev',
93
+ ];
94
+ const child = spawn(process.execPath, args, {
95
+ stdio: 'inherit',
96
+ shell: false,
97
+ });
98
+ child.on('exit', (code, signal) => {
99
+ if (signal) {
100
+ process.kill(process.pid, signal);
101
+ return;
102
+ }
103
+ process.exit(code ?? 1);
104
+ });
105
+ child.on('error', (error) => {
106
+ console.error(`启动失败: ${error.message}`);
107
+ process.exit(1);
108
+ });
109
+ }
110
+
111
+ async function main() {
112
+ try {
113
+ const appsDir = path.resolve(__dirname, '..', 'apps');
114
+ const apps = await collectApps(appsDir);
115
+ if (apps.length === 0) {
116
+ console.error('未找到可启动的应用。');
117
+ process.exit(1);
118
+ }
119
+ let targetApp;
120
+ if (apps.length === 1) {
121
+ output.write(`检测到单一应用 ${apps[0].name}, 正在启动...\n`);
122
+ targetApp = apps[0];
123
+ } else {
124
+ targetApp = await askUser(apps);
125
+ }
126
+ await runDev(targetApp.name);
127
+ } catch (error) {
128
+ console.error(`执行失败: ${error.message}`);
129
+ process.exit(1);
130
+ }
131
+ }
132
+
133
+ main();