@lark-apaas/miaoda-cli 0.1.16-alpha.c0b0ae2 → 0.1.16-alpha.cfec413
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 +16 -49
- package/dist/cli/handlers/app/index.js +1 -3
- package/dist/cli/handlers/app/init.js +41 -12
- package/dist/services/app/init/async-install.js +116 -0
- package/dist/services/app/init/index.js +6 -1
- package/package.json +1 -1
- 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 -216
- package/dist/config/migrate.js +0 -15
- package/dist/utils/codemod-client-toolkit-lite.js +0 -106
- package/dist/utils/migrate-rule.js +0 -558
|
@@ -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')
|
|
@@ -172,6 +123,7 @@ function registerAppInit(parent) {
|
|
|
172
123
|
.option('--template <stack>', `技术栈短名(${index_1.SUPPORTED_STACKS.join(' / ')})`)
|
|
173
124
|
.option('--conf <json>', 'init 配置 JSON。支持 {"version": "<template 版本>"},默认 latest')
|
|
174
125
|
.option('--skip-install', '跳过依赖安装', false)
|
|
126
|
+
.option('--async-install', '派发后台进程装依赖并立即返回(与 --skip-install 互斥)', false)
|
|
175
127
|
.addOption((0, shared_1.appIdOption)())
|
|
176
128
|
.addHelpText('after', `
|
|
177
129
|
幂等
|
|
@@ -200,17 +152,31 @@ function registerAppInit(parent) {
|
|
|
200
152
|
为空都会 fallback npm install。
|
|
201
153
|
JSON 模式下子进程 stdout 重定向到 stderr,避免污染最终 emit 的 JSON。
|
|
202
154
|
|
|
155
|
+
异步安装(--async-install)
|
|
156
|
+
跳过同步 npm install,派发一个 detached 后台进程装依赖后立即返回。
|
|
157
|
+
.spark/meta.json 在返回前写出(= 脚手架就绪),install 真实结果由 event marker 表达。
|
|
158
|
+
仅沙箱(SANDBOX_ID 非空)写 marker(presence 定成败,content 给排障):
|
|
159
|
+
成功 → /tmp/event/MIAODA_DEPS_READY
|
|
160
|
+
失败 → /tmp/event/MIAODA_DEPS_FAILED
|
|
161
|
+
安装日志:/tmp/async_install_dep.std.log
|
|
162
|
+
失败恢复:靠后续 'miaoda app sync'(dev.sh 入口 / 沙箱 pod 启动会跑)兜底,init 不重试。
|
|
163
|
+
与 --skip-install 互斥。
|
|
164
|
+
|
|
203
165
|
JSON 输出
|
|
204
166
|
已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "..."}}
|
|
205
167
|
新初始化:{"data": {"initialized": true, "template": "...", "templateVersion": "...", "steeringVersion": "...",
|
|
206
168
|
"appId": "...", "platformStackFound": true, "platformSyncedFiles": [...],
|
|
207
169
|
"installed": true, "installSource": "cache|npm|skipped", "installHash": "...", ...}}
|
|
170
|
+
async 模式:{"data": {"initialized": true, "asyncInstall": true, "installed": false,
|
|
171
|
+
"installSource": "async", "installPid": 123, "installLogPath": "...",
|
|
172
|
+
"eventReadyPath": "...", "eventFailedPath": "..."}}
|
|
208
173
|
|
|
209
174
|
示例
|
|
210
175
|
$ miaoda app init --template vite-react
|
|
211
176
|
$ miaoda app init --template nestjs-react-fullstack --app-id app_xxx
|
|
212
177
|
$ miaoda app init --template vite-react --conf '{"version": "0.1.0"}'
|
|
213
178
|
$ miaoda app init --template vite-react --skip-install
|
|
179
|
+
$ miaoda app init --template vite-react --async-install
|
|
214
180
|
$ MIAODA_DEP_CACHE_DIR=/tmp/dep-cache miaoda app init --template vite-react
|
|
215
181
|
`);
|
|
216
182
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
@@ -220,6 +186,7 @@ JSON 输出
|
|
|
220
186
|
conf,
|
|
221
187
|
skipInstall: rawOpts.skipInstall,
|
|
222
188
|
appId: rawOpts.appId,
|
|
189
|
+
asyncInstall: rawOpts.asyncInstall,
|
|
223
190
|
});
|
|
224
191
|
}));
|
|
225
192
|
}
|
|
@@ -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; } });
|
|
@@ -60,6 +60,9 @@ async function handleAppInit(opts) {
|
|
|
60
60
|
next_actions: [`可用 stack:${index_1.SUPPORTED_STACKS.join(', ')}`],
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
+
if (opts.asyncInstall && opts.skipInstall) {
|
|
64
|
+
throw new error_1.AppError('ARGS_INVALID', '--async-install 与 --skip-install 互斥');
|
|
65
|
+
}
|
|
63
66
|
const version = opts.conf?.version;
|
|
64
67
|
const projectName = node_path_1.default.basename(targetDir);
|
|
65
68
|
const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
|
|
@@ -86,13 +89,17 @@ async function handleAppInit(opts) {
|
|
|
86
89
|
(0, logger_1.log)('init', `⚠ skills sync failed (continuing init): ${steeringError}`);
|
|
87
90
|
steeringResult = { version: 'unknown', syncedSkills: [], techSynced: false };
|
|
88
91
|
}
|
|
89
|
-
//
|
|
92
|
+
// 依赖安装:async 模式留到 meta 落盘后派发后台进程(不等装完),同步模式当场装。
|
|
93
|
+
// 同步:装模板钉死的依赖。不带 <pkg>@latest 位置参数,因此 npm 不会改写 package.json/lockfile
|
|
90
94
|
// —— "升管控包到 latest" 是 sync 的职责,在 dev.sh 入口跑 `miaoda app sync` 时触发。
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
let installResult;
|
|
96
|
+
if (!opts.asyncInstall) {
|
|
97
|
+
installResult = (0, index_1.installDependencies)({
|
|
98
|
+
targetDir,
|
|
99
|
+
skip: opts.skipInstall,
|
|
100
|
+
quietStdout: (0, output_1.isJsonMode)(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
96
103
|
// template 自带 .githooks/pre-commit;如果用户项目已 git init,顺便设上 core.hooksPath。
|
|
97
104
|
const hookActivation = (0, githooks_1.activateGitHooks)(targetDir);
|
|
98
105
|
(0, index_1.writeSparkMeta)(targetDir, {
|
|
@@ -101,8 +108,18 @@ async function handleAppInit(opts) {
|
|
|
101
108
|
archType: tplResult.archType,
|
|
102
109
|
app_id: opts.appId,
|
|
103
110
|
});
|
|
111
|
+
// async 模式:meta 已落盘(= 脚手架就绪),再派发后台安装并立即返回(不等装完)。
|
|
112
|
+
let asyncDispatch;
|
|
113
|
+
if (opts.asyncInstall) {
|
|
114
|
+
asyncDispatch = (0, index_1.dispatchAsyncInstall)({ targetDir });
|
|
115
|
+
}
|
|
104
116
|
if (!(0, output_1.isJsonMode)()) {
|
|
105
|
-
|
|
117
|
+
if (opts.asyncInstall && asyncDispatch) {
|
|
118
|
+
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install dispatched in background pid ${String(asyncDispatch.pid)}) in ${targetDir}\n`);
|
|
119
|
+
}
|
|
120
|
+
else if (installResult) {
|
|
121
|
+
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
|
|
122
|
+
}
|
|
106
123
|
}
|
|
107
124
|
(0, output_1.emit)({
|
|
108
125
|
data: {
|
|
@@ -117,11 +134,23 @@ async function handleAppInit(opts) {
|
|
|
117
134
|
techSynced: steeringResult.techSynced,
|
|
118
135
|
steeringError,
|
|
119
136
|
gitHooks: hookActivation.action,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
...(opts.asyncInstall && asyncDispatch
|
|
138
|
+
? {
|
|
139
|
+
asyncInstall: true,
|
|
140
|
+
installed: false,
|
|
141
|
+
installSource: 'async',
|
|
142
|
+
installPid: asyncDispatch.pid,
|
|
143
|
+
installLogPath: asyncDispatch.logPath,
|
|
144
|
+
eventReadyPath: asyncDispatch.eventReadyPath,
|
|
145
|
+
eventFailedPath: asyncDispatch.eventFailedPath,
|
|
146
|
+
}
|
|
147
|
+
: {
|
|
148
|
+
installed: installResult?.installed,
|
|
149
|
+
installSource: installResult?.source,
|
|
150
|
+
installHash: installResult?.hash,
|
|
151
|
+
cacheZip: installResult?.cacheZip,
|
|
152
|
+
installError: installResult?.error,
|
|
153
|
+
}),
|
|
125
154
|
},
|
|
126
155
|
});
|
|
127
156
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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.ASYNC_INSTALL_LOG = void 0;
|
|
7
|
+
exports.isSandboxEnv = isSandboxEnv;
|
|
8
|
+
exports.runAsyncInstallWorker = runAsyncInstallWorker;
|
|
9
|
+
exports.dispatchAsyncInstall = dispatchAsyncInstall;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const install_1 = require("./install");
|
|
14
|
+
/** event marker 目录,写死;写前 mkdir -p。沙箱里 code server 轮询此目录。 */
|
|
15
|
+
const EVENT_DIR = '/tmp/event';
|
|
16
|
+
/** 安装成功 marker 名 */
|
|
17
|
+
const MARKER_READY = 'MIAODA_DEPS_READY';
|
|
18
|
+
/** 安装失败 marker 名 */
|
|
19
|
+
const MARKER_FAILED = 'MIAODA_DEPS_FAILED';
|
|
20
|
+
/** 后台 worker stdout/stderr 落盘路径 */
|
|
21
|
+
exports.ASYNC_INSTALL_LOG = '/tmp/async_install_dep.std.log';
|
|
22
|
+
/** 沙箱判定:SANDBOX_ID 非空。与 init handler 的 outputLayout 分流口径一致。 */
|
|
23
|
+
function isSandboxEnv() {
|
|
24
|
+
return process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '';
|
|
25
|
+
}
|
|
26
|
+
function nowIso() {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
function clearStaleMarkers(eventDir) {
|
|
30
|
+
for (const name of [MARKER_READY, MARKER_FAILED]) {
|
|
31
|
+
const p = node_path_1.default.join(eventDir, name);
|
|
32
|
+
if (node_fs_1.default.existsSync(p))
|
|
33
|
+
node_fs_1.default.rmSync(p, { force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function writeMarker(eventDir, name, content) {
|
|
37
|
+
node_fs_1.default.mkdirSync(eventDir, { recursive: true });
|
|
38
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(eventDir, name), JSON.stringify(content) + '\n', 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 后台 install worker 体:跑 installDependencies,按结果写 marker(仅沙箱)。
|
|
42
|
+
* 由 dispatchAsyncInstall 通过 `node -e` 在 detached 子进程里调用(见下)。
|
|
43
|
+
* presence 定成败:成功写 MIAODA_DEPS_READY,失败写 MIAODA_DEPS_FAILED;content 给排障。
|
|
44
|
+
*/
|
|
45
|
+
function runAsyncInstallWorker(opts) {
|
|
46
|
+
const eventDir = opts.eventDir ?? EVENT_DIR;
|
|
47
|
+
const sandbox = isSandboxEnv();
|
|
48
|
+
if (sandbox)
|
|
49
|
+
clearStaleMarkers(eventDir);
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = (0, install_1.installDependencies)({ targetDir: opts.targetDir, quietStdout: false });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57
|
+
if (sandbox) {
|
|
58
|
+
writeMarker(eventDir, MARKER_FAILED, {
|
|
59
|
+
ts: nowIso(),
|
|
60
|
+
error: msg,
|
|
61
|
+
logPath: exports.ASYNC_INSTALL_LOG,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!sandbox)
|
|
67
|
+
return;
|
|
68
|
+
if (result.source === 'failed') {
|
|
69
|
+
writeMarker(eventDir, MARKER_FAILED, {
|
|
70
|
+
ts: nowIso(),
|
|
71
|
+
error: result.error ?? 'npm install failed',
|
|
72
|
+
logPath: exports.ASYNC_INSTALL_LOG,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
writeMarker(eventDir, MARKER_READY, {
|
|
77
|
+
ts: nowIso(),
|
|
78
|
+
durationMs: Date.now() - start,
|
|
79
|
+
source: result.source,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 派发 detached 后台进程跑依赖安装并立即返回。
|
|
85
|
+
* 用 `node -e` 直接 require 本模块(编译产物)调 runAsyncInstallWorker,
|
|
86
|
+
* 不挂任何 CLI 子命令、对外不暴露;worker 内复用 installDependencies(cache / registry / fallback)。
|
|
87
|
+
* 沙箱下派发前清旧 marker;stdout/stderr 重定向到 logPath。
|
|
88
|
+
*/
|
|
89
|
+
function dispatchAsyncInstall(opts) {
|
|
90
|
+
const eventDir = opts.eventDir ?? EVENT_DIR;
|
|
91
|
+
const logPath = opts.logPath ?? exports.ASYNC_INSTALL_LOG;
|
|
92
|
+
const sandbox = isSandboxEnv();
|
|
93
|
+
if (sandbox)
|
|
94
|
+
clearStaleMarkers(eventDir);
|
|
95
|
+
// targetDir 走 argv 传,不拼进代码串(免转义);只把本模块路径 JSON 内联进去。
|
|
96
|
+
const workerCode = `require(${JSON.stringify(__filename)}).runAsyncInstallWorker({ targetDir: process.argv[1] })`;
|
|
97
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(logPath), { recursive: true });
|
|
98
|
+
const fd = node_fs_1.default.openSync(logPath, 'w');
|
|
99
|
+
try {
|
|
100
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, ['-e', workerCode, opts.targetDir], {
|
|
101
|
+
detached: true,
|
|
102
|
+
stdio: ['ignore', fd, fd],
|
|
103
|
+
});
|
|
104
|
+
child.unref();
|
|
105
|
+
return {
|
|
106
|
+
pid: child.pid,
|
|
107
|
+
logPath,
|
|
108
|
+
eventReadyPath: sandbox ? node_path_1.default.join(eventDir, MARKER_READY) : undefined,
|
|
109
|
+
eventFailedPath: sandbox ? node_path_1.default.join(eventDir, MARKER_FAILED) : undefined,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
// 父进程关掉自己那份 fd;子进程已通过 stdio dup 持有独立副本。
|
|
114
|
+
node_fs_1.default.closeSync(fd);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
|
|
3
|
+
exports.ASYNC_INSTALL_LOG = exports.isSandboxEnv = exports.runAsyncInstallWorker = exports.dispatchAsyncInstall = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.TEMPLATE_PACKAGE_BY_STACK = exports.SUPPORTED_STACKS = exports.renderTemplate = void 0;
|
|
4
4
|
var template_1 = require("./template");
|
|
5
5
|
Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
|
|
6
6
|
Object.defineProperty(exports, "SUPPORTED_STACKS", { enumerable: true, get: function () { return template_1.SUPPORTED_STACKS; } });
|
|
@@ -10,3 +10,8 @@ Object.defineProperty(exports, "readSparkMeta", { enumerable: true, get: functio
|
|
|
10
10
|
Object.defineProperty(exports, "writeSparkMeta", { enumerable: true, get: function () { return spark_meta_1.writeSparkMeta; } });
|
|
11
11
|
var install_1 = require("./install");
|
|
12
12
|
Object.defineProperty(exports, "installDependencies", { enumerable: true, get: function () { return install_1.installDependencies; } });
|
|
13
|
+
var async_install_1 = require("./async-install");
|
|
14
|
+
Object.defineProperty(exports, "dispatchAsyncInstall", { enumerable: true, get: function () { return async_install_1.dispatchAsyncInstall; } });
|
|
15
|
+
Object.defineProperty(exports, "runAsyncInstallWorker", { enumerable: true, get: function () { return async_install_1.runAsyncInstallWorker; } });
|
|
16
|
+
Object.defineProperty(exports, "isSandboxEnv", { enumerable: true, get: function () { return async_install_1.isSandboxEnv; } });
|
|
17
|
+
Object.defineProperty(exports, "ASYNC_INSTALL_LOG", { enumerable: true, get: function () { return async_install_1.ASYNC_INSTALL_LOG; } });
|
package/package.json
CHANGED
|
@@ -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
|
-
}
|