@lark-apaas/miaoda-cli 0.1.16 → 0.1.17-alpha.ddef3e9
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 +59 -0
- package/dist/cli/commands/deploy/modern.js +9 -0
- package/dist/cli/handlers/app/index.js +3 -1
- package/dist/cli/handlers/app/init.js +41 -12
- package/dist/cli/handlers/app/migrate.js +251 -0
- package/dist/cli/handlers/deploy/modern.js +39 -0
- package/dist/config/migrate-configs/index.js +35 -0
- package/dist/config/migrate-configs/vite-react-to-nestjs-react-fullstack.js +298 -0
- package/dist/config/migrate.js +15 -0
- package/dist/services/app/init/async-install.js +116 -0
- package/dist/services/app/init/index.js +7 -1
- package/dist/services/deploy/modern/atoms/local-release.js +7 -2
- package/dist/services/deploy/modern/pipelines/local.js +4 -1
- package/dist/utils/codemod-client-toolkit-lite.js +106 -0
- package/dist/utils/migrate-rule.js +319 -0
- package/package.json +1 -1
- package/upgrade/templates/design-stack/templates/scripts/dev-local.js +16 -7
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.js +19 -10
|
@@ -0,0 +1,319 @@
|
|
|
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
|
+
exports.rewriteViteConfigForFullstack = rewriteViteConfigForFullstack;
|
|
8
|
+
exports.rewriteAliasSrcPaths = rewriteAliasSrcPaths;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const sync_rule_1 = require("./sync-rule");
|
|
12
|
+
const spark_meta_1 = require("./spark-meta");
|
|
13
|
+
const codemod_client_toolkit_lite_1 = require("./codemod-client-toolkit-lite");
|
|
14
|
+
const logger_1 = require("./logger");
|
|
15
|
+
/**
|
|
16
|
+
* 顺序执行 MigrateRule[]:
|
|
17
|
+
* - move-* / set-stack 是 migrate 专有 rule,本模块直接处理。
|
|
18
|
+
* - 其余(directory / file / merge-json / delete-* / add-script / ...)全部委派给
|
|
19
|
+
* applySyncRules —— 单条调用,逻辑跟 sync 完全对齐,避免行为漂移。
|
|
20
|
+
*
|
|
21
|
+
* 一条 rule 报错不影响后续(applySyncRules 内部已有 try/catch;本模块的 applyMove / applySetStack
|
|
22
|
+
* 当前不 catch —— 如果 move 失败说明状态已经被破坏,应当直接抛错让 handler 决定回滚或上报)。
|
|
23
|
+
*/
|
|
24
|
+
function applyMigrateRules(rules, opts) {
|
|
25
|
+
const results = [];
|
|
26
|
+
for (const rule of rules) {
|
|
27
|
+
if (rule.type === 'move-directory' || rule.type === 'move-file') {
|
|
28
|
+
results.push(applyMove(rule, opts));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (rule.type === 'set-stack') {
|
|
32
|
+
results.push(applySetStack(rule, opts));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (rule.type === 'delete-json-keys') {
|
|
36
|
+
results.push(applyDeleteJsonKeys(rule, opts));
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (rule.type === 'codemod-client-toolkit-lite') {
|
|
40
|
+
results.push(applyCodemodLite(rule, opts));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (rule.type === 'codemod-vite-config-fullstack') {
|
|
44
|
+
results.push(applyCodemodViteConfigFullstack(rule, opts));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (rule.type === 'add-deps-from-template') {
|
|
48
|
+
results.push(applyAddDepsFromTemplate(rule, opts));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// 前面 if 已经把 migrate 专用 rule 都 narrow 掉,剩下的必然是 SyncRule
|
|
52
|
+
const [syncResult] = (0, sync_rule_1.applySyncRules)([rule], opts);
|
|
53
|
+
results.push(syncResult);
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
function applyMove(rule, opts) {
|
|
58
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
59
|
+
const src = node_path_1.default.join(opts.targetDir, rule.from);
|
|
60
|
+
const dest = node_path_1.default.join(opts.targetDir, rule.to);
|
|
61
|
+
const onConflict = rule.onConflict ?? 'merge';
|
|
62
|
+
if (!node_fs_1.default.existsSync(src)) {
|
|
63
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.from} (source not found, skip)`);
|
|
64
|
+
return { rule, action: 'skipped', path: rule.to };
|
|
65
|
+
}
|
|
66
|
+
if (node_fs_1.default.existsSync(dest)) {
|
|
67
|
+
if (onConflict === 'skip') {
|
|
68
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (already exists, skip per onConflict=skip)`);
|
|
69
|
+
return { rule, action: 'skipped', path: rule.to };
|
|
70
|
+
}
|
|
71
|
+
if (onConflict === 'overwrite') {
|
|
72
|
+
node_fs_1.default.rmSync(dest, { recursive: true, force: true });
|
|
73
|
+
moveAtomic(src, dest);
|
|
74
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to} (overwritten)`);
|
|
75
|
+
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
76
|
+
}
|
|
77
|
+
// 'merge':把 src 的内容递归 overlay 到 dest,源 entry 完成后整体清掉
|
|
78
|
+
mergeOverlay(src, dest);
|
|
79
|
+
node_fs_1.default.rmSync(src, { recursive: true, force: true });
|
|
80
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to} (merged)`);
|
|
81
|
+
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
82
|
+
}
|
|
83
|
+
// 目标不存在 —— 直接 rename
|
|
84
|
+
const destDir = node_path_1.default.dirname(dest);
|
|
85
|
+
if (destDir !== '.' && !node_fs_1.default.existsSync(destDir)) {
|
|
86
|
+
node_fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
moveAtomic(src, dest);
|
|
89
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.from} → ${rule.to}`);
|
|
90
|
+
return { rule, action: 'moved', path: rule.to, detail: rule.from };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* fs.renameSync 在跨设备 / 跨文件系统时报 EXDEV(Linux/macOS),fallback 到 cp -r + rm。
|
|
94
|
+
* worktree 内通常同一卷不会触发,但 tmp 目录跟 user app 可能不同卷,留这层兜底。
|
|
95
|
+
*/
|
|
96
|
+
function moveAtomic(src, dest) {
|
|
97
|
+
try {
|
|
98
|
+
node_fs_1.default.renameSync(src, dest);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err.code !== 'EXDEV') {
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
node_fs_1.default.cpSync(src, dest, { recursive: true });
|
|
105
|
+
node_fs_1.default.rmSync(src, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 把 src 的每个 entry 复制到 dest 同路径下(src 是文件则覆盖 dest 同名文件;src 是目录则
|
|
110
|
+
* 递归创建 + 复制)。同名冲突一律 src 覆盖 dest。本函数不删 src;调用方负责清理。
|
|
111
|
+
*/
|
|
112
|
+
function mergeOverlay(src, dest) {
|
|
113
|
+
const stat = node_fs_1.default.statSync(src);
|
|
114
|
+
if (stat.isFile()) {
|
|
115
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(dest), { recursive: true });
|
|
116
|
+
node_fs_1.default.copyFileSync(src, dest);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!node_fs_1.default.existsSync(dest)) {
|
|
120
|
+
node_fs_1.default.mkdirSync(dest, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
for (const entry of node_fs_1.default.readdirSync(src)) {
|
|
123
|
+
mergeOverlay(node_path_1.default.join(src, entry), node_path_1.default.join(dest, entry));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function applySetStack(rule, opts) {
|
|
127
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
128
|
+
(0, spark_meta_1.writeSparkMeta)(opts.targetDir, { stack: rule.stack });
|
|
129
|
+
(0, logger_1.log)(logPrefix, ` ✓ .spark/meta.json stack → ${rule.stack}`);
|
|
130
|
+
return { rule, action: 'patched', path: '.spark/meta.json', detail: rule.stack };
|
|
131
|
+
}
|
|
132
|
+
function applyDeleteJsonKeys(rule, opts) {
|
|
133
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
134
|
+
const dest = node_path_1.default.join(opts.targetDir, rule.to);
|
|
135
|
+
if (!node_fs_1.default.existsSync(dest)) {
|
|
136
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (not found, skip delete-json-keys)`);
|
|
137
|
+
return { rule, action: 'skipped', path: rule.to };
|
|
138
|
+
}
|
|
139
|
+
const text = node_fs_1.default.readFileSync(dest, 'utf-8');
|
|
140
|
+
const json = JSON.parse(text);
|
|
141
|
+
const deleted = [];
|
|
142
|
+
for (const dotKey of rule.keys) {
|
|
143
|
+
if (deleteAtDotPath(json, dotKey))
|
|
144
|
+
deleted.push(dotKey);
|
|
145
|
+
}
|
|
146
|
+
if (deleted.length === 0) {
|
|
147
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to} (no listed keys present)`);
|
|
148
|
+
return { rule, action: 'noop', path: rule.to };
|
|
149
|
+
}
|
|
150
|
+
// 保持原文件末尾换行约定(package.json 一般以 \n 结尾)
|
|
151
|
+
const trailing = text.endsWith('\n') ? '\n' : '';
|
|
152
|
+
node_fs_1.default.writeFileSync(dest, JSON.stringify(json, null, 2) + trailing);
|
|
153
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.to} (deleted ${deleted.length.toString()} key(s): ${deleted.join(', ')})`);
|
|
154
|
+
return { rule, action: 'patched', path: rule.to, detail: deleted.join(',') };
|
|
155
|
+
}
|
|
156
|
+
function applyCodemodLite(rule, opts) {
|
|
157
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
158
|
+
const root = node_path_1.default.join(opts.targetDir, rule.root ?? 'client/src');
|
|
159
|
+
const r = (0, codemod_client_toolkit_lite_1.rewriteLiteImports)(root);
|
|
160
|
+
if (r.filesChanged.length === 0 && r.unmapped.length === 0) {
|
|
161
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.root ?? 'client/src'} (no @lark-apaas/client-toolkit-lite imports found)`);
|
|
162
|
+
return { rule, action: 'noop', path: rule.root ?? 'client/src' };
|
|
163
|
+
}
|
|
164
|
+
const detail = `${r.rewrittenSymbols.toString()} symbol(s) in ${r.filesChanged.length.toString()} file(s)` +
|
|
165
|
+
(r.unmapped.length > 0 ? `; ${r.unmapped.length.toString()} unmapped` : '');
|
|
166
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rule.root ?? 'client/src'} (codemod: ${detail})`);
|
|
167
|
+
for (const u of r.unmapped) {
|
|
168
|
+
(0, logger_1.log)(logPrefix, ` ⚠ unmapped: ${u.file}:${u.line.toString()} '${u.symbol}'`);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
rule,
|
|
172
|
+
action: r.rewrittenSymbols > 0 ? 'patched' : 'noop',
|
|
173
|
+
path: rule.root ?? 'client/src',
|
|
174
|
+
detail,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function applyCodemodViteConfigFullstack(rule, opts) {
|
|
178
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
179
|
+
const rel = rule.to ?? 'vite.config.ts';
|
|
180
|
+
const dest = node_path_1.default.join(opts.targetDir, rel);
|
|
181
|
+
if (!node_fs_1.default.existsSync(dest)) {
|
|
182
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rel} (not found, skip)`);
|
|
183
|
+
return { rule, action: 'skipped', path: rel };
|
|
184
|
+
}
|
|
185
|
+
const before = node_fs_1.default.readFileSync(dest, 'utf-8');
|
|
186
|
+
const { text, aliasRewriteCount } = rewriteViteConfigForFullstack(before);
|
|
187
|
+
if (aliasRewriteCount === 0) {
|
|
188
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rel} (no src alias path to rewrite)`);
|
|
189
|
+
return { rule, action: 'noop', path: rel };
|
|
190
|
+
}
|
|
191
|
+
node_fs_1.default.writeFileSync(dest, text);
|
|
192
|
+
(0, logger_1.log)(logPrefix, ` ✓ ${rel} (rewrote ${aliasRewriteCount.toString()} alias path(s) src → client/src)`);
|
|
193
|
+
return {
|
|
194
|
+
rule,
|
|
195
|
+
action: 'patched',
|
|
196
|
+
path: rel,
|
|
197
|
+
detail: `${aliasRewriteCount.toString()} alias path(s)`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* 把 vite.config.ts 里 alias 字符串里 `src` 起头的路径重写到 `client/src`。
|
|
202
|
+
*
|
|
203
|
+
* vite-react 时代 user 业务代码在 `src/`,迁移后搬到 `client/src/`,vite.config
|
|
204
|
+
* 里的 alias(典型如 `'@': path.resolve(__dirname, 'src')`)必须同步改:
|
|
205
|
+
* path.resolve(<args>, 'src') → 'src' 替换为 'client/src'
|
|
206
|
+
* path.resolve(<args>, 'src/<rest>') → 同上
|
|
207
|
+
* path.join(<args>, 'src') → 同上
|
|
208
|
+
* './src' / './src/<rest>' 字符串 → './client/src' / './client/src/<rest>'
|
|
209
|
+
* 边界:仅匹配整段 'src' 起头(避免 'sub/src' 误改),保留单/双引号原样。
|
|
210
|
+
*
|
|
211
|
+
* 注:fullstack 形态识别 **不再** 通过 `defineConfig` 的 option 第二参,而是
|
|
212
|
+
* 由 `coding-preset-vite-react` 内部读 `process.env.MIAODA_APP_TYPE === '3'`
|
|
213
|
+
* 自动判断(fullstack stack 的 archType 是 3)—— 所以 vite.config 完全不需要
|
|
214
|
+
* 加 option,只需 alias 路径同步即可。
|
|
215
|
+
*/
|
|
216
|
+
function rewriteViteConfigForFullstack(source) {
|
|
217
|
+
const { text, count } = rewriteAliasSrcPaths(source);
|
|
218
|
+
return { text, aliasRewriteCount: count };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* vite.config 里 alias 路径重写:`src` 起头的路径加 `client/` 前缀。
|
|
222
|
+
*
|
|
223
|
+
* 匹配的形态(不限制在 resolve.alias 块内 —— vite.config 顶层很少出现非 alias 用的
|
|
224
|
+
* `src` 字符串,业务 import 走 `@/...`,所以全文匹配是安全的):
|
|
225
|
+
*
|
|
226
|
+
* path.resolve(<args>, 'src') → path.resolve(<args>, 'client/src')
|
|
227
|
+
* path.resolve(<args>, 'src/<rest>') → path.resolve(<args>, 'client/src/<rest>')
|
|
228
|
+
* path.join(<args>, 'src') → 同上
|
|
229
|
+
* './src' / './src/<rest>'(独立字符串) → './client/src' / './client/src/<rest>'
|
|
230
|
+
*
|
|
231
|
+
* 已含 `client/src` 的不动(视为已迁移),`sub/src` 之类(src 不在词首)不动。
|
|
232
|
+
*/
|
|
233
|
+
function rewriteAliasSrcPaths(source) {
|
|
234
|
+
let count = 0;
|
|
235
|
+
// path.resolve(...) / path.join(...) 的最后一个参数是 'src' 或 'src/...' 起头
|
|
236
|
+
// 匹配 ['"]src[/'"],保留引号
|
|
237
|
+
const pathCallRe = /(\bpath\s*\.\s*(?:resolve|join)\s*\([^)]*?,\s*)(['"`])(src)(\/[^'"`]*)?(['"`])/g;
|
|
238
|
+
let next = source.replace(pathCallRe, (_match, prefix, q1, _src, rest, q2) => {
|
|
239
|
+
count++;
|
|
240
|
+
return `${prefix}${q1}client/src${rest ?? ''}${q2}`;
|
|
241
|
+
});
|
|
242
|
+
// 独立的相对路径字符串字面量 './src' 或 './src/...'(不在 path.resolve 参数里)
|
|
243
|
+
// 注意:path.resolve / join 已被上一步处理,这里只匹配剩下的 './src...' 字面量。
|
|
244
|
+
// 这种用法在 vite.config 较少(典型是直接给 alias 字符串值),但兜底处理。
|
|
245
|
+
const relativeRe = /(['"`])\.\/(src)(\/[^'"`]*)?(['"`])/g;
|
|
246
|
+
next = next.replace(relativeRe, (_match, q1, _src, rest, q2) => {
|
|
247
|
+
count++;
|
|
248
|
+
return `${q1}./client/src${rest ?? ''}${q2}`;
|
|
249
|
+
});
|
|
250
|
+
return { text: next, count };
|
|
251
|
+
}
|
|
252
|
+
function applyAddDepsFromTemplate(rule, opts) {
|
|
253
|
+
const logPrefix = opts.logPrefix ?? 'migrate';
|
|
254
|
+
const userPath = node_path_1.default.join(opts.targetDir, rule.to ?? 'package.json');
|
|
255
|
+
const tplPath = node_path_1.default.join(opts.sourceRoot, rule.from ?? 'package.json');
|
|
256
|
+
if (!node_fs_1.default.existsSync(userPath)) {
|
|
257
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to ?? 'package.json'} (not found, skip add-deps-from-template)`);
|
|
258
|
+
return { rule, action: 'skipped', path: rule.to ?? 'package.json' };
|
|
259
|
+
}
|
|
260
|
+
if (!node_fs_1.default.existsSync(tplPath)) {
|
|
261
|
+
(0, logger_1.log)(logPrefix, ` ⚠ template ${rule.from ?? 'package.json'} not found, skip add-deps-from-template`);
|
|
262
|
+
return { rule, action: 'skipped', path: rule.to ?? 'package.json' };
|
|
263
|
+
}
|
|
264
|
+
const userText = node_fs_1.default.readFileSync(userPath, 'utf-8');
|
|
265
|
+
const userJson = JSON.parse(userText);
|
|
266
|
+
const tplJson = JSON.parse(node_fs_1.default.readFileSync(tplPath, 'utf-8'));
|
|
267
|
+
const added = [];
|
|
268
|
+
const missingInTpl = [];
|
|
269
|
+
for (const grp of ['dependencies', 'devDependencies']) {
|
|
270
|
+
const whitelist = rule[grp] ?? [];
|
|
271
|
+
if (whitelist.length === 0)
|
|
272
|
+
continue;
|
|
273
|
+
userJson[grp] ??= {};
|
|
274
|
+
for (const dep of whitelist) {
|
|
275
|
+
// 在 template 里找版本号:优先 tpl 同 grp,其次另一 grp(template 可能把 dep 写在
|
|
276
|
+
// dependencies/devDependencies 任一处,跟 user 当前 grp 不一定一致)
|
|
277
|
+
const tplVersion = tplJson[grp]?.[dep] ??
|
|
278
|
+
(grp === 'dependencies' ? tplJson.devDependencies?.[dep] : tplJson.dependencies?.[dep]);
|
|
279
|
+
if (tplVersion === undefined) {
|
|
280
|
+
missingInTpl.push(dep);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
userJson[grp][dep] = tplVersion;
|
|
284
|
+
added.push(`${grp}.${dep}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (added.length === 0 && missingInTpl.length === 0) {
|
|
288
|
+
(0, logger_1.log)(logPrefix, ` ○ ${rule.to ?? 'package.json'} (no deps to add)`);
|
|
289
|
+
return { rule, action: 'noop', path: rule.to ?? 'package.json' };
|
|
290
|
+
}
|
|
291
|
+
const trailing = userText.endsWith('\n') ? '\n' : '';
|
|
292
|
+
node_fs_1.default.writeFileSync(userPath, JSON.stringify(userJson, null, 2) + trailing);
|
|
293
|
+
(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(', ')}` : ''})`);
|
|
294
|
+
return {
|
|
295
|
+
rule,
|
|
296
|
+
action: 'patched',
|
|
297
|
+
path: rule.to ?? 'package.json',
|
|
298
|
+
detail: added.join(','),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 按 dot path 删除嵌套对象里的一个 leaf key。返回是否真的删了一项。
|
|
303
|
+
* 不支持包含 `.` 的字段名(实际 dep 名 / scripts 名不会含 `.`,够用)。
|
|
304
|
+
*/
|
|
305
|
+
function deleteAtDotPath(obj, dotPath) {
|
|
306
|
+
const parts = dotPath.split('.');
|
|
307
|
+
let cursor = obj;
|
|
308
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
309
|
+
const next = cursor[parts[i]];
|
|
310
|
+
if (next === undefined || next === null || typeof next !== 'object')
|
|
311
|
+
return false;
|
|
312
|
+
cursor = next;
|
|
313
|
+
}
|
|
314
|
+
const leaf = parts[parts.length - 1];
|
|
315
|
+
if (!Object.prototype.hasOwnProperty.call(cursor, leaf))
|
|
316
|
+
return false;
|
|
317
|
+
// 用 Reflect.deleteProperty 避开 eslint 对 `delete obj[computedKey]` 的禁用规则
|
|
318
|
+
return Reflect.deleteProperty(cursor, leaf);
|
|
319
|
+
}
|
package/package.json
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 流程:
|
|
7
7
|
// 1. env pull —— 拉沙箱身份/凭证到 .env.local
|
|
8
|
-
// 2.
|
|
9
|
-
// 3.
|
|
10
|
-
// 4.
|
|
8
|
+
// 2. action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件
|
|
9
|
+
// 3. skills sync —— 同步当前 stack 的 agent skills
|
|
10
|
+
// 4. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
11
|
+
// 5. 起单进程 dev server(design-stack 单进程,无 server/client 拆分)
|
|
11
12
|
//
|
|
12
13
|
// 设计同 nestjs-react-fullstack/dev-local.js,SDK 包不需要自己 require('dotenv'),
|
|
13
14
|
// env 加载收敛在启动脚本单点。SUDA_WEBUSER 适配同 nrf 那份。
|
|
@@ -26,7 +27,7 @@ function warn(msg) {
|
|
|
26
27
|
if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '4';
|
|
27
28
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
28
29
|
|
|
29
|
-
console.log('[dev-local] (1/
|
|
30
|
+
console.log('[dev-local] (1/5) env pull...');
|
|
30
31
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
31
32
|
if (hasLarkCli) {
|
|
32
33
|
let appId = '';
|
|
@@ -47,15 +48,23 @@ if (hasLarkCli) {
|
|
|
47
48
|
warn('lark-cli 未安装,跳过 env pull;请确保 .env.local 已就绪');
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件。
|
|
52
|
+
console.log('[dev-local] (2/5) action-plugin init...');
|
|
53
|
+
try {
|
|
54
|
+
execSync('npx -y @lark-apaas/fullstack-cli@latest action-plugin init', { stdio: 'inherit' });
|
|
55
|
+
} catch {
|
|
56
|
+
warn('action-plugin init 失败,继续启动');
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
// skills sync —— --local 切 flat layout;不传 --version,handler 默认 coding-steering@latest。
|
|
51
|
-
console.log('[dev-local] (
|
|
60
|
+
console.log('[dev-local] (3/5) miaoda skills sync...');
|
|
52
61
|
try {
|
|
53
62
|
execSync('npx -y @lark-apaas/miaoda-cli@latest skills sync --local', { stdio: 'inherit' });
|
|
54
63
|
} catch {
|
|
55
64
|
console.log(' (skills sync 失败,继续启动)');
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
console.log('[dev-local] (
|
|
67
|
+
console.log('[dev-local] (4/5) loading .env / .env.local...');
|
|
59
68
|
const dotenv = require('dotenv');
|
|
60
69
|
dotenv.config({ path: '.env.local' });
|
|
61
70
|
dotenv.config({ path: '.env' });
|
|
@@ -75,7 +84,7 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
75
84
|
}
|
|
76
85
|
}
|
|
77
86
|
|
|
78
|
-
console.log('[dev-local] (
|
|
87
|
+
console.log('[dev-local] (5/5) npm run dev');
|
|
79
88
|
const child = spawn('npm', ['run', 'dev'], { stdio: 'inherit', env: process.env });
|
|
80
89
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
81
90
|
child.on('error', (err) => {
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
//
|
|
6
6
|
// 流程:
|
|
7
7
|
// 1. env pull —— 拉沙箱身份/凭证到 .env.local
|
|
8
|
-
// 2.
|
|
9
|
-
// 3.
|
|
10
|
-
// 4.
|
|
8
|
+
// 2. action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件
|
|
9
|
+
// 3. skills sync —— 同步当前 stack 的 agent skills
|
|
10
|
+
// 4. dotenv 加载 .env / .env.local 到 process.env(含 SUDA_WEBUSER 适配)
|
|
11
|
+
// 5. 并发起 dev:server + dev:client(子进程继承 process.env)
|
|
11
12
|
//
|
|
12
13
|
// 关键设计:本脚本在 spawn 子进程之前先把 .env / .env.local 加载到 process.env,
|
|
13
14
|
// 然后 spawn 的 server / client 进程通过 env 继承直接拿到——SDK(fullstack-nestjs-core
|
|
@@ -34,7 +35,7 @@ if (!process.env.MIAODA_APP_TYPE) process.env.MIAODA_APP_TYPE = '3';
|
|
|
34
35
|
process.env.MIAODA_LOCAL_DEV = '1';
|
|
35
36
|
|
|
36
37
|
// 1. env pull
|
|
37
|
-
console.log('[dev-local] (1/
|
|
38
|
+
console.log('[dev-local] (1/5) env pull...');
|
|
38
39
|
const hasLarkCli = spawnSync('command', ['-v', 'lark-cli'], { shell: true, stdio: 'ignore' }).status === 0;
|
|
39
40
|
if (hasLarkCli) {
|
|
40
41
|
let appId = '';
|
|
@@ -55,20 +56,28 @@ if (hasLarkCli) {
|
|
|
55
56
|
warn('lark-cli 未安装,跳过 env pull;请确保 .env.local 已就绪');
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
// 2.
|
|
59
|
+
// 2. action-plugin init —— 装 user app 在 package.json.actionPlugins 里声明的插件。
|
|
60
|
+
console.log('[dev-local] (2/5) action-plugin init...');
|
|
61
|
+
try {
|
|
62
|
+
execSync('npx -y @lark-apaas/fullstack-cli@latest action-plugin init', { stdio: 'inherit' });
|
|
63
|
+
} catch {
|
|
64
|
+
warn('action-plugin init 失败,继续启动');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. skills sync —— --local 切到 flat layout (.agents/skills + .claude/skills 软链),
|
|
59
68
|
// 跟沙箱 nested layout 区分。不传 --version,handler 默认拉 coding-steering@latest,
|
|
60
69
|
// 保证每次本地 npm run dev 都把 skills 升到最新。
|
|
61
|
-
console.log('[dev-local] (
|
|
70
|
+
console.log('[dev-local] (3/5) miaoda skills sync...');
|
|
62
71
|
try {
|
|
63
72
|
execSync('npx -y @lark-apaas/miaoda-cli@latest skills sync --local', { stdio: 'inherit' });
|
|
64
73
|
} catch {
|
|
65
74
|
console.log(' (skills sync 失败,继续启动)');
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
//
|
|
77
|
+
// 4. 加载 .env / .env.local 到 process.env
|
|
69
78
|
// dotenv 默认 override:false,先到先得 → 先 .env.local 让它优先于 .env;
|
|
70
79
|
// shell env 已在 process.env,两次 config 都不会覆盖。
|
|
71
|
-
console.log('[dev-local] (
|
|
80
|
+
console.log('[dev-local] (4/5) loading .env / .env.local...');
|
|
72
81
|
const dotenv = require('dotenv');
|
|
73
82
|
dotenv.config({ path: '.env.local' });
|
|
74
83
|
dotenv.config({ path: '.env' });
|
|
@@ -89,8 +98,8 @@ if (process.env.SUDA_WEBUSER) {
|
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
//
|
|
93
|
-
console.log('[dev-local] (
|
|
101
|
+
// 5. 并发起前后端 dev server
|
|
102
|
+
console.log('[dev-local] (5/5) 并发起 dev:server + dev:client');
|
|
94
103
|
const child = spawn(
|
|
95
104
|
'npx',
|
|
96
105
|
[
|