@lark-apaas/miaoda-cli 0.1.16-alpha.0 → 0.1.16-beta.70f1b39
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/app/index.js +0 -49
- package/dist/cli/commands/deploy/modern.js +0 -9
- package/dist/cli/handlers/app/index.js +1 -3
- package/dist/cli/handlers/deploy/modern.js +0 -39
- 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/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
- package/dist/cli/handlers/app/migrate.js +0 -182
- package/dist/config/migrate-configs/index.js +0 -35
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +0 -112
- package/dist/config/migrate.js +0 -15
- package/dist/utils/codemod-client-toolkit-lite.js +0 -257
- package/dist/utils/migrate-rule.js +0 -185
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerAppCommands = registerAppCommands;
|
|
4
4
|
const shared_1 = require("../../../cli/commands/shared");
|
|
5
5
|
const index_1 = require("../../../cli/handlers/app/index");
|
|
6
|
-
const index_2 = require("../../../config/migrate-configs/index");
|
|
7
6
|
const error_1 = require("../../../utils/error");
|
|
8
7
|
function registerAppCommands(program, opts = {}) {
|
|
9
8
|
const description = opts.includeInit
|
|
@@ -23,56 +22,8 @@ function registerAppCommands(program, opts = {}) {
|
|
|
23
22
|
if (opts.includeInit) {
|
|
24
23
|
registerAppInit(appCmd);
|
|
25
24
|
registerAppSync(appCmd);
|
|
26
|
-
registerAppMigrate(appCmd);
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
|
-
function registerAppMigrate(parent) {
|
|
30
|
-
const supportedDesc = (0, index_2.listSupportedMigrations)()
|
|
31
|
-
.map((m) => `${m.from} → ${m.to}`)
|
|
32
|
-
.join(', ');
|
|
33
|
-
const cmd = parent
|
|
34
|
-
.command('migrate')
|
|
35
|
-
.description('跨 stack 原地迁移:按 MigrateConfig 全套 apply(move + delete + 模板覆盖 + 字段 merge + set-stack)')
|
|
36
|
-
.requiredOption('--to <stack>', '目标 stack 短名')
|
|
37
|
-
.option('--from <stack>', '源 stack;默认读 .spark/meta.json 当前 stack')
|
|
38
|
-
.option('--dir <path>', '项目目录,默认 cwd(需含 .spark/meta.json)')
|
|
39
|
-
.addHelpText('after', `
|
|
40
|
-
已支持的迁移路径
|
|
41
|
-
${supportedDesc || '(none)'}
|
|
42
|
-
|
|
43
|
-
执行流
|
|
44
|
-
1. 校验 .spark/meta.json 存在且当前 stack 匹配 --from(或自动读 meta)
|
|
45
|
-
2. 按 (from, to) 在 src/config/migrate-configs/ 取 MigrateConfig
|
|
46
|
-
3. 顺序执行 rules:
|
|
47
|
-
move-directory / move-file → 用户领地搬到新布局(src → client/src 等)
|
|
48
|
-
delete-file / delete-directory → 清理老 stack 的残留文件
|
|
49
|
-
delete-json-keys → 从 user package.json 删 vite-react 专属字段
|
|
50
|
-
file / directory → 拷新 stack 的模板资产覆盖
|
|
51
|
-
merge-json → package.json 等字段级深合并
|
|
52
|
-
add-script / patch-script / add-line / remove-line → 局部修补
|
|
53
|
-
set-stack → 收尾切 .spark/meta.json 的 stack 字段
|
|
54
|
-
4. 不做 npm install —— 让用户自行 review 改动后再装依赖(package-lock.json 已删)
|
|
55
|
-
|
|
56
|
-
JSON 输出
|
|
57
|
-
{"data": {"from": "...", "to": "...",
|
|
58
|
-
"appliedRules": [{"type": "...", "action": "...", "path": "...", "detail": "..."}],
|
|
59
|
-
"moved": [...], "deleted": [...], "synced": [...], "merged": [...], "patched": [...],
|
|
60
|
-
"skipped": N,
|
|
61
|
-
"nextActions": [...]}}
|
|
62
|
-
|
|
63
|
-
示例
|
|
64
|
-
$ miaoda app migrate --to nestjs-react-fullstack
|
|
65
|
-
$ miaoda app migrate --from vite-react --to nestjs-react-fullstack
|
|
66
|
-
$ miaoda app migrate --to nestjs-react-fullstack --dir /path/to/app
|
|
67
|
-
`);
|
|
68
|
-
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
69
|
-
await (0, index_1.handleAppMigrate)({
|
|
70
|
-
dir: rawOpts.dir,
|
|
71
|
-
from: rawOpts.from,
|
|
72
|
-
to: rawOpts.to,
|
|
73
|
-
});
|
|
74
|
-
}));
|
|
75
|
-
}
|
|
76
27
|
function registerAppSync(parent) {
|
|
77
28
|
const cmd = parent
|
|
78
29
|
.command('sync')
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SUPPORTED_STACKS = exports.
|
|
3
|
+
exports.SUPPORTED_STACKS = exports.handleAppUpgrade = exports.handleAppSync = exports.handleAppInit = exports.handleAppUpdate = exports.handleAppGet = void 0;
|
|
4
4
|
var get_1 = require("./get");
|
|
5
5
|
Object.defineProperty(exports, "handleAppGet", { enumerable: true, get: function () { return get_1.handleAppGet; } });
|
|
6
6
|
var update_1 = require("./update");
|
|
@@ -10,8 +10,6 @@ Object.defineProperty(exports, "handleAppInit", { enumerable: true, get: functio
|
|
|
10
10
|
var sync_1 = require("./sync");
|
|
11
11
|
Object.defineProperty(exports, "handleAppSync", { enumerable: true, get: function () { return sync_1.handleAppSync; } });
|
|
12
12
|
Object.defineProperty(exports, "handleAppUpgrade", { enumerable: true, get: function () { return sync_1.handleAppUpgrade; } });
|
|
13
|
-
var migrate_1 = require("./migrate");
|
|
14
|
-
Object.defineProperty(exports, "handleAppMigrate", { enumerable: true, get: function () { return migrate_1.handleAppMigrate; } });
|
|
15
13
|
// commands 层渲染 help 时需要这份枚举;从 handler barrel 转发,避免 commands → services 越界
|
|
16
14
|
var index_1 = require("../../../services/app/init/index");
|
|
17
15
|
Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return index_1.SUPPORTED_STACKS; } });
|
|
@@ -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: {
|
|
@@ -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 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.16-
|
|
3
|
+
"version": "0.1.16-beta.70f1b39",
|
|
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
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.handleAppMigrate = handleAppMigrate;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const index_1 = require("../../../config/migrate-configs/index");
|
|
11
|
-
const index_2 = require("../../../services/app/init/index");
|
|
12
|
-
const migrate_rule_1 = require("../../../utils/migrate-rule");
|
|
13
|
-
const spark_meta_1 = require("../../../utils/spark-meta");
|
|
14
|
-
const error_1 = require("../../../utils/error");
|
|
15
|
-
const output_1 = require("../../../utils/output");
|
|
16
|
-
const logger_1 = require("../../../utils/logger");
|
|
17
|
-
/**
|
|
18
|
-
* miaoda app migrate --to <stack> [--from <stack>] [--dir <path>]
|
|
19
|
-
*
|
|
20
|
-
* 跨 stack 原地迁移:把 user app 从源 stack(默认 .spark/meta.json 当前 stack)转到目标 stack。
|
|
21
|
-
* 同一个 git 仓库内进行,不动 git history,所有改动作为 working tree 变更体现,用户跑完后
|
|
22
|
-
* 自行 `git status` / `git diff` 评估并 commit。
|
|
23
|
-
*
|
|
24
|
-
* 跟 sync 的区别:sync 是同 stack 内的版本对齐,migrate 是跨 stack 切换(布局重排、依赖
|
|
25
|
-
* 集合切换、配置文件全套替换)。两条命令的 rule 引擎共享(applyMigrateRules 内部对
|
|
26
|
-
* SyncRule 委派给 applySyncRules)。
|
|
27
|
-
*
|
|
28
|
-
* 执行流:
|
|
29
|
-
* 1. 校验 user app 已 init(.spark/meta.json 存在)+ 当前 stack 匹配 from
|
|
30
|
-
* 2. 拿到 (from, to) 对应的 MigrateConfig 与模板源根目录
|
|
31
|
-
* 3. 顺序执行 rules:move-* → delete-* → delete-json-keys → file/directory →
|
|
32
|
-
* merge-json → set-stack
|
|
33
|
-
* 4. emit 结果;不做 npm install(用户接下来跑 `npm install` 或 `miaoda app sync` 时自然装)
|
|
34
|
-
*
|
|
35
|
-
* 不做事:
|
|
36
|
-
* - 不自动 commit / stash —— 让用户自己用 git review 改动
|
|
37
|
-
* - 不跑 npm install —— 留给后续 sync 或用户手动
|
|
38
|
-
* - 不动 .agent/skills/(那是 miaoda skills sync 的事,迁移后用户应该单独跑一次切到新 stack 的 skills)
|
|
39
|
-
*/
|
|
40
|
-
async function handleAppMigrate(opts) {
|
|
41
|
-
await Promise.resolve();
|
|
42
|
-
const targetDir = node_path_1.default.resolve(opts.dir ?? process.cwd());
|
|
43
|
-
const meta = (0, spark_meta_1.readSparkMeta)(targetDir);
|
|
44
|
-
if (meta.stack === undefined || meta.stack === '') {
|
|
45
|
-
throw new error_1.AppError('MIGRATE_META_INCOMPLETE', '.spark/meta.json missing stack — run `miaoda app init` first');
|
|
46
|
-
}
|
|
47
|
-
const from = opts.from ?? meta.stack;
|
|
48
|
-
if (opts.from !== undefined && opts.from !== meta.stack) {
|
|
49
|
-
throw new error_1.AppError('MIGRATE_FROM_MISMATCH', `--from '${opts.from}' but current stack is '${meta.stack}'`, {
|
|
50
|
-
next_actions: ['省略 --from 让 cli 自动读 meta.json,或确认 user app 状态'],
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (from === opts.to) {
|
|
54
|
-
throw new error_1.AppError('MIGRATE_NOOP', `from and to are both '${from}', nothing to do`);
|
|
55
|
-
}
|
|
56
|
-
const config = (0, index_1.getMigrateConfig)(from, opts.to);
|
|
57
|
-
if (config === null) {
|
|
58
|
-
const supported = (0, index_1.listSupportedMigrations)()
|
|
59
|
-
.map((m) => `${m.from} → ${m.to}`)
|
|
60
|
-
.join(', ');
|
|
61
|
-
throw new error_1.AppError('MIGRATE_PATH_NOT_SUPPORTED', `no migrate config for '${from}' → '${opts.to}'`, {
|
|
62
|
-
next_actions: [`已支持:${supported || '(none)'}`],
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
// migrate 用 init 同款的 renderTemplate 拉目标 stack 的 npm template tarball 到 tmp,
|
|
66
|
-
// 再把 tmp 当作 sourceRoot 喂给 applyMigrateRules。这样 migrate config 里 file/directory
|
|
67
|
-
// rule 的 `from` 引用的是完整的 npm template 渲染产物,不需要在 upgrade/templates/ 重复
|
|
68
|
-
// 维护一份 server/ + client/ + 全套配置 —— 复杂度由 init 那条链路 amortize。
|
|
69
|
-
const tmpRoot = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'miaoda-migrate-'));
|
|
70
|
-
const projectName = node_path_1.default.basename(targetDir);
|
|
71
|
-
let templateVersion;
|
|
72
|
-
let templateArchType;
|
|
73
|
-
let results;
|
|
74
|
-
try {
|
|
75
|
-
const tpl = (0, index_2.renderTemplate)({ stack: opts.to, targetDir: tmpRoot, projectName });
|
|
76
|
-
templateVersion = tpl.version;
|
|
77
|
-
templateArchType = tpl.archType;
|
|
78
|
-
// template package.json 的 user 私有字段(name / version / private 等)不参与 merge —— 否
|
|
79
|
-
// 则会被 deepMergeJson 当成 template 端"权威值"覆盖 user 项目原本的字段。这里在 tmp 端
|
|
80
|
-
// 直接 strip 这些字段,让 merge-json rule 只对 dependencies / devDependencies / scripts
|
|
81
|
-
// 等 schema-level 字段生效。
|
|
82
|
-
stripUserOwnedFields(node_path_1.default.join(tmpRoot, 'package.json'));
|
|
83
|
-
(0, logger_1.log)('migrate', `Rendered ${opts.to}@${tpl.version} to tmp, applying ${config.rules.length.toString()} rule(s)`);
|
|
84
|
-
results = (0, migrate_rule_1.applyMigrateRules)(config.rules, {
|
|
85
|
-
sourceRoot: tmpRoot,
|
|
86
|
-
targetDir,
|
|
87
|
-
logPrefix: 'migrate',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
node_fs_1.default.rmSync(tmpRoot, { recursive: true, force: true });
|
|
92
|
-
}
|
|
93
|
-
// archType / version 字段同步:set-stack rule 只切 stack,archType 是从 npm template 包
|
|
94
|
-
// 的 miaodaTemplate.archType 拿到的动态值,rule 配置时不知道,由 handler 在拿到
|
|
95
|
-
// renderTemplate 结果后单独写。version 也一并写回方便 sync 后续诊断。
|
|
96
|
-
(0, spark_meta_1.writeSparkMeta)(targetDir, { archType: templateArchType, version: templateVersion });
|
|
97
|
-
(0, output_1.emit)({
|
|
98
|
-
data: {
|
|
99
|
-
from,
|
|
100
|
-
to: opts.to,
|
|
101
|
-
templateVersion,
|
|
102
|
-
appliedRules: results.map((r) => ({
|
|
103
|
-
type: r.rule.type,
|
|
104
|
-
action: r.action,
|
|
105
|
-
path: r.path,
|
|
106
|
-
detail: r.detail,
|
|
107
|
-
})),
|
|
108
|
-
...summarizeResults(results),
|
|
109
|
-
nextActions: [
|
|
110
|
-
'git status / git diff 评估改动并 commit',
|
|
111
|
-
'npm install 装新依赖(package-lock.json 已被删除,将重新生成)',
|
|
112
|
-
'miaoda skills sync 同步到新 stack 的 agent skills',
|
|
113
|
-
],
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* 从 tmp 端 template package.json 中删除 user 项目通常拥有的字段,避免 merge-json 时这些
|
|
119
|
-
* 字段被 template 值覆盖。需要 strip 的字段以 npm 模板里"通常专属于具体应用"的为准。
|
|
120
|
-
*/
|
|
121
|
-
function stripUserOwnedFields(pkgPath) {
|
|
122
|
-
if (!node_fs_1.default.existsSync(pkgPath))
|
|
123
|
-
return;
|
|
124
|
-
const json = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
125
|
-
const userOwned = [
|
|
126
|
-
'name',
|
|
127
|
-
'version',
|
|
128
|
-
'private',
|
|
129
|
-
'description',
|
|
130
|
-
'author',
|
|
131
|
-
'keywords',
|
|
132
|
-
'license',
|
|
133
|
-
'repository',
|
|
134
|
-
'homepage',
|
|
135
|
-
'bugs',
|
|
136
|
-
];
|
|
137
|
-
let changed = false;
|
|
138
|
-
for (const key of userOwned) {
|
|
139
|
-
if (Reflect.deleteProperty(json, key))
|
|
140
|
-
changed = true;
|
|
141
|
-
}
|
|
142
|
-
if (changed) {
|
|
143
|
-
node_fs_1.default.writeFileSync(pkgPath, JSON.stringify(json, null, 2) + '\n');
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function summarizeResults(results) {
|
|
147
|
-
const moved = [];
|
|
148
|
-
const deleted = [];
|
|
149
|
-
const synced = [];
|
|
150
|
-
const merged = [];
|
|
151
|
-
const patched = [];
|
|
152
|
-
let skipped = 0;
|
|
153
|
-
for (const r of results) {
|
|
154
|
-
if (r.path === undefined)
|
|
155
|
-
continue;
|
|
156
|
-
switch (r.action) {
|
|
157
|
-
case 'moved':
|
|
158
|
-
moved.push(r.path);
|
|
159
|
-
break;
|
|
160
|
-
case 'deleted':
|
|
161
|
-
deleted.push(r.path);
|
|
162
|
-
break;
|
|
163
|
-
case 'synced':
|
|
164
|
-
case 'created':
|
|
165
|
-
synced.push(r.path);
|
|
166
|
-
break;
|
|
167
|
-
case 'merged':
|
|
168
|
-
merged.push(r.path);
|
|
169
|
-
break;
|
|
170
|
-
case 'patched':
|
|
171
|
-
patched.push(r.path);
|
|
172
|
-
break;
|
|
173
|
-
case 'skipped':
|
|
174
|
-
skipped++;
|
|
175
|
-
break;
|
|
176
|
-
case 'appended':
|
|
177
|
-
case 'noop':
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return { moved, deleted, synced, merged, patched, skipped };
|
|
182
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* 跨 stack 迁移注册表 —— 按 (from, to) 二元组查找对应的 MigrateConfig。
|
|
4
|
-
*
|
|
5
|
-
* 跟 sync 注册表 (src/config/sync-configs/index.ts) 的关系:sync 一份配置对应一个 stack,
|
|
6
|
-
* migrate 一份配置对应 (源 stack, 目标 stack) 二元组;模板资产 migrate 在 runtime 从 npm
|
|
7
|
-
* 拉目标 stack 的 template tarball(init 用同一套 renderTemplate),不再用 upgrade/templates/
|
|
8
|
-
* 的子集。
|
|
9
|
-
*
|
|
10
|
-
* 新增迁移路径:
|
|
11
|
-
* 1. src/config/migrate-configs/<from>-to-<to>.ts 里 export default MIGRATE_CONFIG
|
|
12
|
-
* 2. 在 MIGRATE_REGISTRY 加一条
|
|
13
|
-
*/
|
|
14
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
-
};
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.getMigrateConfig = getMigrateConfig;
|
|
19
|
-
exports.listSupportedMigrations = listSupportedMigrations;
|
|
20
|
-
const vite_react_to_nestjs_react_fullstack_1 = __importDefault(require("./vite-react-to-nestjs-react-fullstack"));
|
|
21
|
-
/** key 格式:`<from>:<to>` */
|
|
22
|
-
const MIGRATE_REGISTRY = {
|
|
23
|
-
'vite-react:nestjs-react-fullstack': vite_react_to_nestjs_react_fullstack_1.default,
|
|
24
|
-
};
|
|
25
|
-
/** 返回 (from, to) 对应的 MigrateConfig;未注册时返回 null。 */
|
|
26
|
-
function getMigrateConfig(from, to) {
|
|
27
|
-
return MIGRATE_REGISTRY[`${from}:${to}`] ?? null;
|
|
28
|
-
}
|
|
29
|
-
/** 列出已支持的迁移路径,给 handler / 命令 help 用 */
|
|
30
|
-
function listSupportedMigrations() {
|
|
31
|
-
return Object.keys(MIGRATE_REGISTRY).map((key) => {
|
|
32
|
-
const [from, to] = key.split(':');
|
|
33
|
-
return { from, to };
|
|
34
|
-
});
|
|
35
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* vite-react → nestjs-react-fullstack 的跨 stack 迁移规则。
|
|
4
|
-
*
|
|
5
|
-
* 分类原则(与飞书 wiki 设计文档对齐):
|
|
6
|
-
* - 用户领地(src / public / shared / .env / .spark_project):原样保留,搬到新布局位置;
|
|
7
|
-
* - 模板领地(vite.config / tsconfig / scripts / .githooks / lint 配置 / 入口 html 等):
|
|
8
|
-
* 用 fullstack 模板版本整体替换;
|
|
9
|
-
* - package.json:字段级 merge —— 顶层 user 字段(name/version/private)保留,依赖列表
|
|
10
|
-
* 做 set union(template 同名包覆盖版本号 → 避免依赖冲突),scripts 同名替换为 template
|
|
11
|
-
* 版本。
|
|
12
|
-
*
|
|
13
|
-
* 模板资产来源:upgrade/templates/nestjs-react-fullstack/templates/(cli 内嵌的脚手架资产,
|
|
14
|
-
* 与 sync 共享同一份)。
|
|
15
|
-
*/
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.MIGRATE_CONFIG = void 0;
|
|
18
|
-
exports.MIGRATE_CONFIG = {
|
|
19
|
-
from: 'vite-react',
|
|
20
|
-
to: 'nestjs-react-fullstack',
|
|
21
|
-
rules: [
|
|
22
|
-
// ===== 1. 用户领地搬迁 =====
|
|
23
|
-
// src/ 是 vite-react 的业务代码根,fullstack 拆 client/server,全部归 client/src/
|
|
24
|
-
{ type: 'move-directory', from: 'src', to: 'client/src' },
|
|
25
|
-
// public/ 是 vite 端的静态资源根,跟随 client/
|
|
26
|
-
{ type: 'move-directory', from: 'public', to: 'client/public' },
|
|
27
|
-
// shared/ 在两边布局相同,不动。.env / .spark_project 同理,不写规则即可。
|
|
28
|
-
// ===== 2. 清理 vite-react 专属残留 =====
|
|
29
|
-
// 根 index.html 被 client/index.html 取代(fullstack 模板自己带)
|
|
30
|
-
{ type: 'delete-file', to: 'index.html' },
|
|
31
|
-
// ESLint 9 flat config 文件名变了
|
|
32
|
-
{ type: 'delete-file', to: 'eslint.config.mjs' },
|
|
33
|
-
// 依赖集合变了,lockfile 重新生成(首次 npm install 时)
|
|
34
|
-
{ type: 'delete-file', to: 'package-lock.json' },
|
|
35
|
-
// ===== 3. 从 user package.json 删除 vite-react 专属字段 =====
|
|
36
|
-
// 这步在 merge-json 之前,确保 vite-react 的 preset 不残留。
|
|
37
|
-
// merge-json 是"template 覆盖同名 key + user 多出 key 保留",所以 user 的 vite-react
|
|
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 类型)。
|
|
48
|
-
{
|
|
49
|
-
type: 'delete-json-keys',
|
|
50
|
-
to: 'package.json',
|
|
51
|
-
keys: [
|
|
52
|
-
// 顶层 "type": "module" —— vite-react ESM 入口约定,nestjs CommonJS 不需要
|
|
53
|
-
'type',
|
|
54
|
-
// vite-react 的 vite preset,nestjs 用 fullstack-vite-preset
|
|
55
|
-
'devDependencies.@lark-apaas/coding-preset-vite-react',
|
|
56
|
-
// vite-react 的 ESLint preset
|
|
57
|
-
'devDependencies.@lark-apaas/coding-presets-react',
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
// ===== 4. 模板资产覆盖 / 新增 =====
|
|
61
|
-
// 顶层配置文件:直接用 fullstack 模板版本覆盖(用户改过的话靠 git diff 兜底自查)
|
|
62
|
-
{ type: 'file', from: 'vite.config.ts', to: 'vite.config.ts', overwrite: true },
|
|
63
|
-
{ type: 'file', from: 'tsconfig.json', to: 'tsconfig.json', overwrite: true },
|
|
64
|
-
{ type: 'file', from: 'tsconfig.app.json', to: 'tsconfig.app.json', overwrite: true },
|
|
65
|
-
{ type: 'file', from: 'tsconfig.node.json', to: 'tsconfig.node.json', overwrite: true },
|
|
66
|
-
{ type: 'file', from: 'eslint.config.js', to: 'eslint.config.js', overwrite: true },
|
|
67
|
-
{ type: 'file', from: 'nest-cli.json', to: 'nest-cli.json', overwrite: true },
|
|
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
|
|
79
|
-
{ type: 'file', from: 'client/index.html', to: 'client/index.html', overwrite: true },
|
|
80
|
-
{ type: 'file', from: 'client/src/index.tsx', to: 'client/src/index.tsx', overwrite: true },
|
|
81
|
-
// 整目录复制(不存在则创建,存在则递归覆盖同名文件)
|
|
82
|
-
// server/:NestJS SSR runtime,固定模板,不需要业务定制
|
|
83
|
-
{ type: 'directory', from: 'server', to: 'server', overwrite: true },
|
|
84
|
-
// scripts/:dev / build / lint / hooks 全套脚本
|
|
85
|
-
{ type: 'directory', from: 'scripts', to: 'scripts', overwrite: true },
|
|
86
|
-
// .githooks/:pre-commit
|
|
87
|
-
{ type: 'directory', from: '.githooks', to: '.githooks', overwrite: true },
|
|
88
|
-
// shared/ 是用户领地(不在 move 阶段动),但 fullstack 模板带了 shared/api.interface.ts 之类
|
|
89
|
-
// 的 fallback 文件 —— 用 overwrite: false 把 template 新增的文件加入 user shared/,user
|
|
90
|
-
// 已有同名文件保持不动。
|
|
91
|
-
{ type: 'directory', from: 'shared', to: 'shared', overwrite: false },
|
|
92
|
-
// .env 同样 fallback —— user 已有 .env 保留,否则用 template 的(默认 logger 配置)
|
|
93
|
-
{ type: 'file', from: '.env', to: '.env', overwrite: false },
|
|
94
|
-
// ===== 5. package.json 字段级 merge =====
|
|
95
|
-
// 用 template 的 partial pkg(只含 dependencies / devDependencies / scripts)deep merge
|
|
96
|
-
// 进 user pkg:
|
|
97
|
-
// - 顶层 user 字段(name / version / private)保留 —— template 不写这些字段
|
|
98
|
-
// - dependencies / devDependencies:template 覆盖同名版本号,user 业务 deps 保留
|
|
99
|
-
// - scripts:template 同名脚本覆盖 user 同名脚本,user 自定义脚本保留
|
|
100
|
-
// 模板源是 upgrade/templates/nestjs-react-fullstack/templates/package.json
|
|
101
|
-
{
|
|
102
|
-
type: 'merge-json',
|
|
103
|
-
from: 'package.json',
|
|
104
|
-
to: 'package.json',
|
|
105
|
-
},
|
|
106
|
-
// ===== 6. 切 stack =====
|
|
107
|
-
// 收尾:把 .spark/meta.json 的 stack 切到 nestjs-react-fullstack,后续 miaoda app sync
|
|
108
|
-
// 会按新 stack 的 SyncConfig 跑
|
|
109
|
-
{ type: 'set-stack', stack: 'nestjs-react-fullstack' },
|
|
110
|
-
],
|
|
111
|
-
};
|
|
112
|
-
exports.default = exports.MIGRATE_CONFIG;
|
package/dist/config/migrate.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* MigrateRule 类型定义 —— miaoda app migrate(跨 stack 迁移)的核心抽象。
|
|
4
|
-
*
|
|
5
|
-
* 跟 sync 的关系:sync 是**同 stack 内**的版本同步(脚手架更新、平台依赖钉版本);
|
|
6
|
-
* migrate 是**跨 stack 切换**(如 vite-react → nestjs-react-fullstack)。
|
|
7
|
-
*
|
|
8
|
-
* 复用:MigrateRule 把 SyncRule 全部纳入联合类型(10 种)—— migrate 也要做覆盖 / 删除 /
|
|
9
|
-
* merge-json / patch-script 等动作,没必要重新定义。
|
|
10
|
-
*
|
|
11
|
-
* 新增:MoveRule 和 SetStackRule 是 migrate 专有的语义,sync 用不上:
|
|
12
|
-
* - MoveRule:把用户领地从老布局搬到新布局(如 src/ → client/src/),完成后源消失。
|
|
13
|
-
* - SetStackRule:把 .spark/meta.json 的 stack 字段切到新 stack,migrate 必跑的收尾。
|
|
14
|
-
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Codemod:把 `@lark-apaas/client-toolkit-lite` 的 import 改写到 `@lark-apaas/client-toolkit`
|
|
4
|
-
* 的对应子路径。
|
|
5
|
-
*
|
|
6
|
-
* 用于 `miaoda app migrate vite-react → nestjs-react-fullstack`:迁移后让两个包共存属于
|
|
7
|
-
* lib 冗余(两份 react / 两套主题 / 两套 axios),所以扫 client/src 把 lite import 改写到
|
|
8
|
-
* full 等价子路径,然后用 delete-json-keys 移除 lite dep。
|
|
9
|
-
*
|
|
10
|
-
* 表的来源:grep client-toolkit lib/ 目录定位每个 lite symbol 的 d.ts 路径,subpath 取相对
|
|
11
|
-
* 路径。同 symbol 多个 d.ts 时按优先级选最合理的——例如 AppContainer 优先 `components/` 而
|
|
12
|
-
* 非 `apis/components/`(跟 fullstack template 自带 client/src/index.tsx 的约定一致)。
|
|
13
|
-
*
|
|
14
|
-
* 边界:
|
|
15
|
-
* - 仅处理形如 `import [type] { A, B as C } from '@lark-apaas/client-toolkit-lite'` 的
|
|
16
|
-
* 单行 named import。default import / namespace import / 跨多行 specifier 表当作 unmapped。
|
|
17
|
-
* - 表里没有的 symbol:保留原 import 行 + 加一行 `// TODO[migrate]: ...` 注释;后续 build
|
|
18
|
-
* 会因为 lite dep 被删而失败,提示用户手动处理。
|
|
19
|
-
*/
|
|
20
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
-
};
|
|
23
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.rewriteLiteImports = rewriteLiteImports;
|
|
25
|
-
exports.rewriteCssText = rewriteCssText;
|
|
26
|
-
exports.rewriteText = rewriteText;
|
|
27
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
28
|
-
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
|
-
const LITE_PKG = '@lark-apaas/client-toolkit-lite';
|
|
86
|
-
const FULL_PKG = '@lark-apaas/client-toolkit';
|
|
87
|
-
/**
|
|
88
|
-
* 递归扫描 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
|
-
*/
|
|
92
|
-
function rewriteLiteImports(rootDir) {
|
|
93
|
-
const result = {
|
|
94
|
-
filesScanned: 0,
|
|
95
|
-
filesChanged: [],
|
|
96
|
-
rewrittenSymbols: 0,
|
|
97
|
-
unmapped: [],
|
|
98
|
-
};
|
|
99
|
-
if (!node_fs_1.default.existsSync(rootDir))
|
|
100
|
-
return result;
|
|
101
|
-
for (const file of walk(rootDir)) {
|
|
102
|
-
const isJs = /\.(ts|tsx|js|jsx)$/.test(file);
|
|
103
|
-
const isCss = file.endsWith('.css');
|
|
104
|
-
if (!isJs && !isCss)
|
|
105
|
-
continue;
|
|
106
|
-
result.filesScanned++;
|
|
107
|
-
const before = node_fs_1.default.readFileSync(file, 'utf-8');
|
|
108
|
-
if (!before.includes(LITE_PKG))
|
|
109
|
-
continue;
|
|
110
|
-
const rel = node_path_1.default.relative(rootDir, file);
|
|
111
|
-
const { text, count, unmapped } = isJs ? rewriteText(before, rel) : rewriteCssText(before, rel);
|
|
112
|
-
result.unmapped.push(...unmapped);
|
|
113
|
-
if (count > 0) {
|
|
114
|
-
node_fs_1.default.writeFileSync(file, text);
|
|
115
|
-
result.filesChanged.push(rel);
|
|
116
|
-
result.rewrittenSymbols += count;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return result;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* css 改写器:把 `@import "@lark-apaas/client-toolkit-lite/styles.css"` 重写到 full 的入口
|
|
123
|
-
* `@lark-apaas/client-toolkit/lib/index.css`;其他 lite styles 子路径(如 `styles/*`)
|
|
124
|
-
* 在 full 里没有对等物,留下 TODO 注释让用户手动处理。
|
|
125
|
-
*/
|
|
126
|
-
function rewriteCssText(source, fileLabel) {
|
|
127
|
-
const unmapped = [];
|
|
128
|
-
let count = 0;
|
|
129
|
-
const lines = source.split('\n');
|
|
130
|
-
const out = [];
|
|
131
|
-
// 匹配 `@import "@lark-apaas/client-toolkit-lite/<path>";` 或 single quote
|
|
132
|
-
const re = /^(\s*)@import\s+['"]@lark-apaas\/client-toolkit-lite\/([^'"]+)['"]\s*;?\s*$/;
|
|
133
|
-
for (let i = 0; i < lines.length; i++) {
|
|
134
|
-
const line = lines[i];
|
|
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 };
|
|
155
|
-
}
|
|
156
|
-
/** 对单个文件文本做改写。导出供单测直接验证。 */
|
|
157
|
-
function rewriteText(source, fileLabel) {
|
|
158
|
-
const unmapped = [];
|
|
159
|
-
let count = 0;
|
|
160
|
-
const lines = source.split('\n');
|
|
161
|
-
const out = [];
|
|
162
|
-
// 匹配单行 named import:捕获 indent / `type ` 修饰符 / specifier 段
|
|
163
|
-
const reImport = /^(\s*)import\s+(type\s+)?\{([^}]+)\}\s+from\s+['"]@lark-apaas\/client-toolkit-lite['"]\s*;?\s*$/;
|
|
164
|
-
for (let i = 0; i < lines.length; i++) {
|
|
165
|
-
const line = lines[i];
|
|
166
|
-
const m = reImport.exec(line);
|
|
167
|
-
if (!m) {
|
|
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;
|
|
244
|
-
}
|
|
245
|
-
function* walk(dir) {
|
|
246
|
-
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
247
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.'))
|
|
248
|
-
continue;
|
|
249
|
-
const full = node_path_1.default.join(dir, entry.name);
|
|
250
|
-
if (entry.isDirectory()) {
|
|
251
|
-
yield* walk(full);
|
|
252
|
-
}
|
|
253
|
-
else if (entry.isFile()) {
|
|
254
|
-
yield full;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.applyMigrateRules = applyMigrateRules;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const sync_rule_1 = require("./sync-rule");
|
|
10
|
-
const spark_meta_1 = require("./spark-meta");
|
|
11
|
-
const codemod_client_toolkit_lite_1 = require("./codemod-client-toolkit-lite");
|
|
12
|
-
const logger_1 = require("./logger");
|
|
13
|
-
/**
|
|
14
|
-
* 顺序执行 MigrateRule[]:
|
|
15
|
-
* - move-* / set-stack 是 migrate 专有 rule,本模块直接处理。
|
|
16
|
-
* - 其余(directory / file / merge-json / delete-* / add-script / ...)全部委派给
|
|
17
|
-
* applySyncRules —— 单条调用,逻辑跟 sync 完全对齐,避免行为漂移。
|
|
18
|
-
*
|
|
19
|
-
* 一条 rule 报错不影响后续(applySyncRules 内部已有 try/catch;本模块的 applyMove / applySetStack
|
|
20
|
-
* 当前不 catch —— 如果 move 失败说明状态已经被破坏,应当直接抛错让 handler 决定回滚或上报)。
|
|
21
|
-
*/
|
|
22
|
-
function applyMigrateRules(rules, opts) {
|
|
23
|
-
const results = [];
|
|
24
|
-
for (const rule of rules) {
|
|
25
|
-
if (rule.type === 'move-directory' || rule.type === 'move-file') {
|
|
26
|
-
results.push(applyMove(rule, opts));
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (rule.type === 'set-stack') {
|
|
30
|
-
results.push(applySetStack(rule, opts));
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (rule.type === 'delete-json-keys') {
|
|
34
|
-
results.push(applyDeleteJsonKeys(rule, opts));
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (rule.type === 'codemod-client-toolkit-lite') {
|
|
38
|
-
results.push(applyCodemodLite(rule, opts));
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
// 前面 if 已经把 migrate 专用 rule 都 narrow 掉,剩下的必然是 SyncRule
|
|
42
|
-
const [syncResult] = (0, sync_rule_1.applySyncRules)([rule], opts);
|
|
43
|
-
results.push(syncResult);
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
function applyMove(rule, opts) {
|
|
48
|
-
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
49
|
-
const src = node_path_1.default.join(opts.targetDir, rule.from);
|
|
50
|
-
const dest = node_path_1.default.join(opts.targetDir, rule.to);
|
|
51
|
-
const onConflict = rule.onConflict ?? 'merge';
|
|
52
|
-
if (!node_fs_1.default.existsSync(src)) {
|
|
53
|
-
(0, logger_1.log)(logPrefix, ` ○ ${rule.from} (source not found, skip)`);
|
|
54
|
-
return { rule, action: 'skipped', path: rule.to };
|
|
55
|
-
}
|
|
56
|
-
if (node_fs_1.default.existsSync(dest)) {
|
|
57
|
-
if (onConflict === 'skip') {
|
|
58
|
-
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (already exists, skip per onConflict=skip)`);
|
|
59
|
-
return { rule, action: 'skipped', path: rule.to };
|
|
60
|
-
}
|
|
61
|
-
if (onConflict === 'overwrite') {
|
|
62
|
-
node_fs_1.default.rmSync(dest, { recursive: true, force: true });
|
|
63
|
-
moveAtomic(src, dest);
|
|
64
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to} (overwritten)`);
|
|
65
|
-
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
66
|
-
}
|
|
67
|
-
// 'merge':把 src 的内容递归 overlay 到 dest,源 entry 完成后整体清掉
|
|
68
|
-
mergeOverlay(src, dest);
|
|
69
|
-
node_fs_1.default.rmSync(src, { recursive: true, force: true });
|
|
70
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to} (merged)`);
|
|
71
|
-
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
72
|
-
}
|
|
73
|
-
// 目标不存在 —— 直接 rename
|
|
74
|
-
const destDir = node_path_1.default.dirname(dest);
|
|
75
|
-
if (destDir !== '.' && !node_fs_1.default.existsSync(destDir)) {
|
|
76
|
-
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
77
|
-
}
|
|
78
|
-
moveAtomic(src, dest);
|
|
79
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to}`);
|
|
80
|
-
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* fs.renameSync 在跨设备 / 跨文件系统时报 EXDEV(Linux/macOS),fallback 到 cp -r + rm。
|
|
84
|
-
* worktree 内通常同一卷不会触发,但 tmp 目录跟 user app 可能不同卷,留这层兜底。
|
|
85
|
-
*/
|
|
86
|
-
function moveAtomic(src, dest) {
|
|
87
|
-
try {
|
|
88
|
-
node_fs_1.default.renameSync(src, dest);
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
if (err.code !== 'EXDEV') {
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
node_fs_1.default.cpSync(src, dest, { recursive: true });
|
|
95
|
-
node_fs_1.default.rmSync(src, { recursive: true, force: true });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* 把 src 的每个 entry 复制到 dest 同路径下(src 是文件则覆盖 dest 同名文件;src 是目录则
|
|
100
|
-
* 递归创建 + 复制)。同名冲突一律 src 覆盖 dest。本函数不删 src;调用方负责清理。
|
|
101
|
-
*/
|
|
102
|
-
function mergeOverlay(src, dest) {
|
|
103
|
-
const stat = node_fs_1.default.statSync(src);
|
|
104
|
-
if (stat.isFile()) {
|
|
105
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dest), { recursive: true });
|
|
106
|
-
node_fs_1.default.copyFileSync(src, dest);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (!node_fs_1.default.existsSync(dest)) {
|
|
110
|
-
node_fs_1.default.mkdirSync(dest, { recursive: true });
|
|
111
|
-
}
|
|
112
|
-
for (const entry of node_fs_1.default.readdirSync(src)) {
|
|
113
|
-
mergeOverlay(node_path_1.default.join(src, entry), node_path_1.default.join(dest, entry));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function applySetStack(rule, opts) {
|
|
117
|
-
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
118
|
-
(0, spark_meta_1.writeSparkMeta)(opts.targetDir, { stack: rule.stack });
|
|
119
|
-
(0, logger_1.log)(logPrefix, ` ✓ .spark/meta.json stack → ${rule.stack}`);
|
|
120
|
-
return { rule, action: 'patched', path: '.spark/meta.json', detail: rule.stack };
|
|
121
|
-
}
|
|
122
|
-
function applyDeleteJsonKeys(rule, opts) {
|
|
123
|
-
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
124
|
-
const dest = node_path_1.default.join(opts.targetDir, rule.to);
|
|
125
|
-
if (!node_fs_1.default.existsSync(dest)) {
|
|
126
|
-
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (not found, skip delete-json-keys)`);
|
|
127
|
-
return { rule, action: 'skipped', path: rule.to };
|
|
128
|
-
}
|
|
129
|
-
const text = node_fs_1.default.readFileSync(dest, 'utf-8');
|
|
130
|
-
const json = JSON.parse(text);
|
|
131
|
-
const deleted = [];
|
|
132
|
-
for (const dotKey of rule.keys) {
|
|
133
|
-
if (deleteAtDotPath(json, dotKey))
|
|
134
|
-
deleted.push(dotKey);
|
|
135
|
-
}
|
|
136
|
-
if (deleted.length === 0) {
|
|
137
|
-
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (no listed keys present)`);
|
|
138
|
-
return { rule, action: 'noop', path: rule.to };
|
|
139
|
-
}
|
|
140
|
-
// 保持原文件末尾换行约定(package.json 一般以 \n 结尾)
|
|
141
|
-
const trailing = text.endsWith('\n') ? '\n' : '';
|
|
142
|
-
node_fs_1.default.writeFileSync(dest, JSON.stringify(json, null, 2) + trailing);
|
|
143
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.to} (deleted ${deleted.length.toString()} key(s): ${deleted.join(', ')})`);
|
|
144
|
-
return { rule, action: 'patched', path: rule.to, detail: deleted.join(',') };
|
|
145
|
-
}
|
|
146
|
-
function applyCodemodLite(rule, opts) {
|
|
147
|
-
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
148
|
-
const root = node_path_1.default.join(opts.targetDir, rule.root ?? 'client/src');
|
|
149
|
-
const r = (0, codemod_client_toolkit_lite_1.rewriteLiteImports)(root);
|
|
150
|
-
if (r.filesChanged.length === 0 && r.unmapped.length === 0) {
|
|
151
|
-
(0, logger_1.log)(logPrefix, ` ○ ${rule.root ?? 'client/src'} (no @lark-apaas/client-toolkit-lite imports found)`);
|
|
152
|
-
return { rule, action: 'noop', path: rule.root ?? 'client/src' };
|
|
153
|
-
}
|
|
154
|
-
const detail = `${r.rewrittenSymbols.toString()} symbol(s) in ${r.filesChanged.length.toString()} file(s)` +
|
|
155
|
-
(r.unmapped.length > 0 ? `; ${r.unmapped.length.toString()} unmapped` : '');
|
|
156
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.root ?? 'client/src'} (codemod: ${detail})`);
|
|
157
|
-
for (const u of r.unmapped) {
|
|
158
|
-
(0, logger_1.log)(logPrefix, ` ⚠ unmapped: ${u.file}:${u.line.toString()} '${u.symbol}'`);
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
rule,
|
|
162
|
-
action: r.rewrittenSymbols > 0 ? 'patched' : 'noop',
|
|
163
|
-
path: rule.root ?? 'client/src',
|
|
164
|
-
detail,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* 按 dot path 删除嵌套对象里的一个 leaf key。返回是否真的删了一项。
|
|
169
|
-
* 不支持包含 `.` 的字段名(实际 dep 名 / scripts 名不会含 `.`,够用)。
|
|
170
|
-
*/
|
|
171
|
-
function deleteAtDotPath(obj, dotPath) {
|
|
172
|
-
const parts = dotPath.split('.');
|
|
173
|
-
let cursor = obj;
|
|
174
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
175
|
-
const next = cursor[parts[i]];
|
|
176
|
-
if (next === undefined || next === null || typeof next !== 'object')
|
|
177
|
-
return false;
|
|
178
|
-
cursor = next;
|
|
179
|
-
}
|
|
180
|
-
const leaf = parts[parts.length - 1];
|
|
181
|
-
if (!Object.prototype.hasOwnProperty.call(cursor, leaf))
|
|
182
|
-
return false;
|
|
183
|
-
// 用 Reflect.deleteProperty 避开 eslint 对 `delete obj[computedKey]` 的禁用规则
|
|
184
|
-
return Reflect.deleteProperty(cursor, leaf);
|
|
185
|
-
}
|