@lark-apaas/miaoda-cli 0.1.13 → 0.1.15
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.
|
@@ -10,16 +10,25 @@ const index_1 = require("../../../services/app/init/index");
|
|
|
10
10
|
const coding_steering_1 = require("../../../utils/coding-steering");
|
|
11
11
|
const logger_1 = require("../../../utils/logger");
|
|
12
12
|
const githooks_1 = require("../../../utils/githooks");
|
|
13
|
-
const sync_1 = require("../../../cli/handlers/app/sync");
|
|
14
13
|
const error_1 = require("../../../utils/error");
|
|
15
14
|
const output_1 = require("../../../utils/output");
|
|
16
15
|
/**
|
|
17
16
|
* miaoda app init
|
|
18
17
|
*
|
|
19
|
-
* 一站式本地脚手架:render template →
|
|
18
|
+
* 一站式本地脚手架:render template → skills sync → install deps,把
|
|
20
19
|
* {stack, version, archType, app_id?} 写入 .spark/meta.json。--app-id(或 MIAODA_APP_ID)提供时
|
|
21
20
|
* 持久化 app_id;运行端命令仍优先读 env,未设置时回退到 meta.json。
|
|
22
21
|
*
|
|
22
|
+
* init 不跑 `miaoda app sync` 的任何子动作(SyncRule 应用 / 平台脚本覆盖 / patches /
|
|
23
|
+
* 升管控包到 latest)。原因:
|
|
24
|
+
* - 本地 `npm run dev` 入口 dev.sh 已经会先跑 `miaoda app sync` 再起服务(看 templates
|
|
25
|
+
* /scripts/dev.sh),sync 在用户真正用 app 之前必然跑过一次
|
|
26
|
+
* - 沙箱 pod 启动阶段平台侧也会做同步(dev.sh 注释明文:"脚本同步由平台 pod 启动阶段做过")
|
|
27
|
+
* - 两条路径都不依赖 init 时的 sync,init 重跑一遍是冗余
|
|
28
|
+
* 副作用:init 完到首次 dev/sync 之间,user app 的 scripts/ 模板文件是 template 包发版时
|
|
29
|
+
* 的版本(没经 sync 覆盖);首次 install 装的也是 template 钉的版本(没升 latest),
|
|
30
|
+
* package.json 跟 template 完全一致。dev.sh 入口的 sync 会修正这些 drift。
|
|
31
|
+
*
|
|
23
32
|
* 幂等:.spark/meta.json 存在即表示已完整成功一次,直接退出,不重跑任何子步骤。
|
|
24
33
|
* 半渲染状态(package.json 在但 meta.json 不在)会重新跑完整流程。
|
|
25
34
|
*/
|
|
@@ -54,22 +63,14 @@ async function handleAppInit(opts) {
|
|
|
54
63
|
const version = opts.conf?.version;
|
|
55
64
|
const projectName = node_path_1.default.basename(targetDir);
|
|
56
65
|
const tplResult = (0, index_1.renderTemplate)({ stack, version, targetDir, projectName });
|
|
57
|
-
// sync 紧跟 template 渲染之后,赶在 install 之前:
|
|
58
|
-
// 这样 directory / merge-json / add-script 等 rule 都已落地,install 出来的 lockfile
|
|
59
|
-
// 与最终态对齐;patches 还能覆盖 template 提供的同名文件。
|
|
60
|
-
const syncRunResult = (0, sync_1.runStackSync)(stack, targetDir, 'init');
|
|
61
|
-
const syncSummary = (0, sync_1.summarizeSyncResults)(syncRunResult);
|
|
62
|
-
// template 渲染下来的 @lark-apaas/* 版本可能落后 MANAGED_PLATFORM_PACKAGES 白名单,赶在
|
|
63
|
-
// install 之前统一指 latest,这样一次 npm install 就直接装最新版本,不需要 install 完再补。
|
|
64
|
-
const upgradedPackages = (0, sync_1.upgradePlatformDeps)(targetDir, 'init');
|
|
65
66
|
// skills 同步软失败:拉不到 coding-steering 包不该阻断 writeSparkMeta /
|
|
66
67
|
// activateGitHooks(之前会让 .spark/meta.json 没写,下次 init 半渲染状态又得重跑全套)。
|
|
67
|
-
//
|
|
68
|
-
// -
|
|
69
|
-
//
|
|
70
|
-
// -
|
|
71
|
-
//
|
|
72
|
-
const outputLayout =
|
|
68
|
+
// 按运行环境(SANDBOX_ID)分流 outputLayout,不绑 stack:
|
|
69
|
+
// - 沙箱内(SANDBOX_ID 非空)→ nested (.agent/skills/steering/<stack>/skills/),
|
|
70
|
+
// 跟沙箱端 update-skills.sh 链路对齐
|
|
71
|
+
// - 本地(SANDBOX_ID 空)→ flat (.agents/skills + .claude/skills 软链),
|
|
72
|
+
// Agent / Claude Code 用同一份;nrf 在本地是主战场,vite-react 等也一样适用
|
|
73
|
+
const outputLayout = process.env.SANDBOX_ID !== undefined && process.env.SANDBOX_ID !== '' ? 'nested' : 'flat';
|
|
73
74
|
let steeringResult;
|
|
74
75
|
let steeringError;
|
|
75
76
|
try {
|
|
@@ -85,6 +86,8 @@ async function handleAppInit(opts) {
|
|
|
85
86
|
(0, logger_1.log)('init', `⚠ skills sync failed (continuing init): ${steeringError}`);
|
|
86
87
|
steeringResult = { version: 'unknown', syncedSkills: [], techSynced: false };
|
|
87
88
|
}
|
|
89
|
+
// 装模板钉死的依赖。不带 <pkg>@latest 位置参数,因此 npm 不会改写 package.json/lockfile
|
|
90
|
+
// —— "升管控包到 latest" 是 sync 的职责,在 dev.sh 入口跑 `miaoda app sync` 时触发。
|
|
88
91
|
const installResult = (0, index_1.installDependencies)({
|
|
89
92
|
targetDir,
|
|
90
93
|
skip: opts.skipInstall,
|
|
@@ -98,13 +101,8 @@ async function handleAppInit(opts) {
|
|
|
98
101
|
archType: tplResult.archType,
|
|
99
102
|
app_id: opts.appId,
|
|
100
103
|
});
|
|
101
|
-
const syncedSummary = syncRunResult.stackFound
|
|
102
|
-
? syncRunResult.rulesApplied.length > 0
|
|
103
|
-
? `${syncRunResult.rulesApplied.length.toString()} rule(s)`
|
|
104
|
-
: 'legacy'
|
|
105
|
-
: 'skipped';
|
|
106
104
|
if (!(0, output_1.isJsonMode)()) {
|
|
107
|
-
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version},
|
|
105
|
+
process.stdout.write(`✓ Initialized ${stack} (template ${tplResult.version}, steering ${steeringResult.version}, install ${installResult.source}) in ${targetDir}\n`);
|
|
108
106
|
}
|
|
109
107
|
(0, output_1.emit)({
|
|
110
108
|
data: {
|
|
@@ -118,14 +116,6 @@ async function handleAppInit(opts) {
|
|
|
118
116
|
syncedSkills: steeringResult.syncedSkills,
|
|
119
117
|
techSynced: steeringResult.techSynced,
|
|
120
118
|
steeringError,
|
|
121
|
-
// sync 流程的产出:rule-driven 时 appliedRules + 一系列汇总字段;fallback 到旧 platform-sync
|
|
122
|
-
// 时只填 platformSyncedFiles / platformMergedJsonFiles;stack 不识别时全空且 platformStackFound=false。
|
|
123
|
-
platformStackFound: syncRunResult.stackFound,
|
|
124
|
-
appliedRules: syncSummary.appliedRules,
|
|
125
|
-
platformSyncedFiles: syncSummary.syncedFiles,
|
|
126
|
-
platformMergedJsonFiles: syncSummary.mergedJsonFiles,
|
|
127
|
-
patchedScripts: syncSummary.patchedScripts,
|
|
128
|
-
upgradedPackages,
|
|
129
119
|
gitHooks: hookActivation.action,
|
|
130
120
|
installed: installResult.installed,
|
|
131
121
|
installSource: installResult.source,
|
|
@@ -7,8 +7,8 @@ exports.handleAppUpgrade = void 0;
|
|
|
7
7
|
exports.handleAppSync = handleAppSync;
|
|
8
8
|
exports.runStackSync = runStackSync;
|
|
9
9
|
exports.summarizeSyncResults = summarizeSyncResults;
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
10
|
+
exports.snapshotManagedDepSpecs = snapshotManagedDepSpecs;
|
|
11
|
+
exports.diffManagedSpecs = diffManagedSpecs;
|
|
12
12
|
const node_child_process_1 = require("node:child_process");
|
|
13
13
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -79,17 +79,13 @@ async function handleAppSync(opts) {
|
|
|
79
79
|
],
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
// 2.
|
|
83
|
-
const upgradedPackages = upgradePlatformDeps(targetDir, 'sync');
|
|
84
|
-
// 3. activate git hooks(template 自带 .githooks/pre-commit,core.hooksPath 一次性设置)
|
|
82
|
+
// 2. activate git hooks(template 自带 .githooks/pre-commit,core.hooksPath 一次性设置)
|
|
85
83
|
const hookActivation = (0, githooks_1.activateGitHooks)(targetDir);
|
|
84
|
+
// 3. snapshot 当前 user app 装的管控包版本,供 install 后 diff 用
|
|
85
|
+
const beforeSpecs = snapshotManagedDepSpecs(targetDir);
|
|
86
86
|
// 4. npm install —— 跟 init 一样软失败,install 挂了不该阻断 emit / sync 总结输出。
|
|
87
|
-
// 把 user app
|
|
88
|
-
//
|
|
89
|
-
// - 即便 package.json spec 已经是 "latest"(没有 from→to 变化),user app 老 lockfile
|
|
90
|
-
// 可能锁着具体老版本 (如 fullstack-vite-preset@1.0.9),纯 `npm install` 会按 lockfile
|
|
91
|
-
// 拉老版,绕过 spec "latest" 的解析意图
|
|
92
|
-
// - 显式 `npm install foo@latest bar@latest` 让 npm 重新解析,lockfile 跟着更新到当前 latest
|
|
87
|
+
// 把 user app 装着的**所有**管控包显式作为 `<pkg>@latest` 位置参数传给 npm install:
|
|
88
|
+
// - npm 自然会把 spec 改写成 ^具体最新版,lockfile 同步更新 → 不需要 sync 主动改 spec
|
|
93
89
|
// - 没装的管控包不动 (sync 的职责是对齐,不是塞依赖)
|
|
94
90
|
// --ignore-scripts 绕开 action-plugin postinstall 在缺平台 env 时的 ENOENT。
|
|
95
91
|
// --registry 跟 init 钉同一份 npmmirror,避免 user 全局 ~/.npmrc 把 sync 拉到另一个源
|
|
@@ -102,7 +98,7 @@ async function handleAppSync(opts) {
|
|
|
102
98
|
'--registry',
|
|
103
99
|
(0, install_1.resolveNpmInstallRegistry)(),
|
|
104
100
|
];
|
|
105
|
-
for (const name of
|
|
101
|
+
for (const name of Object.keys(beforeSpecs)) {
|
|
106
102
|
installArgs.push(`${name}@${MANAGED_PLATFORM_PACKAGES[name]}`);
|
|
107
103
|
}
|
|
108
104
|
(0, logger_1.log)('sync', `Running npm ${installArgs.join(' ')}...`);
|
|
@@ -117,6 +113,9 @@ async function handleAppSync(opts) {
|
|
|
117
113
|
installError = err instanceof Error ? err.message : String(err);
|
|
118
114
|
(0, logger_1.log)('sync', `⚠ npm install failed (continuing): ${installError}`);
|
|
119
115
|
}
|
|
116
|
+
// 5. install 之后再读一次 package.json,diff 出 npm 实际改写的 spec
|
|
117
|
+
const afterSpecs = snapshotManagedDepSpecs(targetDir);
|
|
118
|
+
const upgradedPackages = diffManagedSpecs(beforeSpecs, afterSpecs, 'sync');
|
|
120
119
|
(0, output_1.emit)({
|
|
121
120
|
data: {
|
|
122
121
|
stack: meta.stack,
|
|
@@ -203,64 +202,39 @@ function readPackageJson(pkgJsonPath) {
|
|
|
203
202
|
return null;
|
|
204
203
|
return JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
|
|
205
204
|
}
|
|
206
|
-
|
|
207
|
-
* 列出 user app 装着的 @lark-apaas/* 管控包 (在 MANAGED_PLATFORM_PACKAGES 白名单内的)。
|
|
208
|
-
* sync handler 用来给 `npm install` 显式列管控包名 + @latest,强制 npm 重新 resolve
|
|
209
|
-
* lockfile entry —— 即便 package.json spec 已经是 "latest" 没 from→to 变化,
|
|
210
|
-
* 老 lockfile 锁的具体老版本(如 fullstack-vite-preset@1.0.9)也会被刷掉。
|
|
211
|
-
*/
|
|
212
|
-
function listManagedDeps(targetDir) {
|
|
205
|
+
function snapshotManagedDepSpecs(targetDir) {
|
|
213
206
|
const pkg = readPackageJson(node_path_1.default.join(targetDir, 'package.json'));
|
|
214
207
|
if (!pkg)
|
|
215
|
-
return
|
|
216
|
-
const
|
|
208
|
+
return {};
|
|
209
|
+
const snap = {};
|
|
217
210
|
for (const section of ['dependencies', 'devDependencies']) {
|
|
218
211
|
const deps = pkg[section];
|
|
219
212
|
if (!deps || typeof deps !== 'object')
|
|
220
213
|
continue;
|
|
221
|
-
for (const name of Object.
|
|
222
|
-
if (Object.prototype.hasOwnProperty.call(MANAGED_PLATFORM_PACKAGES, name))
|
|
223
|
-
|
|
224
|
-
|
|
214
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
215
|
+
if (!Object.prototype.hasOwnProperty.call(MANAGED_PLATFORM_PACKAGES, name))
|
|
216
|
+
continue;
|
|
217
|
+
if (Object.prototype.hasOwnProperty.call(snap, name))
|
|
218
|
+
continue; // dependencies 优先
|
|
219
|
+
snap[name] = { spec, section };
|
|
225
220
|
}
|
|
226
221
|
}
|
|
227
|
-
return
|
|
222
|
+
return snap;
|
|
228
223
|
}
|
|
229
224
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* 渲染后、install 之前)共用。
|
|
233
|
-
*
|
|
234
|
-
* - user app 已装的管控包 → 写表里的目标 spec(默认 latest,紧急止血时可改具体版本)
|
|
235
|
-
* - user app 未装的管控包 → 不引入(sync 的职责是对齐,不是塞依赖)
|
|
236
|
-
* - 装着但不在白名单的 @lark-apaas/* 包 → 完全不动
|
|
237
|
-
*
|
|
238
|
-
* @param logPrefix 用于 `[<prefix>] deps.xxx: a → b` 日志前缀;sync 流程传 'sync',init 流程传 'init'。
|
|
225
|
+
* 对比 install 前后的管控包 spec snapshot,得到 `npm install` 实际改写了哪些 spec。
|
|
226
|
+
* sync 和 init 共用:把"主动改 package.json"完全交给 npm,本函数只做事后观测。
|
|
239
227
|
*/
|
|
240
|
-
function
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
return [];
|
|
245
|
-
const updated = [];
|
|
246
|
-
for (const section of ['dependencies', 'devDependencies']) {
|
|
247
|
-
const deps = pkg[section];
|
|
248
|
-
if (!deps || typeof deps !== 'object')
|
|
228
|
+
function diffManagedSpecs(before, after, logPrefix) {
|
|
229
|
+
const bumps = [];
|
|
230
|
+
for (const [name, b] of Object.entries(before)) {
|
|
231
|
+
if (!Object.prototype.hasOwnProperty.call(after, name))
|
|
249
232
|
continue;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (from === target)
|
|
256
|
-
continue;
|
|
257
|
-
deps[name] = target;
|
|
258
|
-
updated.push({ name, from, to: target, section });
|
|
259
|
-
(0, logger_1.log)(logPrefix, ` ${section}.${name}: ${from} → ${target}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (updated.length > 0) {
|
|
263
|
-
node_fs_1.default.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
233
|
+
const a = after[name];
|
|
234
|
+
if (a.spec === b.spec)
|
|
235
|
+
continue;
|
|
236
|
+
bumps.push({ name, from: b.spec, to: a.spec, section: b.section });
|
|
237
|
+
(0, logger_1.log)(logPrefix, ` ${b.section}.${name}: ${b.spec} → ${a.spec}`);
|
|
264
238
|
}
|
|
265
|
-
return
|
|
239
|
+
return bumps;
|
|
266
240
|
}
|
|
@@ -45,24 +45,24 @@ function installDependencies(opts) {
|
|
|
45
45
|
return { installed: true, source: 'cache', hash, cacheZip };
|
|
46
46
|
}
|
|
47
47
|
(0, logger_1.log)('init', 'Cache zip extracted but node_modules/ missing or empty; falling back to npm install');
|
|
48
|
-
return runInstallSoft(opts.targetDir, childStdio, hash);
|
|
48
|
+
return runInstallSoft(opts.targetDir, childStdio, opts.extraPackages, hash);
|
|
49
49
|
}
|
|
50
50
|
(0, logger_1.log)('init', `Cache miss for ${hash}.zip, falling back to npm install`);
|
|
51
|
-
return runInstallSoft(opts.targetDir, childStdio, hash);
|
|
51
|
+
return runInstallSoft(opts.targetDir, childStdio, opts.extraPackages, hash);
|
|
52
52
|
}
|
|
53
53
|
if (cacheDir && !hasPkg) {
|
|
54
54
|
(0, logger_1.log)('init', `${DEP_CACHE_ENV} set but no package.json in target; running npm install`);
|
|
55
55
|
}
|
|
56
|
-
return runInstallSoft(opts.targetDir, childStdio);
|
|
56
|
+
return runInstallSoft(opts.targetDir, childStdio, opts.extraPackages);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* 跑 npm install;失败不抛错,返回 source='failed' 让上层 init handler 继续走 writeSparkMeta
|
|
60
60
|
* 等后续步骤(避免 install 挂了 .spark/meta.json 没写出来,下次 init 看到半渲染状态又得重跑全套)。
|
|
61
61
|
* 失败信息通过 stderr 直出 + emit data.installError 透传给用户。
|
|
62
62
|
*/
|
|
63
|
-
function runInstallSoft(targetDir, stdio, hash) {
|
|
63
|
+
function runInstallSoft(targetDir, stdio, extraPackages, hash) {
|
|
64
64
|
try {
|
|
65
|
-
runNpmInstall(targetDir, stdio);
|
|
65
|
+
runNpmInstall(targetDir, stdio, extraPackages);
|
|
66
66
|
return { installed: true, source: 'npm', hash };
|
|
67
67
|
}
|
|
68
68
|
catch (err) {
|
|
@@ -123,10 +123,11 @@ const NPM_INSTALL_REGISTRY_DEFAULT = 'https://registry.npmmirror.com/';
|
|
|
123
123
|
function resolveNpmInstallRegistry() {
|
|
124
124
|
return process.env.MIAODA_NPM_REGISTRY ?? NPM_INSTALL_REGISTRY_DEFAULT;
|
|
125
125
|
}
|
|
126
|
-
function runNpmInstall(targetDir, stdio) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
126
|
+
function runNpmInstall(targetDir, stdio, extraPackages) {
|
|
127
|
+
const args = ['install', '--no-audit', '--no-fund', '--registry', resolveNpmInstallRegistry()];
|
|
128
|
+
if (extraPackages !== undefined && extraPackages.length > 0) {
|
|
129
|
+
args.push(...extraPackages);
|
|
130
|
+
}
|
|
131
|
+
(0, logger_1.log)('init', `npm ${args.join(' ')} in ${targetDir}...`);
|
|
132
|
+
(0, node_child_process_1.execFileSync)('npm', args, { cwd: targetDir, stdio });
|
|
132
133
|
}
|