generator-mico-cli 0.2.22 → 0.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/bin/mico.js +15 -2
- package/generators/micro-react/index.js +44 -6
- package/generators/micro-react/meta.json +2 -1
- package/generators/micro-react/templates/_gitignore +3 -1
- package/generators/micro-react/templates/_npmrc +1 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/345/276/256/345/211/215/347/253/257/346/250/241/345/274/217.md +0 -1
- 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 +9 -8
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +3 -3
- package/generators/micro-react/templates/apps/layout/package.json +1 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +5 -2
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +1 -5
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +12 -16
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +2 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +7 -8
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +5 -1
- package/generators/micro-react/templates/package.json +1 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
- package/generators/subapp-react/index.js +46 -4
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +0 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -2
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -2
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +9 -2
- package/generators/subapp-react/templates/homepage/package.json +1 -0
- package/generators/subapp-react/templates/homepage/src/app.tsx +6 -0
- package/generators/subapp-umd/ignore-list.json +5 -0
- package/generators/subapp-umd/index.js +325 -0
- package/generators/subapp-umd/meta.json +11 -0
- package/generators/subapp-umd/templates/README.md +94 -0
- package/generators/subapp-umd/templates/package.json +35 -0
- package/generators/subapp-umd/templates/public/index.html +34 -0
- package/generators/subapp-umd/templates/src/App.less +15 -0
- package/generators/subapp-umd/templates/src/App.tsx +13 -0
- package/generators/subapp-umd/templates/src/index.ts +2 -0
- package/generators/subapp-umd/templates/tsconfig.json +27 -0
- package/generators/subapp-umd/templates/webpack.config.js +68 -0
- package/lib/utils.js +2 -1
- package/package.json +1 -1
|
@@ -2,12 +2,12 @@ import type { ParsedMenuItem } from '@/common/menu';
|
|
|
2
2
|
import { filterMenuItems, parseMenuItems } from '@/common/menu';
|
|
3
3
|
import { getMenus } from '@/common/portal-data';
|
|
4
4
|
import IconFont from '@/components/IconFont';
|
|
5
|
-
import { isAuthDisabled
|
|
5
|
+
import { isAuthDisabled } from '@/constants';
|
|
6
6
|
import { useMenuState } from '@/hooks/useMenuState';
|
|
7
7
|
import { useTheme } from '@/hooks/useTheme';
|
|
8
8
|
import { Layout, Menu } from '@mico-platform/ui';
|
|
9
9
|
import * as Icons from '@mico-platform/ui/icon';
|
|
10
|
-
import {
|
|
10
|
+
import { useModel } from '@umijs/max';
|
|
11
11
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
12
12
|
import './index.less';
|
|
13
13
|
|
|
@@ -115,16 +115,15 @@ interface LayoutMenuProps {
|
|
|
115
115
|
const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
116
116
|
const siderRef = useRef<HTMLDivElement>(null);
|
|
117
117
|
const { isDark } = useTheme();
|
|
118
|
-
const location = useLocation();
|
|
119
|
-
|
|
120
118
|
const { initialState } = useModel('@@initialState');
|
|
121
119
|
const currentUser = initialState?.currentUser;
|
|
122
120
|
|
|
123
121
|
// Parse menu data
|
|
124
|
-
//
|
|
122
|
+
// isMenuAllowed 内部已对每个菜单项单独检查 isNoPermissionRoute,
|
|
123
|
+
// 无需在此按当前页面路径全局跳过过滤,避免菜单可见性随页面变化
|
|
125
124
|
const menuItems = useMemo(() => {
|
|
126
125
|
const menus = getMenus();
|
|
127
|
-
if (isAuthDisabled()
|
|
126
|
+
if (isAuthDisabled()) {
|
|
128
127
|
return parseMenuItems(menus);
|
|
129
128
|
}
|
|
130
129
|
const filteredMenus = filterMenuItems(menus, {
|
|
@@ -132,7 +131,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
132
131
|
sideMenus: (currentUser?.side_menus || []) as string[],
|
|
133
132
|
});
|
|
134
133
|
return parseMenuItems(filteredMenus);
|
|
135
|
-
}, [currentUser?.is_superuser, currentUser?.side_menus
|
|
134
|
+
}, [currentUser?.is_superuser, currentUser?.side_menus]);
|
|
136
135
|
|
|
137
136
|
// 使用菜单状态 Hook
|
|
138
137
|
const {
|
|
@@ -177,7 +176,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
177
176
|
onCollapse={handleCollapsed}
|
|
178
177
|
collapsible
|
|
179
178
|
breakpoint="xl"
|
|
180
|
-
className="
|
|
179
|
+
className="layout-web-sider"
|
|
181
180
|
trigger={clickTriggerBtn}
|
|
182
181
|
theme={isDark ? 'dark' : 'light'}
|
|
183
182
|
>
|
|
@@ -42,7 +42,11 @@ export async function fetchUserInfo(): Promise<IUserInfo> {
|
|
|
42
42
|
skipProxy: true,
|
|
43
43
|
});
|
|
44
44
|
if (response.code === 200 && response.data) {
|
|
45
|
-
|
|
45
|
+
const data = response.data;
|
|
46
|
+
if (!data.user_name) {
|
|
47
|
+
data.user_name = data.username || '';
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
46
50
|
}
|
|
47
51
|
throw new Error(response.msg || '获取用户信息失败');
|
|
48
52
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@commitlint/cli": "^19.5.0",
|
|
34
34
|
"@commitlint/config-conventional": "^19.5.0",
|
|
35
|
+
"@sentry/webpack-plugin": "^4.9.1",
|
|
35
36
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
36
37
|
"@typescript-eslint/parser": "^8.54.0",
|
|
37
38
|
"dotenv-cli": "^7.4.1",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
interface ApplySentryPluginOptions {
|
|
2
|
+
/** webpack-chain 实例(Umi chainWebpack 的参数) */
|
|
3
|
+
memo: any;
|
|
4
|
+
/** 应用名称,用于 urlPrefix 路径 */
|
|
5
|
+
appName: string;
|
|
6
|
+
/** Sentry project 名称,默认 '<%= projectName %>' */
|
|
7
|
+
project?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 往 webpack-chain 配置上挂载 Sentry sourcemap 上传插件
|
|
12
|
+
*/
|
|
13
|
+
export function applySentryPlugin({
|
|
14
|
+
memo,
|
|
15
|
+
appName,
|
|
16
|
+
project = '<%= projectName %>',
|
|
17
|
+
}: ApplySentryPluginOptions) {
|
|
18
|
+
const version = require('../package.json').version;
|
|
19
|
+
|
|
20
|
+
// 使用 Legacy Upload 方式,自托管 Sentry 不支持 Debug ID / Artifact Bundle
|
|
21
|
+
const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
|
|
22
|
+
memo.plugin('sentry').use(
|
|
23
|
+
class {
|
|
24
|
+
apply(compiler: any) {
|
|
25
|
+
sentryWebpackPlugin({
|
|
26
|
+
url: 'https://sentry.micoworld.net',
|
|
27
|
+
org: 'micocenter',
|
|
28
|
+
project,
|
|
29
|
+
/** @see https://micoworld.feishu.cn/wiki/AmXQwmnEairvQ9k2m9KcgJNcndv#share-OupCdqeeUoESO4x6YKPcrs4nn3c */
|
|
30
|
+
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
31
|
+
release: {
|
|
32
|
+
name: version,
|
|
33
|
+
uploadLegacySourcemaps: {
|
|
34
|
+
paths: ['./dist'],
|
|
35
|
+
urlPrefix: `~/portal-center/<%= projectName %>/${version}/${appName}`,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
sourcemaps: {
|
|
39
|
+
disable: true,
|
|
40
|
+
},
|
|
41
|
+
}).apply(compiler);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -428,9 +428,38 @@ module.exports = class extends Generator {
|
|
|
428
428
|
|
|
429
429
|
this.log('');
|
|
430
430
|
this.log('📦 正在安装依赖...');
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
431
|
+
try {
|
|
432
|
+
this.spawnCommandSync('pnpm', ['install'], {
|
|
433
|
+
cwd: this.monorepoRoot
|
|
434
|
+
});
|
|
435
|
+
} catch (e) {
|
|
436
|
+
this._installFailed = true;
|
|
437
|
+
this.log('');
|
|
438
|
+
this.log('⚠️ 依赖安装失败,但子应用文件已全部生成。');
|
|
439
|
+
this.logger.verbose('Install error:', e.message);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 依赖安装成功后,格式化被修改的 layout 文件
|
|
444
|
+
const layoutDir = path.join(this.monorepoRoot, 'apps/layout');
|
|
445
|
+
if (fs.existsSync(path.join(layoutDir, '.prettierrc'))) {
|
|
446
|
+
const filesToFormat = [
|
|
447
|
+
path.join(layoutDir, 'mock/pages.ts'),
|
|
448
|
+
path.join(layoutDir, 'config/config.dev.ts'),
|
|
449
|
+
].filter((f) => fs.existsSync(f));
|
|
450
|
+
|
|
451
|
+
if (filesToFormat.length > 0) {
|
|
452
|
+
try {
|
|
453
|
+
this.spawnCommandSync(
|
|
454
|
+
'pnpm',
|
|
455
|
+
['-C', 'apps/layout', 'exec', 'prettier', '--write', ...filesToFormat],
|
|
456
|
+
{ cwd: this.monorepoRoot },
|
|
457
|
+
);
|
|
458
|
+
} catch (e) {
|
|
459
|
+
this.logger.verbose('Formatting failed (non-critical):', e.message);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
434
463
|
}
|
|
435
464
|
|
|
436
465
|
end() {
|
|
@@ -438,7 +467,20 @@ module.exports = class extends Generator {
|
|
|
438
467
|
if (this._skipInstall) return;
|
|
439
468
|
|
|
440
469
|
this.log('');
|
|
441
|
-
|
|
470
|
+
|
|
471
|
+
if (this._installFailed) {
|
|
472
|
+
this.log('⚠️ 子应用文件已创建,但依赖安装未完成。');
|
|
473
|
+
this.log('');
|
|
474
|
+
this.log(' 请手动安装依赖:');
|
|
475
|
+
this.log('');
|
|
476
|
+
this.log(' pnpm install');
|
|
477
|
+
this.log('');
|
|
478
|
+
this.log(' 如果安装仍然失败,可尝试:');
|
|
479
|
+
this.log(' pnpm install --network-concurrency 4');
|
|
480
|
+
} else {
|
|
481
|
+
this.log('✅ 子应用创建成功!');
|
|
482
|
+
}
|
|
483
|
+
|
|
442
484
|
this.log('');
|
|
443
485
|
this.log(' 后续步骤:');
|
|
444
486
|
this.log('');
|
|
@@ -21,14 +21,14 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
21
21
|
* @doc https://umijs.org/docs/api/config#externals
|
|
22
22
|
*
|
|
23
23
|
* 作为 qiankun 子应用时,这些库由主应用提供:
|
|
24
|
-
* - react / react-dom
|
|
24
|
+
* - react / react-dom: 主应用已加载,避免多实例问题
|
|
25
25
|
* - @mico-platform/ui: 主应用已加载,复用组件和样式
|
|
26
26
|
*/
|
|
27
27
|
externals: {
|
|
28
28
|
react: 'React',
|
|
29
29
|
'react-dom': 'ReactDOM',
|
|
30
|
-
'react-dom/client': 'ReactDOMClient',
|
|
31
30
|
'@mico-platform/ui': 'micoUI',
|
|
31
|
+
'@common-web/sentry': 'CommonWebSentry',
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
|
|
@@ -21,14 +21,14 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
21
21
|
* @doc https://umijs.org/docs/api/config#externals
|
|
22
22
|
*
|
|
23
23
|
* 作为 qiankun 子应用时,这些库由主应用提供:
|
|
24
|
-
* - react / react-dom
|
|
24
|
+
* - react / react-dom: 主应用已加载,避免多实例问题
|
|
25
25
|
* - @mico-platform/ui: 主应用已加载,复用组件和样式
|
|
26
26
|
*/
|
|
27
27
|
externals: {
|
|
28
28
|
react: 'React',
|
|
29
29
|
'react-dom': 'ReactDOM',
|
|
30
|
-
'react-dom/client': 'ReactDOMClient',
|
|
31
30
|
'@mico-platform/ui': 'micoUI',
|
|
31
|
+
'@common-web/sentry': 'CommonWebSentry',
|
|
32
32
|
},
|
|
33
33
|
};
|
|
34
34
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// https://umijs.org/config/
|
|
2
2
|
|
|
3
3
|
import { defineConfig } from '@umijs/max';
|
|
4
|
+
import { applySentryPlugin } from '../../../scripts/apply-sentry-plugin';
|
|
4
5
|
const { CDN_PUBLIC_PATH } = process.env;
|
|
5
6
|
|
|
6
7
|
const PUBLIC_PATH: string = CDN_PUBLIC_PATH
|
|
@@ -8,6 +9,11 @@ const PUBLIC_PATH: string = CDN_PUBLIC_PATH
|
|
|
8
9
|
: '/<%= appName %>/';
|
|
9
10
|
|
|
10
11
|
const config: ReturnType<typeof defineConfig> = {
|
|
12
|
+
/**
|
|
13
|
+
* 启用 Source Map(用于 Sentry 错误追踪)
|
|
14
|
+
* hidden-source-map 会生成 .map 文件但不在 JS 中添加 sourcemap 注释
|
|
15
|
+
*/
|
|
16
|
+
devtool: 'hidden-source-map',
|
|
11
17
|
// 生产环境:将所有代码打包到一个文件
|
|
12
18
|
extraBabelPlugins: ['babel-plugin-dynamic-import-node'],
|
|
13
19
|
|
|
@@ -17,6 +23,7 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
17
23
|
memo.optimization.splitChunks(false);
|
|
18
24
|
// 禁用 runtimeChunk
|
|
19
25
|
memo.optimization.runtimeChunk(false);
|
|
26
|
+
applySentryPlugin({ memo, appName: '<%= appName %>' });
|
|
20
27
|
return memo;
|
|
21
28
|
},
|
|
22
29
|
publicPath: PUBLIC_PATH,
|
|
@@ -27,14 +34,14 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
27
34
|
* @doc https://umijs.org/docs/api/config#externals
|
|
28
35
|
*
|
|
29
36
|
* 作为 qiankun 子应用时,这些库由主应用提供:
|
|
30
|
-
* - react / react-dom
|
|
37
|
+
* - react / react-dom: 主应用已加载,避免多实例问题
|
|
31
38
|
* - @mico-platform/ui: 主应用已加载,复用组件和样式
|
|
32
39
|
*/
|
|
33
40
|
externals: {
|
|
34
41
|
react: 'React',
|
|
35
42
|
'react-dom': 'ReactDOM',
|
|
36
|
-
'react-dom/client': 'ReactDOMClient',
|
|
37
43
|
'@mico-platform/ui': 'micoUI',
|
|
44
|
+
'@common-web/sentry': 'CommonWebSentry',
|
|
38
45
|
},
|
|
39
46
|
};
|
|
40
47
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* 自定义函数请放在 @/common/mainApp.ts 中
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import React from 'react';
|
|
10
11
|
import { history } from '@umijs/max';
|
|
12
|
+
import { SentryErrorBoundary } from '@common-web/sentry';
|
|
11
13
|
import { appLogger } from './common/logger';
|
|
12
14
|
import { type IMicroAppProps, setMainAppProps } from './common/mainApp';
|
|
13
15
|
|
|
@@ -95,3 +97,7 @@ export async function getInitialState() {
|
|
|
95
97
|
name: '<%= appName %>',
|
|
96
98
|
};
|
|
97
99
|
}
|
|
100
|
+
|
|
101
|
+
export function rootContainer(container: React.ReactNode) {
|
|
102
|
+
return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
|
|
103
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
toCamel,
|
|
10
|
+
collectFiles,
|
|
11
|
+
transformDestPath,
|
|
12
|
+
isTemplateFile,
|
|
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
|
+
|
|
29
|
+
const { config: rcConfig, configPath } = loadMicorc(this.monorepoRoot);
|
|
30
|
+
this.rcConfig = rcConfig;
|
|
31
|
+
if (configPath) {
|
|
32
|
+
this.logger.verbose('Loaded config from:', configPath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const packagesDir = path.join(this.monorepoRoot, 'packages');
|
|
36
|
+
const workspaceFile = path.join(this.monorepoRoot, 'pnpm-workspace.yaml');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(packagesDir)) {
|
|
39
|
+
console.error('');
|
|
40
|
+
console.error('❌ Error: packages directory not found.');
|
|
41
|
+
console.error('');
|
|
42
|
+
console.error(' This generator must be run from a monorepo root that contains a "packages" directory.');
|
|
43
|
+
console.error(` Current directory: ${this.monorepoRoot}`);
|
|
44
|
+
console.error('');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(workspaceFile)) {
|
|
49
|
+
console.warn('');
|
|
50
|
+
console.warn('⚠️ Warning: pnpm-workspace.yaml not found.');
|
|
51
|
+
console.warn(' Make sure you are in the correct monorepo.');
|
|
52
|
+
console.warn('');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_detectPackageScope() {
|
|
57
|
+
const rootPkgPath = path.join(this.monorepoRoot, 'package.json');
|
|
58
|
+
if (fs.existsSync(rootPkgPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));
|
|
61
|
+
if (pkg.name) {
|
|
62
|
+
return pkg.name.startsWith('@')
|
|
63
|
+
? pkg.name.replace(/^(@[^/]+)\/.*/, '$1')
|
|
64
|
+
: `@${pkg.name}`;
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async prompting() {
|
|
74
|
+
try {
|
|
75
|
+
const detectedScope = this._detectPackageScope();
|
|
76
|
+
const rc = this.rcConfig || {};
|
|
77
|
+
|
|
78
|
+
this.answers = await this.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'appName',
|
|
82
|
+
message: 'Package name (e.g. my-widget)',
|
|
83
|
+
default: rc.defaultUmdName || 'my-widget',
|
|
84
|
+
filter: (input) => toKebab(input),
|
|
85
|
+
validate: (input) => {
|
|
86
|
+
const value = toKebab(input);
|
|
87
|
+
if (!value) return 'Package name is required';
|
|
88
|
+
const destDir = path.join(this.monorepoRoot, 'packages', value);
|
|
89
|
+
if (fs.existsSync(destDir)) {
|
|
90
|
+
return `Target already exists: packages/${value}`;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'umdGlobalName',
|
|
98
|
+
message: 'UMD global variable name (e.g. MyWidget)',
|
|
99
|
+
default: (answers) => toPascal(answers.appName),
|
|
100
|
+
validate: (input) => {
|
|
101
|
+
if (!input) return 'UMD global name is required';
|
|
102
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(input)) {
|
|
103
|
+
return 'Must be a valid JavaScript identifier';
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: 'input',
|
|
110
|
+
name: 'packageScope',
|
|
111
|
+
message: 'Package scope',
|
|
112
|
+
default: rc.packageScope || detectedScope || '@my-project',
|
|
113
|
+
validate: (input) => {
|
|
114
|
+
if (!input) return 'Package scope is required';
|
|
115
|
+
if (!input.startsWith('@')) return 'Package scope must start with @';
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'input',
|
|
121
|
+
name: 'devPort',
|
|
122
|
+
message: 'Dev server port',
|
|
123
|
+
default: rc.umdDevPort || '9100',
|
|
124
|
+
validate: (input) => {
|
|
125
|
+
const port = Number(input);
|
|
126
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
127
|
+
return 'Port must be an integer between 1024 and 65535';
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
this.appName = toKebab(this.answers.appName);
|
|
135
|
+
this.appNamePascal = toPascal(this.appName);
|
|
136
|
+
this.appNameCamel = toCamel(this.appName);
|
|
137
|
+
this.umdGlobalName = this.answers.umdGlobalName;
|
|
138
|
+
this.packageScope = this.answers.packageScope;
|
|
139
|
+
this.devPort = this.answers.devPort;
|
|
140
|
+
this.templateDir = this.templatePath();
|
|
141
|
+
this.destDir = path.join(this.monorepoRoot, 'packages', this.appName);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('');
|
|
144
|
+
console.error('❌ Error during prompting:');
|
|
145
|
+
console.error(` ${error.message}`);
|
|
146
|
+
console.error('');
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async writing() {
|
|
152
|
+
try {
|
|
153
|
+
if (!fs.existsSync(this.templateDir)) {
|
|
154
|
+
console.error('');
|
|
155
|
+
console.error('❌ Error: Template directory not found.');
|
|
156
|
+
console.error(` Expected: ${this.templateDir}`);
|
|
157
|
+
console.error('');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.logger.verbose('Template directory:', this.templateDir);
|
|
162
|
+
this.logger.verbose('Destination directory:', this.destDir);
|
|
163
|
+
|
|
164
|
+
const templateData = {
|
|
165
|
+
appName: this.appName,
|
|
166
|
+
AppName: this.appNamePascal,
|
|
167
|
+
appNameCamel: this.appNameCamel,
|
|
168
|
+
umdGlobalName: this.umdGlobalName,
|
|
169
|
+
packageScope: this.packageScope,
|
|
170
|
+
devPort: this.devPort,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
this.logger.verbose('Template data:', JSON.stringify(templateData, null, 2));
|
|
174
|
+
|
|
175
|
+
const files = collectFiles(this.templateDir, this.templateDir, IGNORE_LIST);
|
|
176
|
+
|
|
177
|
+
if (files.length === 0) {
|
|
178
|
+
console.error('');
|
|
179
|
+
console.error('❌ Error: No template files found.');
|
|
180
|
+
console.error(` Template directory: ${this.templateDir}`);
|
|
181
|
+
console.error('');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.isDryRun) {
|
|
186
|
+
this.log('');
|
|
187
|
+
this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
|
|
188
|
+
this.log('');
|
|
189
|
+
this.log(` Package: ${this.appName}`);
|
|
190
|
+
this.log(` UMD global: ${this.umdGlobalName}`);
|
|
191
|
+
this.log(` Scope: ${this.packageScope}`);
|
|
192
|
+
this.log(` Dev port: ${this.devPort}`);
|
|
193
|
+
this.log(` Destination: packages/${this.appName}`);
|
|
194
|
+
this.log('');
|
|
195
|
+
this.log(' Would create the following files:');
|
|
196
|
+
this.log('');
|
|
197
|
+
|
|
198
|
+
let templateCount = 0;
|
|
199
|
+
let copyCount = 0;
|
|
200
|
+
|
|
201
|
+
for (const relPath of files) {
|
|
202
|
+
const destRelPath = transformDestPath(relPath);
|
|
203
|
+
const isTemplate = isTemplateFile(relPath);
|
|
204
|
+
const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
|
|
205
|
+
this.log(` ${tag} packages/${this.appName}/${destRelPath}`);
|
|
206
|
+
|
|
207
|
+
if (isTemplate) {
|
|
208
|
+
templateCount++;
|
|
209
|
+
} else {
|
|
210
|
+
copyCount++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.log('');
|
|
215
|
+
this.log(` Total: ${files.length} files (${templateCount} templates, ${copyCount} static)`);
|
|
216
|
+
this.log('');
|
|
217
|
+
this.log(' Run without --dry-run to actually create these files.');
|
|
218
|
+
this.log('');
|
|
219
|
+
|
|
220
|
+
this._skipInstall = true;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.log('');
|
|
225
|
+
this.log(`📦 Creating UMD package: ${this.appName}`);
|
|
226
|
+
this.log(` UMD global: ${this.umdGlobalName}`);
|
|
227
|
+
this.log(` Destination: packages/${this.appName}`);
|
|
228
|
+
this.log('');
|
|
229
|
+
|
|
230
|
+
let templateCount = 0;
|
|
231
|
+
let copyCount = 0;
|
|
232
|
+
|
|
233
|
+
for (const relPath of files) {
|
|
234
|
+
const srcPath = path.join(this.templateDir, relPath);
|
|
235
|
+
const destRelPath = transformDestPath(relPath);
|
|
236
|
+
const destPath = path.join(this.destDir, destRelPath);
|
|
237
|
+
|
|
238
|
+
if (isTemplateFile(relPath)) {
|
|
239
|
+
this.logger.file('template', destRelPath);
|
|
240
|
+
this.fs.copyTpl(srcPath, destPath, templateData);
|
|
241
|
+
templateCount++;
|
|
242
|
+
} else {
|
|
243
|
+
this.logger.file('copy', destRelPath);
|
|
244
|
+
this.fs.copy(srcPath, destPath);
|
|
245
|
+
copyCount++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.logger.verbose(`Processed: ${templateCount} templates, ${copyCount} copied`);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error('');
|
|
252
|
+
console.error('❌ Error during file generation:');
|
|
253
|
+
console.error(` ${error.message}`);
|
|
254
|
+
console.error('');
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
install() {
|
|
260
|
+
if (this._skipInstall) return;
|
|
261
|
+
|
|
262
|
+
this.log('');
|
|
263
|
+
this.log('📦 正在安装依赖...');
|
|
264
|
+
try {
|
|
265
|
+
this.spawnCommandSync('pnpm', ['install'], {
|
|
266
|
+
cwd: this.monorepoRoot,
|
|
267
|
+
});
|
|
268
|
+
} catch (e) {
|
|
269
|
+
this._installFailed = true;
|
|
270
|
+
this.log('');
|
|
271
|
+
this.log('⚠️ 依赖安装失败,但 UMD 包文件已全部生成。');
|
|
272
|
+
this.logger.verbose('Install error:', e.message);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 依赖安装成功后,格式化生成的文件
|
|
277
|
+
const prettierConfig = path.join(this.monorepoRoot, 'apps/layout/.prettierrc');
|
|
278
|
+
if (fs.existsSync(prettierConfig)) {
|
|
279
|
+
try {
|
|
280
|
+
this.spawnCommandSync(
|
|
281
|
+
'pnpm',
|
|
282
|
+
[
|
|
283
|
+
'-C', 'apps/layout', 'exec', 'prettier',
|
|
284
|
+
'--config', prettierConfig,
|
|
285
|
+
'--write',
|
|
286
|
+
`${this.destDir}/**/*.{ts,tsx,js,jsx,json,less,md}`,
|
|
287
|
+
],
|
|
288
|
+
{ cwd: this.monorepoRoot },
|
|
289
|
+
);
|
|
290
|
+
} catch (e) {
|
|
291
|
+
this.logger.verbose('Formatting failed (non-critical):', e.message);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
end() {
|
|
297
|
+
if (this._skipInstall) return;
|
|
298
|
+
|
|
299
|
+
this.log('');
|
|
300
|
+
|
|
301
|
+
if (this._installFailed) {
|
|
302
|
+
this.log('⚠️ UMD 包文件已创建,但依赖安装未完成。');
|
|
303
|
+
this.log('');
|
|
304
|
+
this.log(' 请手动安装依赖:');
|
|
305
|
+
this.log('');
|
|
306
|
+
this.log(' pnpm install');
|
|
307
|
+
this.log('');
|
|
308
|
+
this.log(' 如果安装仍然失败,可尝试:');
|
|
309
|
+
this.log(' pnpm install --network-concurrency 4');
|
|
310
|
+
} else {
|
|
311
|
+
this.log('✅ UMD 包创建成功!');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.log('');
|
|
315
|
+
this.log(' 后续步骤:');
|
|
316
|
+
this.log('');
|
|
317
|
+
this.log(` cd packages/${this.appName}`);
|
|
318
|
+
this.log(' pnpm dev');
|
|
319
|
+
this.log('');
|
|
320
|
+
this.log(' 开发模式:');
|
|
321
|
+
this.log(` • 预览页面: http://localhost:${this.devPort}/`);
|
|
322
|
+
this.log(` • UMD 文件: http://localhost:${this.devPort}/${this.appName}.umd.js`);
|
|
323
|
+
this.log('');
|
|
324
|
+
}
|
|
325
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "subapp-umd",
|
|
3
|
+
"description": "在现有 Monorepo 中创建 UMD 组件包(Webpack 构建)",
|
|
4
|
+
"usage": "cd <monorepo-root> && mico create subapp-umd",
|
|
5
|
+
"features": [
|
|
6
|
+
"Webpack UMD 构建",
|
|
7
|
+
"React 18 + TypeScript",
|
|
8
|
+
"dev server 同时提供 HTML 预览和 UMD JS",
|
|
9
|
+
"Source Map 源码调试"
|
|
10
|
+
]
|
|
11
|
+
}
|