@lark-apaas/miaoda-cli 0.1.16-alpha.0 → 0.1.16-alpha.ab80f52
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/dist/cli/commands/deploy/modern.js +0 -9
- package/dist/cli/handlers/deploy/modern.js +0 -39
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +174 -70
- package/dist/config/sync-configs/design-stack.js +6 -1
- package/dist/config/sync-configs/nestjs-react-fullstack.js +6 -0
- package/dist/services/deploy/modern/atoms/local-release.js +2 -7
- package/dist/services/deploy/modern/pipelines/local.js +1 -4
- package/dist/utils/codemod-client-toolkit-lite.js +37 -188
- package/dist/utils/migrate-rule.js +322 -0
- package/package.json +14 -17
- package/upgrade/templates/design-stack/templates/.githooks/pre-commit +0 -0
- package/upgrade/templates/design-stack/templates/scripts/dev-local.js +0 -0
- package/upgrade/templates/design-stack/templates/scripts/dev.sh +0 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.githooks/pre-commit +0 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +0 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.js +0 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev.sh +0 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/run.sh +0 -0
|
@@ -16,7 +16,6 @@ function registerDeployCommandsModern(program) {
|
|
|
16
16
|
.description('触发 modern 应用发布:本地构建 + 本地部署(preRelease + localPublish)')
|
|
17
17
|
.option('--dir <path>', '项目目录', '.')
|
|
18
18
|
.option('--skip-build', '跳过 build 步骤(已构建好时使用)', false)
|
|
19
|
-
.option('--conf <json>', '关联元信息 JSON,仅识别 checkPointVersion / commitID 两字段')
|
|
20
19
|
.addHelpText('after', `
|
|
21
20
|
不要用异步模式或后台模式调用 deploy,否则调用可能提前结束,Agent 会误判发布已完成。
|
|
22
21
|
|
|
@@ -33,12 +32,6 @@ function registerDeployCommandsModern(program) {
|
|
|
33
32
|
6. savePluginInstances(扫描 dist/output_capabilities/*.json 批量注册)
|
|
34
33
|
7. finalizeLocalRelease(Finished|Failed)
|
|
35
34
|
|
|
36
|
-
--conf(关联元信息透传)
|
|
37
|
-
值为 JSON string,只识别两个字段,其余 key 忽略:
|
|
38
|
-
checkPointVersion 关联的 checkpoint 版本
|
|
39
|
-
commitID 关联的代码 commit ID
|
|
40
|
-
两字段均可选;非法 JSON / 非对象 / 字段值非字符串会报错。
|
|
41
|
-
|
|
42
35
|
JSON 输出(stdout)
|
|
43
36
|
{"data": {"appId": "...", "version": <n>, "url": "...", "releaseID": "...", "preReleaseID": "..."}}
|
|
44
37
|
|
|
@@ -46,14 +39,12 @@ JSON 输出(stdout)
|
|
|
46
39
|
$ miaoda deploy
|
|
47
40
|
$ miaoda deploy --dir ./my-app
|
|
48
41
|
$ miaoda deploy --skip-build
|
|
49
|
-
$ miaoda deploy --conf '{"checkPointVersion":"v3","commitID":"a1b2c3d"}'
|
|
50
42
|
`);
|
|
51
43
|
deployCmd.action((0, shared_1.withHelp)(deployCmd, async (rawOpts) => {
|
|
52
44
|
await (0, modern_1.handleDeployModern)({
|
|
53
45
|
dir: rawOpts.dir ?? '.',
|
|
54
46
|
appId: (0, shared_1.resolveAppId)({}),
|
|
55
47
|
skipBuild: rawOpts.skipBuild,
|
|
56
|
-
conf: rawOpts.conf,
|
|
57
48
|
});
|
|
58
49
|
}));
|
|
59
50
|
}
|
|
@@ -3,46 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.parseDeployConf = parseDeployConf;
|
|
7
6
|
exports.handleDeployModern = handleDeployModern;
|
|
8
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
8
|
const output_1 = require("../../../utils/output");
|
|
10
|
-
const error_1 = require("../../../utils/error");
|
|
11
9
|
const index_1 = require("../../../services/deploy/modern/index");
|
|
12
|
-
const CONF_EXPECTED = '期望形如 {"checkPointVersion":"...","commitID":"..."} 的 JSON 对象';
|
|
13
|
-
/**
|
|
14
|
-
* 解析 `--conf` JSON string,只取 checkPointVersion / commitID 两个白名单字段。
|
|
15
|
-
* - 未传 → 返回 {}(行为不变)。
|
|
16
|
-
* - 非法 JSON / 非 object / 字段值非 string → 抛 DEPLOY_CONF_INVALID。
|
|
17
|
-
* - 空串字段视为未提供(不进 body,保持可选语义);未知 key 忽略。
|
|
18
|
-
*/
|
|
19
|
-
function parseDeployConf(raw) {
|
|
20
|
-
if (raw === undefined || raw === '')
|
|
21
|
-
return {};
|
|
22
|
-
let parsed;
|
|
23
|
-
try {
|
|
24
|
-
parsed = JSON.parse(raw);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
throw new error_1.AppError('DEPLOY_CONF_INVALID', `--conf 不是合法 JSON,${CONF_EXPECTED}`);
|
|
28
|
-
}
|
|
29
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
30
|
-
throw new error_1.AppError('DEPLOY_CONF_INVALID', `--conf 必须是 JSON 对象,${CONF_EXPECTED}`);
|
|
31
|
-
}
|
|
32
|
-
const source = parsed;
|
|
33
|
-
const conf = {};
|
|
34
|
-
for (const key of ['checkPointVersion', 'commitID']) {
|
|
35
|
-
const value = source[key];
|
|
36
|
-
if (value === undefined)
|
|
37
|
-
continue;
|
|
38
|
-
if (typeof value !== 'string') {
|
|
39
|
-
throw new error_1.AppError('DEPLOY_CONF_INVALID', `--conf.${key} 必须是字符串,${CONF_EXPECTED}`);
|
|
40
|
-
}
|
|
41
|
-
if (value !== '')
|
|
42
|
-
conf[key] = value;
|
|
43
|
-
}
|
|
44
|
-
return conf;
|
|
45
|
-
}
|
|
46
10
|
/**
|
|
47
11
|
* miaoda deploy(modern scene 专用,CLI 表面对齐 openclaw-cli)
|
|
48
12
|
*
|
|
@@ -50,13 +14,10 @@ function parseDeployConf(raw) {
|
|
|
50
14
|
*/
|
|
51
15
|
async function handleDeployModern(opts) {
|
|
52
16
|
const projectDir = node_path_1.default.resolve(opts.dir);
|
|
53
|
-
const conf = parseDeployConf(opts.conf);
|
|
54
17
|
const result = await (0, index_1.runModernDeploy)({
|
|
55
18
|
projectDir,
|
|
56
19
|
appId: opts.appId,
|
|
57
20
|
skipBuild: opts.skipBuild ?? false,
|
|
58
|
-
checkPointVersion: conf.checkPointVersion,
|
|
59
|
-
commitID: conf.commitID,
|
|
60
21
|
});
|
|
61
22
|
(0, output_1.emit)({
|
|
62
23
|
data: {
|
|
@@ -2,16 +2,28 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* vite-react → nestjs-react-fullstack 的跨 stack 迁移规则。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 用 fullstack 模板版本整体替换;
|
|
9
|
-
* - package.json:字段级 merge —— 顶层 user 字段(name/version/private)保留,依赖列表
|
|
10
|
-
* 做 set union(template 同名包覆盖版本号 → 避免依赖冲突),scripts 同名替换为 template
|
|
11
|
-
* 版本。
|
|
5
|
+
* 设计原则:**最小改动**。只动 fullstack 形态必须的文件 / package.json key,user 已有
|
|
6
|
+
* 的 vite.config / tsconfig / eslint / prettier / tailwind / postcss / components.json
|
|
7
|
+
* 等业务配置一律**保留**,不被 template 整体覆盖。
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* 步骤:
|
|
10
|
+
* 1. src/ public/ 搬到 client/ 下(fullstack 布局)
|
|
11
|
+
* 2. 清掉 vite-react 时代的根 index.html / eslint.config.mjs / package-lock.json
|
|
12
|
+
* 3. 删 user package.json 里 lite SDK 相关 key(保留 coding-preset-vite-react)
|
|
13
|
+
* 4. 把 NestJS server runtime 拷进来(server/ + nest-cli.json + tsconfig.node.json
|
|
14
|
+
* + client/index.html 等 fullstack 形态新增的资产)
|
|
15
|
+
* 5. 把 fullstack 必需的 scripts 加到 package.json(dev / build / type:check 等)
|
|
16
|
+
* 6. 从 fullstack template/package.json 取版本号,精确加 NestJS 强依赖白名单
|
|
17
|
+
* (client-toolkit + 几个 @nestjs/* + fullstack-nestjs-core + hbs 等;不全量 merge)
|
|
18
|
+
* 7. codemod 业务代码:@lark-apaas/client-toolkit-lite → @lark-apaas/client-toolkit
|
|
19
|
+
* 8. codemod vite.config:defineConfig(...) → defineConfig(..., { fullstack: true }),
|
|
20
|
+
* 让 coding-preset-vite-react 启用 fullstack dev-proxy / html-output / basename /
|
|
21
|
+
* server-log 4 件套,vite 8080 反代 HTML / API 到 nest 3000
|
|
22
|
+
* 9. set-stack 切 .spark/meta.json
|
|
23
|
+
*
|
|
24
|
+
* 跟 SDK 的协作:client-toolkit@1.2.52+ 顶层 single-entry 完整 re-export 了
|
|
25
|
+
* client-toolkit-lite 全部 48 项 API(fullstack-plugin#1124),所以 codemod 只换
|
|
26
|
+
* 包名、specifier 列表不动就够。
|
|
15
27
|
*/
|
|
16
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
29
|
exports.MIGRATE_CONFIG = void 0;
|
|
@@ -20,92 +32,184 @@ exports.MIGRATE_CONFIG = {
|
|
|
20
32
|
to: 'nestjs-react-fullstack',
|
|
21
33
|
rules: [
|
|
22
34
|
// ===== 1. 用户领地搬迁 =====
|
|
23
|
-
// src/ 是 vite-react 的业务代码根,fullstack 拆 client/server,全部归 client/src/
|
|
24
35
|
{ type: 'move-directory', from: 'src', to: 'client/src' },
|
|
25
|
-
// public/ 是 vite 端的静态资源根,跟随 client/
|
|
26
36
|
{ type: 'move-directory', from: 'public', to: 'client/public' },
|
|
27
|
-
// shared/
|
|
28
|
-
// ===== 2. 清理 vite-react
|
|
29
|
-
// 根 index.html 被 client/index.html 取代(fullstack
|
|
37
|
+
// shared/ 不动(两边布局相同);.env / .spark_project 同理。
|
|
38
|
+
// ===== 2. 清理 vite-react 残留 =====
|
|
39
|
+
// 根 index.html 被 client/index.html 取代(fullstack 用 HBS 渲染)
|
|
30
40
|
{ type: 'delete-file', to: 'index.html' },
|
|
31
|
-
// ESLint 9 flat config
|
|
41
|
+
// ESLint 9 flat config 文件名变了(user eslint.config.mjs → eslint.config.js)
|
|
32
42
|
{ type: 'delete-file', to: 'eslint.config.mjs' },
|
|
33
|
-
// 依赖集合变了,lockfile
|
|
43
|
+
// 依赖集合变了,lockfile 重新生成
|
|
34
44
|
{ type: 'delete-file', to: 'package-lock.json' },
|
|
35
|
-
// ===== 3. 从 user package.json
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// 专属字段不会被自动清理 —— 必须显式删。
|
|
39
|
-
//
|
|
40
|
-
// 注意:不删 dependencies.@lark-apaas/client-toolkit-lite —— lite 是 vite-react 时代的
|
|
41
|
-
// 入门 wrapper(顶层 38 个 export),完整版 @lark-apaas/client-toolkit 只暴露 ~20 个
|
|
42
|
-
// 对等出口(components/* / hooks/* / utils/* / tools/* / 顶层 alias),剩下 ~18 个 lite
|
|
43
|
-
// symbol(copyToClipboard / getAppId / clsxWithTw / isMobile / avatarImages 等)在 full
|
|
44
|
-
// 公共 API 里没有对等物。lite 跟 full 不是简单替换关系,而是不同抽象层级。强行替换
|
|
45
|
-
// 会让 build 失败;让两个包共存(lite 很轻量)是当前的现实选择。后续若 client-toolkit
|
|
46
|
-
// 补全 utility 出口,可启用 src/utils/codemod-client-toolkit-lite.ts 里已实现的 codemod
|
|
47
|
-
// rule(在 MigrateRule 联合里有 CodemodClientToolkitLiteRule 类型)。
|
|
45
|
+
// ===== 3. 从 user package.json 删 vite-react 专属字段 =====
|
|
46
|
+
// 注意保留 devDependencies.@lark-apaas/coding-preset-vite-react —— user 迁移后
|
|
47
|
+
// vite.config 仍用它(开 fullstack 模式即可),不切到 fullstack-vite-preset。
|
|
48
48
|
{
|
|
49
49
|
type: 'delete-json-keys',
|
|
50
50
|
to: 'package.json',
|
|
51
51
|
keys: [
|
|
52
52
|
// 顶层 "type": "module" —— vite-react ESM 入口约定,nestjs CommonJS 不需要
|
|
53
53
|
'type',
|
|
54
|
-
//
|
|
55
|
-
'
|
|
56
|
-
// vite-react 的 ESLint preset
|
|
57
|
-
'devDependencies.@lark-apaas/coding-presets-react',
|
|
54
|
+
// lite SDK:codemod 后 import 走 client-toolkit 顶层
|
|
55
|
+
'dependencies.@lark-apaas/client-toolkit-lite',
|
|
58
56
|
],
|
|
59
57
|
},
|
|
60
|
-
// ===== 4.
|
|
61
|
-
//
|
|
62
|
-
{ type: '
|
|
58
|
+
// ===== 4. fullstack 形态必需资产(覆盖 user 已有同名文件) =====
|
|
59
|
+
// server/ 整目录:main.ts / app.module.ts / common/ / modules/view/ 等(fullstack 必需)
|
|
60
|
+
{ type: 'directory', from: 'server', to: 'server', overwrite: true },
|
|
61
|
+
// nest-cli.json:nest build 入口
|
|
62
|
+
{ type: 'file', from: 'nest-cli.json', to: 'nest-cli.json', overwrite: true },
|
|
63
|
+
// tsconfig.* —— vite-react 时代 tsconfig 配 include: ["src"],迁完代码搬到 client/src,
|
|
64
|
+
// include 路径不对 + 老 tsconfig.app 还 extends @lark-apaas/coding-presets-react;
|
|
65
|
+
// 整体覆盖到 fullstack 版本(include 改 client/** + shared/**、extends fullstack-presets)。
|
|
66
|
+
// user 自己加的 paths / strict 等设置如有需要,按 git diff 手动加回。
|
|
63
67
|
{ type: 'file', from: 'tsconfig.json', to: 'tsconfig.json', overwrite: true },
|
|
64
68
|
{ type: 'file', from: 'tsconfig.app.json', to: 'tsconfig.app.json', overwrite: true },
|
|
65
69
|
{ type: 'file', from: 'tsconfig.node.json', to: 'tsconfig.node.json', overwrite: true },
|
|
70
|
+
// ESLint 9 flat config —— vite-react 用 eslint.config.mjs 已删,fullstack 用
|
|
71
|
+
// eslint.config.js (commonjs) + extends @lark-apaas/fullstack-presets;user 通常不改
|
|
66
72
|
{ type: 'file', from: 'eslint.config.js', to: 'eslint.config.js', overwrite: true },
|
|
67
|
-
|
|
68
|
-
{ type: 'file', from: 'postcss.config.js', to: 'postcss.config.js', overwrite: true },
|
|
69
|
-
{ type: 'file', from: 'tailwind.config.ts', to: 'tailwind.config.ts', overwrite: true },
|
|
70
|
-
{ type: 'file', from: 'components.json', to: 'components.json', overwrite: true },
|
|
71
|
-
{ type: 'file', from: '.prettierrc', to: '.prettierrc', overwrite: true },
|
|
72
|
-
{ type: 'file', from: '.stylelintrc.js', to: '.stylelintrc.js', overwrite: true },
|
|
73
|
-
// renderTemplate 拉 tarball 时会把 _gitignore / _npmrc 占位名重命名回 .gitignore / .npmrc,
|
|
74
|
-
// 所以 from 也写 dot-form
|
|
75
|
-
{ type: 'file', from: '.gitignore', to: '.gitignore', overwrite: true },
|
|
76
|
-
{ type: 'file', from: '.npmrc', to: '.npmrc', overwrite: true },
|
|
77
|
-
// client 入口:用 nestjs 模板的 index.tsx 覆盖(含 Toaster / defaultTheme),原 vite-react
|
|
78
|
-
// 入口在前面 move-directory 时已搬到 client/src/index.tsx
|
|
73
|
+
// client/index.html:HBS 模板(fullstack 用,vite-react 用根 index.html 已删)
|
|
79
74
|
{ type: 'file', from: 'client/index.html', to: 'client/index.html', overwrite: true },
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
// scripts/:dev / build / lint / hooks 全套脚本
|
|
75
|
+
// scripts/ 整目录:fullstack 形态启动 / 构建 / lint 所需的 8 个平台脚本(dev.sh /
|
|
76
|
+
// dev.js / dev-local.js / build.sh / lint.js / prune-smart.js / run.sh / hooks/)。
|
|
77
|
+
// dev.sh exec node dev.js,dev.js 才是并发起 nest + vite 的核心 —— 漏一个 dev 跑不起来。
|
|
78
|
+
// overwrite: true:这些脚本由平台 cli 维护,user 一般不改;按 fullstack 版本覆盖。
|
|
85
79
|
{ type: 'directory', from: 'scripts', to: 'scripts', overwrite: true },
|
|
86
|
-
// .githooks
|
|
87
|
-
{ type: 'directory', from: '.githooks', to: '.githooks', overwrite:
|
|
88
|
-
// shared/
|
|
89
|
-
// 的 fallback 文件 —— 用 overwrite: false 把 template 新增的文件加入 user shared/,user
|
|
90
|
-
// 已有同名文件保持不动。
|
|
80
|
+
// .githooks(fallback,user 已有则保留)
|
|
81
|
+
{ type: 'directory', from: '.githooks', to: '.githooks', overwrite: false },
|
|
82
|
+
// shared/ fallback(user 已有同名文件保留,template 新增文件加入)
|
|
91
83
|
{ type: 'directory', from: 'shared', to: 'shared', overwrite: false },
|
|
92
|
-
// .env
|
|
84
|
+
// .env fallback(user 没有时用 template 默认)
|
|
93
85
|
{ type: 'file', from: '.env', to: '.env', overwrite: false },
|
|
94
|
-
// ===== 5.
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
// ===== 5. 加 fullstack 形态必需的 scripts =====
|
|
87
|
+
// 这些 scripts 用 user 现有的可能性低(vite-react 没有 nest 相关 script),直接覆盖
|
|
88
|
+
{
|
|
89
|
+
type: 'add-script',
|
|
90
|
+
name: 'dev',
|
|
91
|
+
command: './scripts/dev.sh',
|
|
92
|
+
overwrite: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'add-script',
|
|
96
|
+
name: 'dev:server',
|
|
97
|
+
command: 'NODE_ENV=development nest start --watch',
|
|
98
|
+
overwrite: true,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'add-script',
|
|
102
|
+
name: 'dev:client',
|
|
103
|
+
command: 'NODE_ENV=development vite --config vite.config.ts',
|
|
104
|
+
overwrite: true,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'add-script',
|
|
108
|
+
name: 'build',
|
|
109
|
+
command: './scripts/build.sh',
|
|
110
|
+
overwrite: true,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
type: 'add-script',
|
|
114
|
+
name: 'build:server',
|
|
115
|
+
command: 'NODE_ENV=production nest build',
|
|
116
|
+
overwrite: true,
|
|
117
|
+
},
|
|
101
118
|
{
|
|
102
|
-
type: '
|
|
119
|
+
type: 'add-script',
|
|
120
|
+
name: 'build:client',
|
|
121
|
+
command: 'NODE_ENV=production vite build --config vite.config.ts',
|
|
122
|
+
overwrite: true,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'add-script',
|
|
126
|
+
name: 'build:prod',
|
|
127
|
+
command: 'npm run build:server && npm run build:client',
|
|
128
|
+
overwrite: true,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'add-script',
|
|
132
|
+
name: 'start',
|
|
133
|
+
command: 'NODE_ENV=production node main.js',
|
|
134
|
+
overwrite: true,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: 'add-script',
|
|
138
|
+
name: 'type:check:server',
|
|
139
|
+
command: 'tsc --noEmit --project tsconfig.node.json',
|
|
140
|
+
overwrite: true,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'add-script',
|
|
144
|
+
name: 'type:check:client',
|
|
145
|
+
command: 'tsc --noEmit --project tsconfig.app.json',
|
|
146
|
+
overwrite: true,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'add-script',
|
|
150
|
+
name: 'type:check',
|
|
151
|
+
command: 'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
|
|
152
|
+
overwrite: true,
|
|
153
|
+
},
|
|
154
|
+
// ===== 6. 从 fullstack template 取版本号加 NestJS 强依赖(精确白名单) =====
|
|
155
|
+
// 不走 merge-json(template 130+ devDeps 全 merge 会污染 user 业务依赖集合);
|
|
156
|
+
// 白名单只列 fullstack 启动能跑起来必需的最小集。
|
|
157
|
+
{
|
|
158
|
+
type: 'add-deps-from-template',
|
|
103
159
|
from: 'package.json',
|
|
104
160
|
to: 'package.json',
|
|
161
|
+
dependencies: [
|
|
162
|
+
// NestJS server 核心
|
|
163
|
+
'@nestjs/core',
|
|
164
|
+
'@nestjs/common',
|
|
165
|
+
'@nestjs/platform-express',
|
|
166
|
+
'@nestjs/config',
|
|
167
|
+
'@nestjs/axios',
|
|
168
|
+
'@nestjs/swagger',
|
|
169
|
+
// 妙搭 fullstack server SDK(main.ts / view module 等都依赖)
|
|
170
|
+
'@lark-apaas/fullstack-nestjs-core',
|
|
171
|
+
// HBS 模板引擎(client/index.html 渲染)
|
|
172
|
+
'hbs',
|
|
173
|
+
// NestJS runtime 必需
|
|
174
|
+
'reflect-metadata',
|
|
175
|
+
'rxjs',
|
|
176
|
+
'tslib',
|
|
177
|
+
// DTO 校验 / 转换
|
|
178
|
+
'class-transformer',
|
|
179
|
+
'class-validator',
|
|
180
|
+
// .env 加载(scripts/dev.sh 用到 dotenv-cli 风格)
|
|
181
|
+
'dotenv',
|
|
182
|
+
// client-toolkit:替代 lite,业务代码 codemod 后走它
|
|
183
|
+
'@lark-apaas/client-toolkit',
|
|
184
|
+
],
|
|
185
|
+
devDependencies: [
|
|
186
|
+
// nest start / nest build 必需
|
|
187
|
+
'@nestjs/cli',
|
|
188
|
+
'@nestjs/testing',
|
|
189
|
+
// server ts 编译
|
|
190
|
+
'ts-node',
|
|
191
|
+
'@types/express',
|
|
192
|
+
'@types/hbs',
|
|
193
|
+
'@types/node',
|
|
194
|
+
// 并发跑 nest + vite(scripts/dev.sh 用 concurrently)
|
|
195
|
+
'concurrently',
|
|
196
|
+
// fullstack server 端 tsconfig.node.json 通过 extends 引这个包
|
|
197
|
+
// (@lark-apaas/fullstack-presets/lib/simple/tsconfig/tsconfig.node.json);
|
|
198
|
+
// 不装会导致 nest build / type:check:server / vite dev 解析 tsconfig 时 ENOENT
|
|
199
|
+
'@lark-apaas/fullstack-presets',
|
|
200
|
+
],
|
|
105
201
|
},
|
|
106
|
-
// =====
|
|
107
|
-
//
|
|
108
|
-
//
|
|
202
|
+
// ===== 7. 业务代码 codemod:lite → client-toolkit 包名替换 =====
|
|
203
|
+
// client-toolkit@1.2.52+ 顶层完整 re-export lite 全部 48 项,specifier 列表不动。
|
|
204
|
+
// 同时扫 shared/(client/server 共享代码也可能引 lite 类型 / utils)。
|
|
205
|
+
{ type: 'codemod-client-toolkit-lite', root: 'client/src' },
|
|
206
|
+
{ type: 'codemod-client-toolkit-lite', root: 'shared' },
|
|
207
|
+
// ===== 8. vite.config codemod:加 { fullstack: true } 第二参 =====
|
|
208
|
+
// user vite.config 现在是 defineConfig({...}) 单参;fullstack 形态需要 vite 8080
|
|
209
|
+
// 反代 HTML / API 到 nest 3000,coding-preset-vite-react 用 fullstack option 启用
|
|
210
|
+
// 这一行为(详见 fullstack-plugin coding-preset-vite-react#fullstack)。
|
|
211
|
+
{ type: 'codemod-vite-config-fullstack', to: 'vite.config.ts' },
|
|
212
|
+
// ===== 9. 切 stack =====
|
|
109
213
|
{ type: 'set-stack', stack: 'nestjs-react-fullstack' },
|
|
110
214
|
],
|
|
111
215
|
};
|
|
@@ -42,7 +42,7 @@ exports.SYNC_CONFIG = {
|
|
|
42
42
|
command: 'node scripts/hooks/run-precommit.js',
|
|
43
43
|
overwrite: false,
|
|
44
44
|
},
|
|
45
|
-
// 2. .gitignore 卫生:移除 package-lock.json + 加 .agent/
|
|
45
|
+
// 2. .gitignore 卫生:移除 package-lock.json + 加 .agent/ / .npm_cache
|
|
46
46
|
{
|
|
47
47
|
type: 'remove-line',
|
|
48
48
|
to: '.gitignore',
|
|
@@ -53,6 +53,11 @@ exports.SYNC_CONFIG = {
|
|
|
53
53
|
to: '.gitignore',
|
|
54
54
|
line: '.agent/',
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
type: 'add-line',
|
|
58
|
+
to: '.gitignore',
|
|
59
|
+
line: '.npm_cache',
|
|
60
|
+
},
|
|
56
61
|
// 3. 老 npm scripts 迁移(裸 fullstack-cli → 钉版 npx)。两条 ifStartsWith 分别覆盖
|
|
57
62
|
// "裸 fullstack-cli"(最老形态)和 "npx 未钉版" 形态,统一升到 FULLSTACK_CLI_PIN_SPEC。
|
|
58
63
|
{
|
|
@@ -113,6 +113,12 @@ exports.SYNC_CONFIG = {
|
|
|
113
113
|
to: '.gitignore',
|
|
114
114
|
line: '.agent/',
|
|
115
115
|
},
|
|
116
|
+
// 7a. 确保 .gitignore 包含 .npm_cache 目录
|
|
117
|
+
{
|
|
118
|
+
type: 'add-line',
|
|
119
|
+
to: '.gitignore',
|
|
120
|
+
line: '.npm_cache',
|
|
121
|
+
},
|
|
116
122
|
// 8. 同步 .spark_project 配置文件(总是覆盖)
|
|
117
123
|
{
|
|
118
124
|
type: 'file',
|
|
@@ -10,13 +10,8 @@ const logger_1 = require("../../../../utils/logger");
|
|
|
10
10
|
* 创建本地发布单(加锁;不挂 pipeline)。
|
|
11
11
|
* 返回的 releaseId 必须由 finalizeLocalRelease 翻为终态,否则发布单一直挂着。
|
|
12
12
|
*/
|
|
13
|
-
async function createLocalRelease(appId, version
|
|
14
|
-
return (0, index_1.createLocalRelease)({
|
|
15
|
-
appID: appId,
|
|
16
|
-
version,
|
|
17
|
-
checkPointVersion: extra?.checkPointVersion,
|
|
18
|
-
commitID: extra?.commitID,
|
|
19
|
-
});
|
|
13
|
+
async function createLocalRelease(appId, version) {
|
|
14
|
+
return (0, index_1.createLocalRelease)({ appID: appId, version });
|
|
20
15
|
}
|
|
21
16
|
/**
|
|
22
17
|
* 把本地发布单翻为终态。Finished / Failed 由 pipeline 视上下游结果决定。
|
|
@@ -43,10 +43,7 @@ async function localBuildLocalPublishPipeline(opts) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
(0, logger_1.log)('deploy', 'Creating local release...');
|
|
46
|
-
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version
|
|
47
|
-
checkPointVersion: opts.checkPointVersion,
|
|
48
|
-
commitID: opts.commitID,
|
|
49
|
-
});
|
|
46
|
+
const release = await (0, index_1.createLocalRelease)(ctx.appId, pre.version);
|
|
50
47
|
try {
|
|
51
48
|
(0, logger_1.log)('deploy', 'Uploading artifacts...');
|
|
52
49
|
await (0, index_1.uploadArtifacts)({ projectDir: ctx.projectDir, appId: ctx.appId, data });
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Codemod:把 `@lark-apaas/client-toolkit-lite` 的 import 改写到 `@lark-apaas/client-toolkit
|
|
4
|
-
* 的对应子路径。
|
|
3
|
+
* Codemod:把 `@lark-apaas/client-toolkit-lite` 的 import 改写到 `@lark-apaas/client-toolkit`。
|
|
5
4
|
*
|
|
6
5
|
* 用于 `miaoda app migrate vite-react → nestjs-react-fullstack`:迁移后让两个包共存属于
|
|
7
|
-
* lib 冗余(两份 react / 两套主题 / 两套 axios),所以扫
|
|
8
|
-
* full
|
|
6
|
+
* lib 冗余(两份 react / 两套主题 / 两套 axios),所以扫 user 代码把 lite import 改写到
|
|
7
|
+
* full,然后用 delete-json-keys 移除 lite dep。
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* 实现策略:单行包名替换。
|
|
10
|
+
*
|
|
11
|
+
* client-toolkit 自 v1.2.48 起在顶层 single-entry 完整 re-export 了 client-toolkit-lite
|
|
12
|
+
* 的全部 48 项(components / hooks / utils / integrations / logger / trace / types /
|
|
13
|
+
* constants),跟 lite 同名同签 —— 所以 codemod 只需把 import 来源包名从 `-lite` 换掉,
|
|
14
|
+
* specifier 列表完全不动。CSS `@import` 同理(client-toolkit 顶层加了 ./styles.css 别名)。
|
|
13
15
|
*
|
|
14
16
|
* 边界:
|
|
15
17
|
* - 仅处理形如 `import [type] { A, B as C } from '@lark-apaas/client-toolkit-lite'` 的
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 会因为 lite dep 被删而失败,提示用户手动处理。
|
|
18
|
+
* named import;default import / namespace import / 跨多行 specifier 当前不识别,
|
|
19
|
+
* 但只要 `from '...lite'` 在同一行,包名替换仍有效(regex 抓 from 段)。
|
|
19
20
|
*/
|
|
20
21
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -26,68 +27,10 @@ exports.rewriteCssText = rewriteCssText;
|
|
|
26
27
|
exports.rewriteText = rewriteText;
|
|
27
28
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
28
29
|
const node_path_1 = __importDefault(require("node:path"));
|
|
29
|
-
/** lite 38 个 export → full 子路径的映射表 */
|
|
30
|
-
const SYMBOL_MAP = {
|
|
31
|
-
// === 组件(apis/components 是 lite 兼容层,components 是基础层) ===
|
|
32
|
-
ActiveLink: { subpath: 'apis/components/ActiveLink' },
|
|
33
|
-
AppContainer: { subpath: 'components/AppContainer' },
|
|
34
|
-
ErrorRender: { subpath: 'components/ErrorRender' },
|
|
35
|
-
NavLink: { subpath: 'apis/components/NavLink' },
|
|
36
|
-
PagePlaceholder: { subpath: 'components/PagePlaceholder' },
|
|
37
|
-
QueryProvider: { subpath: 'components/QueryProvider' },
|
|
38
|
-
UniversalLink: { subpath: 'apis/components/UniversalLink' },
|
|
39
|
-
Welcome: { subpath: 'apis/components/Welcome' },
|
|
40
|
-
// === 类型 ===
|
|
41
|
-
AppInfoPayload: { subpath: 'types/common' },
|
|
42
|
-
IFileAttachment: { subpath: 'apis/udt-types' },
|
|
43
|
-
IJson: { subpath: 'apis/udt-types' },
|
|
44
|
-
IUserProfile: { subpath: 'apis/udt-types' },
|
|
45
|
-
LogLevel: { subpath: 'logger/log-types' },
|
|
46
|
-
LogWithMeta: { subpath: 'logger/log-types' },
|
|
47
|
-
TrackKey: { subpath: 'types/tea' },
|
|
48
|
-
TrackParams: { subpath: 'types/tea' },
|
|
49
|
-
// === 图片资源(lite 端用了 alias:avatar→avatarImages 等) ===
|
|
50
|
-
avatarImages: { subpath: 'apis/constants/img-resources/avatar', sourceName: 'avatar' },
|
|
51
|
-
bannerImages: { subpath: 'apis/constants/img-resources/banner', sourceName: 'banner' },
|
|
52
|
-
coverImages: { subpath: 'apis/constants/img-resources/cover', sourceName: 'cover' },
|
|
53
|
-
// === Hooks ===
|
|
54
|
-
useAppInfo: { subpath: 'hooks/useAppInfo' },
|
|
55
|
-
useCurrentUserProfile: { subpath: 'hooks/useCurrentUserProfile' },
|
|
56
|
-
useIsMobile: { subpath: 'hooks/useIsMobile' },
|
|
57
|
-
useLogout: { subpath: 'hooks/useLogout' },
|
|
58
|
-
// === Utils / 工具函数 ===
|
|
59
|
-
axiosForBackend: { subpath: 'utils/getAxiosForBackend' },
|
|
60
|
-
capabilityClient: { subpath: '' }, // 在 full 顶层 export
|
|
61
|
-
clsxWithTw: { subpath: 'utils/utils' },
|
|
62
|
-
copyToClipboard: { subpath: 'utils/copyToClipboard' },
|
|
63
|
-
getAppId: { subpath: 'utils/getAppId' },
|
|
64
|
-
getAppInfo: { subpath: 'integrations/getAppInfo' },
|
|
65
|
-
getAxiosForBackend: { subpath: 'utils/getAxiosForBackend' },
|
|
66
|
-
getCsrfToken: { subpath: 'utils/getCsrfToken' },
|
|
67
|
-
getCurrentUserProfile: { subpath: 'integrations/getCurrentUserProfile' },
|
|
68
|
-
getDataloom: { subpath: 'apis/dataloom' },
|
|
69
|
-
getEnvPath: { subpath: 'utils/getEnvPath' },
|
|
70
|
-
getWsPath: { subpath: 'utils/utils' },
|
|
71
|
-
initAxiosConfig: { subpath: 'utils/axiosConfig' },
|
|
72
|
-
initObservable: { subpath: 'runtime/observable' },
|
|
73
|
-
isIOS: { subpath: 'utils/deviceType' },
|
|
74
|
-
isIpad: { subpath: 'utils/deviceType' },
|
|
75
|
-
isMobile: { subpath: 'utils/deviceType' },
|
|
76
|
-
isPreview: { subpath: 'utils/utils' },
|
|
77
|
-
logger: { subpath: 'apis/logger' },
|
|
78
|
-
normalizeBasePath: { subpath: 'utils/utils' },
|
|
79
|
-
reportTeaEvent: { subpath: 'components/AppContainer/utils/tea' },
|
|
80
|
-
resolveAppUrl: { subpath: 'utils/resolveAppUrl' },
|
|
81
|
-
safeStringify: { subpath: 'utils/safeStringify' },
|
|
82
|
-
scopedStorage: { subpath: 'apis/utils/scopedStorage' },
|
|
83
|
-
trace: { subpath: 'trace/index' },
|
|
84
|
-
};
|
|
85
30
|
const LITE_PKG = '@lark-apaas/client-toolkit-lite';
|
|
86
31
|
const FULL_PKG = '@lark-apaas/client-toolkit';
|
|
87
32
|
/**
|
|
88
33
|
* 递归扫描 rootDir 下的 .ts/.tsx/.js/.jsx/.css 文件,把 lite import 改写到 full。
|
|
89
|
-
* css 里 `@import "@lark-apaas/client-toolkit-lite/styles.css"` 同样要重写到
|
|
90
|
-
* `@lark-apaas/client-toolkit/lib/index.css`,否则 css build 会因 lite dep 被删而 ENOENT。
|
|
91
34
|
*/
|
|
92
35
|
function rewriteLiteImports(rootDir) {
|
|
93
36
|
const result = {
|
|
@@ -108,8 +51,7 @@ function rewriteLiteImports(rootDir) {
|
|
|
108
51
|
if (!before.includes(LITE_PKG))
|
|
109
52
|
continue;
|
|
110
53
|
const rel = node_path_1.default.relative(rootDir, file);
|
|
111
|
-
const { text, count
|
|
112
|
-
result.unmapped.push(...unmapped);
|
|
54
|
+
const { text, count } = isJs ? rewriteText(before, rel) : rewriteCssText(before, rel);
|
|
113
55
|
if (count > 0) {
|
|
114
56
|
node_fs_1.default.writeFileSync(file, text);
|
|
115
57
|
result.filesChanged.push(rel);
|
|
@@ -119,128 +61,35 @@ function rewriteLiteImports(rootDir) {
|
|
|
119
61
|
return result;
|
|
120
62
|
}
|
|
121
63
|
/**
|
|
122
|
-
* css
|
|
123
|
-
* `@lark-apaas/client-toolkit
|
|
124
|
-
*
|
|
64
|
+
* css 改写器:单行包名替换。`@import "@lark-apaas/client-toolkit-lite/<path>"` →
|
|
65
|
+
* `@import "@lark-apaas/client-toolkit/<path>"`。其中 styles.css 在 full 端已加同名别名
|
|
66
|
+
* 指向 ./lib/index.css。
|
|
125
67
|
*/
|
|
126
|
-
function rewriteCssText(source,
|
|
127
|
-
const unmapped = [];
|
|
68
|
+
function rewriteCssText(source, _fileLabel) {
|
|
128
69
|
let count = 0;
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const m = re.exec(line);
|
|
136
|
-
if (!m) {
|
|
137
|
-
out.push(line);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const captures = m;
|
|
141
|
-
const indent = captures[1] ?? '';
|
|
142
|
-
const subpath = captures[2] ?? '';
|
|
143
|
-
if (subpath === 'styles.css') {
|
|
144
|
-
out.push(`${indent}@import "${FULL_PKG}/lib/index.css";`);
|
|
145
|
-
count++;
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// styles/* 等子路径 full 没对等物,保留原行 + TODO(build 会失败)
|
|
149
|
-
out.push(line);
|
|
150
|
-
out.push(`${indent}/* TODO[migrate]: @lark-apaas/client-toolkit-lite/${subpath} has no known mapping in @lark-apaas/client-toolkit; replace manually. */`);
|
|
151
|
-
unmapped.push({ file: fileLabel, symbol: subpath, line: i + 1 });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return { text: out.join('\n'), count, unmapped };
|
|
70
|
+
const re = /(@import\s+['"])@lark-apaas\/client-toolkit-lite(\/[^'"]*)?(['"])/g;
|
|
71
|
+
const text = source.replace(re, (_match, p1, sub, p3) => {
|
|
72
|
+
count++;
|
|
73
|
+
return `${p1}${FULL_PKG}${sub ?? ''}${p3}`;
|
|
74
|
+
});
|
|
75
|
+
return { text, count, unmapped: [] };
|
|
155
76
|
}
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
|
|
77
|
+
/**
|
|
78
|
+
* JS/TS 文件改写器:把任何 `from '@lark-apaas/client-toolkit-lite'` 改成 full 包名。
|
|
79
|
+
* specifier 列表完全保留 —— full 顶层 single-entry 已经兼容 lite 全部 48 项 export。
|
|
80
|
+
*
|
|
81
|
+
* 同样兼容 dynamic import / require / `export ... from` 等其他形式(只要包名字符串在)。
|
|
82
|
+
*/
|
|
83
|
+
function rewriteText(source, _fileLabel) {
|
|
159
84
|
let count = 0;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
out.push(line);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
// RegExp.exec 返回 string[],但 optional capture group 实际可能是 undefined。
|
|
172
|
-
// 显式 cast 让 TS narrow 配合 eslint no-unnecessary-condition 不报"永远 truthy"。
|
|
173
|
-
const captures = m;
|
|
174
|
-
const indent = captures[1] ?? '';
|
|
175
|
-
const wholeIsType = captures[2] !== undefined;
|
|
176
|
-
const specifiers = parseSpecifiers(captures[3] ?? '');
|
|
177
|
-
// 按 target subpath 分组(同 subpath 的 specifier 合并到一条 import)
|
|
178
|
-
const groups = new Map();
|
|
179
|
-
const localUnmapped = [];
|
|
180
|
-
for (const spec of specifiers) {
|
|
181
|
-
// SYMBOL_MAP 是 Record,TS 默认推断索引返回非 undefined;用 hasOwnProperty 做 narrow
|
|
182
|
-
if (!Object.prototype.hasOwnProperty.call(SYMBOL_MAP, spec.imported)) {
|
|
183
|
-
localUnmapped.push(spec.imported);
|
|
184
|
-
unmapped.push({ file: fileLabel, symbol: spec.imported, line: i + 1 });
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const info = SYMBOL_MAP[spec.imported];
|
|
188
|
-
const target = info.subpath ? `${FULL_PKG}/${info.subpath}` : FULL_PKG;
|
|
189
|
-
const sourceName = info.sourceName ?? spec.imported;
|
|
190
|
-
const isType = wholeIsType || spec.isType;
|
|
191
|
-
const entry = groups.get(target) ?? { items: [], allTypes: true };
|
|
192
|
-
entry.items.push({ source: sourceName, local: spec.local, isType });
|
|
193
|
-
entry.allTypes = entry.allTypes && isType;
|
|
194
|
-
groups.set(target, entry);
|
|
195
|
-
count++;
|
|
196
|
-
}
|
|
197
|
-
if (groups.size === 0) {
|
|
198
|
-
// 全部 unmapped:保留原行 + TODO 注释
|
|
199
|
-
out.push(line);
|
|
200
|
-
out.push(`${indent}// TODO[migrate]: '${localUnmapped.join(', ')}' has no known mapping from '${LITE_PKG}' to '${FULL_PKG}'; replace manually before building.`);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
// 输出 group 化的新 import 行(同 target 下区分 type-only / 混合)
|
|
204
|
-
for (const [target, { items, allTypes }] of groups) {
|
|
205
|
-
if (allTypes) {
|
|
206
|
-
const inner = items.map(fmtSpec).join(', ');
|
|
207
|
-
out.push(`${indent}import type { ${inner} } from '${target}';`);
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
// 把 type 和非 type 分两条输出更稳:避免 `import { type X, Y }` 在某些工具链下不被支持
|
|
211
|
-
const typeItems = items.filter((it) => it.isType);
|
|
212
|
-
const valItems = items.filter((it) => !it.isType);
|
|
213
|
-
if (typeItems.length > 0) {
|
|
214
|
-
out.push(`${indent}import type { ${typeItems.map(fmtSpec).join(', ')} } from '${target}';`);
|
|
215
|
-
}
|
|
216
|
-
out.push(`${indent}import { ${valItems.map(fmtSpec).join(', ')} } from '${target}';`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (localUnmapped.length > 0) {
|
|
220
|
-
out.push(`${indent}// TODO[migrate]: '${localUnmapped.join(', ')}' has no known mapping from '${LITE_PKG}' to '${FULL_PKG}'; replace manually before building.`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return { text: out.join('\n'), count, unmapped };
|
|
224
|
-
}
|
|
225
|
-
function fmtSpec(s) {
|
|
226
|
-
return s.local === s.source ? s.source : `${s.source} as ${s.local}`;
|
|
227
|
-
}
|
|
228
|
-
function parseSpecifiers(raw) {
|
|
229
|
-
const out = [];
|
|
230
|
-
for (const part of raw.split(',')) {
|
|
231
|
-
const t = part.trim();
|
|
232
|
-
if (!t)
|
|
233
|
-
continue;
|
|
234
|
-
const m = /^(type\s+)?([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*))?$/.exec(t);
|
|
235
|
-
if (!m)
|
|
236
|
-
continue;
|
|
237
|
-
const captures = m;
|
|
238
|
-
const imported = captures[2];
|
|
239
|
-
if (imported === undefined)
|
|
240
|
-
continue;
|
|
241
|
-
out.push({ isType: captures[1] !== undefined, imported, local: captures[3] ?? imported });
|
|
242
|
-
}
|
|
243
|
-
return out;
|
|
85
|
+
// 既匹配 `from 'pkg'` 也匹配 `from "pkg"`,单双引号都管。
|
|
86
|
+
// 同时覆盖 `import('pkg')` / `require('pkg')` 等其他常见形态。
|
|
87
|
+
const re = /(['"])@lark-apaas\/client-toolkit-lite(['"])/g;
|
|
88
|
+
const text = source.replace(re, (_match, p1, p2) => {
|
|
89
|
+
count++;
|
|
90
|
+
return `${p1}${FULL_PKG}${p2}`;
|
|
91
|
+
});
|
|
92
|
+
return { text, count, unmapped: [] };
|
|
244
93
|
}
|
|
245
94
|
function* walk(dir) {
|
|
246
95
|
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.applyMigrateRules = applyMigrateRules;
|
|
7
|
+
exports.rewriteViteConfigForFullstack = rewriteViteConfigForFullstack;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
10
|
const sync_rule_1 = require("./sync-rule");
|
|
@@ -38,6 +39,14 @@ function applyMigrateRules(rules, opts) {
|
|
|
38
39
|
results.push(applyCodemodLite(rule, opts));
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
42
|
+
if (rule.type === 'codemod-vite-config-fullstack') {
|
|
43
|
+
results.push(applyCodemodViteConfigFullstack(rule, opts));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (rule.type === 'add-deps-from-template') {
|
|
47
|
+
results.push(applyAddDepsFromTemplate(rule, opts));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
41
50
|
// 前面 if 已经把 migrate 专用 rule 都 narrow 掉,剩下的必然是 SyncRule
|
|
42
51
|
const [syncResult] = (0, sync_rule_1.applySyncRules)([rule], opts);
|
|
43
52
|
results.push(syncResult);
|
|
@@ -164,6 +173,319 @@ function applyCodemodLite(rule, opts) {
|
|
|
164
173
|
detail,
|
|
165
174
|
};
|
|
166
175
|
}
|
|
176
|
+
function applyCodemodViteConfigFullstack(rule, opts) {
|
|
177
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
178
|
+
const rel = rule.to ?? 'vite.config.ts';
|
|
179
|
+
const dest = node_path_1.default.join(opts.targetDir, rel);
|
|
180
|
+
if (!node_fs_1.default.existsSync(dest)) {
|
|
181
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rel} (not found, skip)`);
|
|
182
|
+
return { rule, action: 'skipped', path: rel };
|
|
183
|
+
}
|
|
184
|
+
const before = node_fs_1.default.readFileSync(dest, 'utf-8');
|
|
185
|
+
const { text, changed, alreadyDone } = rewriteViteConfigForFullstack(before);
|
|
186
|
+
if (alreadyDone) {
|
|
187
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rel} (fullstack: true already present)`);
|
|
188
|
+
return { rule, action: 'noop', path: rel };
|
|
189
|
+
}
|
|
190
|
+
if (!changed) {
|
|
191
|
+
(0, logger_1.log)(logPrefix, ` ⚠ ${rel} (defineConfig call not found; please add { fullstack: true } manually)`);
|
|
192
|
+
return { rule, action: 'noop', path: rel, detail: 'defineConfig call not found' };
|
|
193
|
+
}
|
|
194
|
+
node_fs_1.default.writeFileSync(dest, text);
|
|
195
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rel} (added { fullstack: true } to defineConfig)`);
|
|
196
|
+
return { rule, action: 'patched', path: rel };
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 给 vite.config.ts 的 `defineConfig(...)` 调用加上 `{ fullstack: true }` 第二参。
|
|
200
|
+
*
|
|
201
|
+
* 支持的形态:
|
|
202
|
+
* defineConfig({ ... }) → defineConfig({ ... }, { fullstack: true })
|
|
203
|
+
* defineConfig({ ... }, { ... }) → 已有第二参,把 fullstack: true 注入对象字面量
|
|
204
|
+
* defineConfig() → defineConfig({}, { fullstack: true })
|
|
205
|
+
*
|
|
206
|
+
* 跨多行 + 嵌套 arrow function / 字符串 / 模板字符串都要保留,所以用括号匹配而非正则。
|
|
207
|
+
* 已经含 `fullstack: true` 字面量则跳过(视为已迁移)。
|
|
208
|
+
*/
|
|
209
|
+
function rewriteViteConfigForFullstack(source) {
|
|
210
|
+
const callRe = /defineConfig\s*\(/g;
|
|
211
|
+
const m = callRe.exec(source);
|
|
212
|
+
if (m === null) {
|
|
213
|
+
return { text: source, changed: false, alreadyDone: false };
|
|
214
|
+
}
|
|
215
|
+
const openIdx = m.index + m[0].length - 1; // 指向 '('
|
|
216
|
+
const closeIdx = findMatchingParen(source, openIdx);
|
|
217
|
+
if (closeIdx < 0) {
|
|
218
|
+
return { text: source, changed: false, alreadyDone: false };
|
|
219
|
+
}
|
|
220
|
+
const argsRaw = source.slice(openIdx + 1, closeIdx);
|
|
221
|
+
if (/fullstack\s*:\s*true/.test(argsRaw)) {
|
|
222
|
+
return { text: source, changed: false, alreadyDone: true };
|
|
223
|
+
}
|
|
224
|
+
const { firstArgEnd, secondArgRange } = splitFirstTwoArgs(source, openIdx + 1, closeIdx);
|
|
225
|
+
if (secondArgRange === null) {
|
|
226
|
+
// 单参 / 无参:在第一参之后插 ", { fullstack: true }"
|
|
227
|
+
const insertAt = firstArgEnd ?? openIdx + 1;
|
|
228
|
+
const head = source.slice(0, insertAt);
|
|
229
|
+
const tail = source.slice(insertAt, closeIdx);
|
|
230
|
+
// 第一参是否非空(去空白)
|
|
231
|
+
const arg1 = source.slice(openIdx + 1, insertAt).trim();
|
|
232
|
+
const sep = arg1.length === 0 ? '{}, { fullstack: true }' : ', { fullstack: true }';
|
|
233
|
+
return {
|
|
234
|
+
text: head + (arg1.length === 0 ? sep : sep) + tail + source.slice(closeIdx),
|
|
235
|
+
changed: true,
|
|
236
|
+
alreadyDone: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// 双参:在第二参对象 `{` 之后插 ` fullstack: true,`
|
|
240
|
+
const arg2 = source.slice(secondArgRange.start, secondArgRange.end);
|
|
241
|
+
const objOpen = arg2.indexOf('{');
|
|
242
|
+
if (objOpen < 0) {
|
|
243
|
+
// 第二参不是对象字面量(罕见),保守跳过
|
|
244
|
+
return { text: source, changed: false, alreadyDone: false };
|
|
245
|
+
}
|
|
246
|
+
const absObjOpen = secondArgRange.start + objOpen;
|
|
247
|
+
const inserted = '\n fullstack: true,';
|
|
248
|
+
return {
|
|
249
|
+
text: source.slice(0, absObjOpen + 1) + inserted + source.slice(absObjOpen + 1),
|
|
250
|
+
changed: true,
|
|
251
|
+
alreadyDone: false,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 从 openIdx(指向 `(`)开始扫到匹配的 `)`,跳过 string / template / 括号嵌套。
|
|
256
|
+
* 找不到匹配返回 -1。
|
|
257
|
+
*/
|
|
258
|
+
function findMatchingParen(text, openIdx) {
|
|
259
|
+
let depth = 0;
|
|
260
|
+
let i = openIdx;
|
|
261
|
+
let inSingle = false;
|
|
262
|
+
let inDouble = false;
|
|
263
|
+
let inTpl = false;
|
|
264
|
+
let inLineComment = false;
|
|
265
|
+
let inBlockComment = false;
|
|
266
|
+
while (i < text.length) {
|
|
267
|
+
const c = text[i];
|
|
268
|
+
const next = i + 1 < text.length ? text[i + 1] : '';
|
|
269
|
+
if (inLineComment) {
|
|
270
|
+
if (c === '\n')
|
|
271
|
+
inLineComment = false;
|
|
272
|
+
i++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (inBlockComment) {
|
|
276
|
+
if (c === '*' && next === '/') {
|
|
277
|
+
inBlockComment = false;
|
|
278
|
+
i += 2;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
i++;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (inSingle) {
|
|
285
|
+
if (c === '\\')
|
|
286
|
+
i += 2;
|
|
287
|
+
else if (c === "'") {
|
|
288
|
+
inSingle = false;
|
|
289
|
+
i++;
|
|
290
|
+
}
|
|
291
|
+
else
|
|
292
|
+
i++;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (inDouble) {
|
|
296
|
+
if (c === '\\')
|
|
297
|
+
i += 2;
|
|
298
|
+
else if (c === '"') {
|
|
299
|
+
inDouble = false;
|
|
300
|
+
i++;
|
|
301
|
+
}
|
|
302
|
+
else
|
|
303
|
+
i++;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (inTpl) {
|
|
307
|
+
if (c === '\\')
|
|
308
|
+
i += 2;
|
|
309
|
+
else if (c === '`') {
|
|
310
|
+
inTpl = false;
|
|
311
|
+
i++;
|
|
312
|
+
}
|
|
313
|
+
else
|
|
314
|
+
i++;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (c === '/' && next === '/') {
|
|
318
|
+
inLineComment = true;
|
|
319
|
+
i += 2;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (c === '/' && next === '*') {
|
|
323
|
+
inBlockComment = true;
|
|
324
|
+
i += 2;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (c === "'") {
|
|
328
|
+
inSingle = true;
|
|
329
|
+
i++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (c === '"') {
|
|
333
|
+
inDouble = true;
|
|
334
|
+
i++;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (c === '`') {
|
|
338
|
+
inTpl = true;
|
|
339
|
+
i++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (c === '(' || c === '{' || c === '[') {
|
|
343
|
+
depth++;
|
|
344
|
+
i++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (c === ')' || c === '}' || c === ']') {
|
|
348
|
+
depth--;
|
|
349
|
+
if (depth === 0 && c === ')')
|
|
350
|
+
return i;
|
|
351
|
+
i++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
i++;
|
|
355
|
+
}
|
|
356
|
+
return -1;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 在 `argsStart`(`(` 之后第一字符)到 `argsEnd`(`)` 位置)范围内识别顶层 `,`,
|
|
360
|
+
* 拿到第一参结束位置和(如果存在)第二参的范围。
|
|
361
|
+
*/
|
|
362
|
+
function splitFirstTwoArgs(text, argsStart, argsEnd) {
|
|
363
|
+
let depth = 0;
|
|
364
|
+
let i = argsStart;
|
|
365
|
+
let inSingle = false;
|
|
366
|
+
let inDouble = false;
|
|
367
|
+
let inTpl = false;
|
|
368
|
+
let firstComma = -1;
|
|
369
|
+
while (i < argsEnd) {
|
|
370
|
+
const c = text[i];
|
|
371
|
+
if (inSingle) {
|
|
372
|
+
if (c === '\\') {
|
|
373
|
+
i += 2;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (c === "'")
|
|
377
|
+
inSingle = false;
|
|
378
|
+
i++;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (inDouble) {
|
|
382
|
+
if (c === '\\') {
|
|
383
|
+
i += 2;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (c === '"')
|
|
387
|
+
inDouble = false;
|
|
388
|
+
i++;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (inTpl) {
|
|
392
|
+
if (c === '\\') {
|
|
393
|
+
i += 2;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (c === '`')
|
|
397
|
+
inTpl = false;
|
|
398
|
+
i++;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (c === "'") {
|
|
402
|
+
inSingle = true;
|
|
403
|
+
i++;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (c === '"') {
|
|
407
|
+
inDouble = true;
|
|
408
|
+
i++;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (c === '`') {
|
|
412
|
+
inTpl = true;
|
|
413
|
+
i++;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (c === '(' || c === '{' || c === '[') {
|
|
417
|
+
depth++;
|
|
418
|
+
i++;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (c === ')' || c === '}' || c === ']') {
|
|
422
|
+
depth--;
|
|
423
|
+
i++;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (c === ',' && depth === 0) {
|
|
427
|
+
firstComma = i;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
i++;
|
|
431
|
+
}
|
|
432
|
+
if (firstComma < 0) {
|
|
433
|
+
return { firstArgEnd: argsEnd, secondArgRange: null };
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
firstArgEnd: firstComma,
|
|
437
|
+
secondArgRange: { start: firstComma + 1, end: argsEnd },
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function applyAddDepsFromTemplate(rule, opts) {
|
|
441
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
442
|
+
const userPath = node_path_1.default.join(opts.targetDir, rule.to ?? 'package.json');
|
|
443
|
+
const tplPath = node_path_1.default.join(opts.sourceRoot, rule.from ?? 'package.json');
|
|
444
|
+
if (!node_fs_1.default.existsSync(userPath)) {
|
|
445
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to ?? 'package.json'} (not found, skip add-deps-from-template)`);
|
|
446
|
+
return { rule, action: 'skipped', path: rule.to ?? 'package.json' };
|
|
447
|
+
}
|
|
448
|
+
if (!node_fs_1.default.existsSync(tplPath)) {
|
|
449
|
+
(0, logger_1.log)(logPrefix, ` ⚠ template ${rule.from ?? 'package.json'} not found, skip add-deps-from-template`);
|
|
450
|
+
return { rule, action: 'skipped', path: rule.to ?? 'package.json' };
|
|
451
|
+
}
|
|
452
|
+
const userText = node_fs_1.default.readFileSync(userPath, 'utf-8');
|
|
453
|
+
const userJson = JSON.parse(userText);
|
|
454
|
+
const tplJson = JSON.parse(node_fs_1.default.readFileSync(tplPath, 'utf-8'));
|
|
455
|
+
const added = [];
|
|
456
|
+
const missingInTpl = [];
|
|
457
|
+
for (const grp of ['dependencies', 'devDependencies']) {
|
|
458
|
+
const whitelist = rule[grp] ?? [];
|
|
459
|
+
if (whitelist.length === 0)
|
|
460
|
+
continue;
|
|
461
|
+
userJson[grp] ??= {};
|
|
462
|
+
for (const dep of whitelist) {
|
|
463
|
+
// 在 template 里找版本号:优先 tpl 同 grp,其次另一 grp(template 可能把 dep 写在
|
|
464
|
+
// dependencies/devDependencies 任一处,跟 user 当前 grp 不一定一致)
|
|
465
|
+
const tplVersion = tplJson[grp]?.[dep] ??
|
|
466
|
+
(grp === 'dependencies' ? tplJson.devDependencies?.[dep] : tplJson.dependencies?.[dep]);
|
|
467
|
+
if (tplVersion === undefined) {
|
|
468
|
+
missingInTpl.push(dep);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
userJson[grp][dep] = tplVersion;
|
|
472
|
+
added.push(`${grp}.${dep}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (added.length === 0 && missingInTpl.length === 0) {
|
|
476
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to ?? 'package.json'} (no deps to add)`);
|
|
477
|
+
return { rule, action: 'noop', path: rule.to ?? 'package.json' };
|
|
478
|
+
}
|
|
479
|
+
const trailing = userText.endsWith('\n') ? '\n' : '';
|
|
480
|
+
node_fs_1.default.writeFileSync(userPath, JSON.stringify(userJson, null, 2) + trailing);
|
|
481
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.to ?? 'package.json'} (added ${added.length.toString()} dep(s)${missingInTpl.length > 0 ? `; ${missingInTpl.length.toString()} missing in template: ${missingInTpl.join(', ')}` : ''})`);
|
|
482
|
+
return {
|
|
483
|
+
rule,
|
|
484
|
+
action: 'patched',
|
|
485
|
+
path: rule.to ?? 'package.json',
|
|
486
|
+
detail: added.join(','),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
167
489
|
/**
|
|
168
490
|
* 按 dot path 删除嵌套对象里的一个 leaf key。返回是否真的删了一项。
|
|
169
491
|
* 不支持包含 `.` 的字段名(实际 dep 名 / scripts 名不会含 `.`,够用)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.16-alpha.
|
|
3
|
+
"version": "0.1.16-alpha.ab80f52",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -17,20 +17,6 @@
|
|
|
17
17
|
"dist",
|
|
18
18
|
"upgrade"
|
|
19
19
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "bash scripts/build.sh",
|
|
22
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
23
|
-
"lint": "eslint src/ --max-warnings 0",
|
|
24
|
-
"format": "prettier --write src/",
|
|
25
|
-
"format:check": "prettier --check src/",
|
|
26
|
-
"test": "vitest run --project=unit",
|
|
27
|
-
"test:watch": "vitest --project=unit",
|
|
28
|
-
"test:integration": "vitest run --project=integration",
|
|
29
|
-
"dev": "node --import tsx src/main.ts",
|
|
30
|
-
"cli": "node --env-file-if-exists=integration/.env --import tsx src/main.ts",
|
|
31
|
-
"prepare": "husky",
|
|
32
|
-
"prepublishOnly": "pnpm format:check && pnpm lint && pnpm build && pnpm test"
|
|
33
|
-
},
|
|
34
20
|
"keywords": [
|
|
35
21
|
"miaoda",
|
|
36
22
|
"cli",
|
|
@@ -65,5 +51,16 @@
|
|
|
65
51
|
"vitest": "^4.1.4",
|
|
66
52
|
"xml2js": "^0.6.2"
|
|
67
53
|
},
|
|
68
|
-
"
|
|
69
|
-
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "bash scripts/build.sh",
|
|
56
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
57
|
+
"lint": "eslint src/ --max-warnings 0",
|
|
58
|
+
"format": "prettier --write src/",
|
|
59
|
+
"format:check": "prettier --check src/",
|
|
60
|
+
"test": "vitest run --project=unit",
|
|
61
|
+
"test:watch": "vitest --project=unit",
|
|
62
|
+
"test:integration": "vitest run --project=integration",
|
|
63
|
+
"dev": "node --import tsx src/main.ts",
|
|
64
|
+
"cli": "node --env-file-if-exists=integration/.env --import tsx src/main.ts"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|