@lark-apaas/openclaw-scripts-diagnose-cli 0.1.1-alpha.25 → 0.1.1-alpha.27
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/index.cjs +225 -86
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -365,6 +365,74 @@ ConfigSyntaxRule = __decorate([Rule({
|
|
|
365
365
|
repairMode: "ai"
|
|
366
366
|
})], ConfigSyntaxRule);
|
|
367
367
|
//#endregion
|
|
368
|
+
//#region src/rules/template-vars-unreplaced.ts
|
|
369
|
+
/**
|
|
370
|
+
* Placeholder format used by miaoda-openclaw-template and Go-side templateVars,
|
|
371
|
+
* e.g. `$$__FEISHU_APP_ID__`. Double underscores on both sides act as a natural
|
|
372
|
+
* boundary so split-join replacement can't accidentally overlap between keys.
|
|
373
|
+
*/
|
|
374
|
+
const PLACEHOLDER_RE = /\$\$__[A-Z0-9_]+__/g;
|
|
375
|
+
let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends DiagnoseRule {
|
|
376
|
+
validate(ctx) {
|
|
377
|
+
const found = /* @__PURE__ */ new Set();
|
|
378
|
+
collectPlaceholders(ctx.config, found);
|
|
379
|
+
if (found.size === 0) return { pass: true };
|
|
380
|
+
return {
|
|
381
|
+
pass: false,
|
|
382
|
+
message: "存在未替换的模板占位符: " + [...found].sort().join(", ")
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
repair(ctx) {
|
|
386
|
+
const map = ctx.templateVars;
|
|
387
|
+
if (!map || Object.keys(map).length === 0) return;
|
|
388
|
+
replaceInPlace(ctx.config, Object.entries(map));
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
TemplateVarsUnreplacedRule = __decorate([Rule({
|
|
392
|
+
key: "template_vars_unreplaced",
|
|
393
|
+
dependsOn: ["config_syntax_check"],
|
|
394
|
+
repairMode: "standard"
|
|
395
|
+
})], TemplateVarsUnreplacedRule);
|
|
396
|
+
function collectPlaceholders(value, found) {
|
|
397
|
+
if (typeof value === "string") {
|
|
398
|
+
const matches = value.match(PLACEHOLDER_RE);
|
|
399
|
+
if (matches) for (const m of matches) found.add(m);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (Array.isArray(value)) {
|
|
403
|
+
for (const v of value) collectPlaceholders(v, found);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (value && typeof value === "object") for (const v of Object.values(value)) collectPlaceholders(v, found);
|
|
407
|
+
}
|
|
408
|
+
function replaceInPlace(value, entries) {
|
|
409
|
+
if (Array.isArray(value)) {
|
|
410
|
+
for (let i = 0; i < value.length; i++) {
|
|
411
|
+
const el = value[i];
|
|
412
|
+
if (typeof el === "string") value[i] = applyVars(el, entries);
|
|
413
|
+
else replaceInPlace(el, entries);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (value && typeof value === "object") {
|
|
418
|
+
const obj = value;
|
|
419
|
+
for (const key of Object.keys(obj)) {
|
|
420
|
+
const v = obj[key];
|
|
421
|
+
if (typeof v === "string") obj[key] = applyVars(v, entries);
|
|
422
|
+
else replaceInPlace(v, entries);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Split-join replacement — matches the algorithm in reset.ts:120 and avoids regex-escaping `$$`. */
|
|
427
|
+
function applyVars(str, entries) {
|
|
428
|
+
let out = str;
|
|
429
|
+
for (const [placeholder, value] of entries) {
|
|
430
|
+
if (!value) continue;
|
|
431
|
+
if (out.includes(placeholder)) out = out.split(placeholder).join(value);
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
368
436
|
//#region src/rules/model-provider.ts
|
|
369
437
|
var _ModelProviderRule;
|
|
370
438
|
let ModelProviderRule = class ModelProviderRule extends DiagnoseRule {
|
|
@@ -614,6 +682,27 @@ FeishuChannelRule = _FeishuChannelRule = __decorate([Rule({
|
|
|
614
682
|
repairMode: "standard"
|
|
615
683
|
})], FeishuChannelRule);
|
|
616
684
|
//#endregion
|
|
685
|
+
//#region src/rules/feishu-default-account.ts
|
|
686
|
+
let FeishuDefaultAccountRule = class FeishuDefaultAccountRule extends DiagnoseRule {
|
|
687
|
+
validate(ctx) {
|
|
688
|
+
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
689
|
+
if (feishu && "defaultAccount" in feishu) return {
|
|
690
|
+
pass: false,
|
|
691
|
+
message: "channels.feishu.defaultAccount should be removed"
|
|
692
|
+
};
|
|
693
|
+
return { pass: true };
|
|
694
|
+
}
|
|
695
|
+
repair(ctx) {
|
|
696
|
+
const feishu = getNestedMap(ctx.config, "channels", "feishu");
|
|
697
|
+
if (feishu && "defaultAccount" in feishu) delete feishu.defaultAccount;
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
FeishuDefaultAccountRule = __decorate([Rule({
|
|
701
|
+
key: "feishu_default_account",
|
|
702
|
+
dependsOn: ["config_syntax_check"],
|
|
703
|
+
repairMode: "standard"
|
|
704
|
+
})], FeishuDefaultAccountRule);
|
|
705
|
+
//#endregion
|
|
617
706
|
//#region src/rules/gateway.ts
|
|
618
707
|
var _GatewayRule;
|
|
619
708
|
let GatewayRule = class GatewayRule extends DiagnoseRule {
|
|
@@ -877,74 +966,6 @@ SecretsRule = __decorate([Rule({
|
|
|
877
966
|
skipWhen: ({ hasMiaoda, deps }) => !hasMiaoda || !deps.usesMiaodaSecretProvider
|
|
878
967
|
})], SecretsRule);
|
|
879
968
|
//#endregion
|
|
880
|
-
//#region src/rules/template-vars-unreplaced.ts
|
|
881
|
-
/**
|
|
882
|
-
* Placeholder format used by miaoda-openclaw-template and Go-side templateVars,
|
|
883
|
-
* e.g. `$$__FEISHU_APP_ID__`. Double underscores on both sides act as a natural
|
|
884
|
-
* boundary so split-join replacement can't accidentally overlap between keys.
|
|
885
|
-
*/
|
|
886
|
-
const PLACEHOLDER_RE = /\$\$__[A-Z0-9_]+__/g;
|
|
887
|
-
let TemplateVarsUnreplacedRule = class TemplateVarsUnreplacedRule extends DiagnoseRule {
|
|
888
|
-
validate(ctx) {
|
|
889
|
-
const found = /* @__PURE__ */ new Set();
|
|
890
|
-
collectPlaceholders(ctx.config, found);
|
|
891
|
-
if (found.size === 0) return { pass: true };
|
|
892
|
-
return {
|
|
893
|
-
pass: false,
|
|
894
|
-
message: "存在未替换的模板占位符: " + [...found].sort().join(", ")
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
repair(ctx) {
|
|
898
|
-
const map = ctx.templateVars;
|
|
899
|
-
if (!map || Object.keys(map).length === 0) return;
|
|
900
|
-
replaceInPlace(ctx.config, Object.entries(map));
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
TemplateVarsUnreplacedRule = __decorate([Rule({
|
|
904
|
-
key: "template_vars_unreplaced",
|
|
905
|
-
dependsOn: ["config_syntax_check"],
|
|
906
|
-
repairMode: "standard"
|
|
907
|
-
})], TemplateVarsUnreplacedRule);
|
|
908
|
-
function collectPlaceholders(value, found) {
|
|
909
|
-
if (typeof value === "string") {
|
|
910
|
-
const matches = value.match(PLACEHOLDER_RE);
|
|
911
|
-
if (matches) for (const m of matches) found.add(m);
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
if (Array.isArray(value)) {
|
|
915
|
-
for (const v of value) collectPlaceholders(v, found);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
if (value && typeof value === "object") for (const v of Object.values(value)) collectPlaceholders(v, found);
|
|
919
|
-
}
|
|
920
|
-
function replaceInPlace(value, entries) {
|
|
921
|
-
if (Array.isArray(value)) {
|
|
922
|
-
for (let i = 0; i < value.length; i++) {
|
|
923
|
-
const el = value[i];
|
|
924
|
-
if (typeof el === "string") value[i] = applyVars(el, entries);
|
|
925
|
-
else replaceInPlace(el, entries);
|
|
926
|
-
}
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
if (value && typeof value === "object") {
|
|
930
|
-
const obj = value;
|
|
931
|
-
for (const key of Object.keys(obj)) {
|
|
932
|
-
const v = obj[key];
|
|
933
|
-
if (typeof v === "string") obj[key] = applyVars(v, entries);
|
|
934
|
-
else replaceInPlace(v, entries);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
/** Split-join replacement — matches the algorithm in reset.ts:120 and avoids regex-escaping `$$`. */
|
|
939
|
-
function applyVars(str, entries) {
|
|
940
|
-
let out = str;
|
|
941
|
-
for (const [placeholder, value] of entries) {
|
|
942
|
-
if (!value) continue;
|
|
943
|
-
if (out.includes(placeholder)) out = out.split(placeholder).join(value);
|
|
944
|
-
}
|
|
945
|
-
return out;
|
|
946
|
-
}
|
|
947
|
-
//#endregion
|
|
948
969
|
//#region src/rules/cleanup-install-backup-dirs.ts
|
|
949
970
|
const DIR_PREFIX = ".openclaw-install-";
|
|
950
971
|
function resolveExtensionsDir(configPath) {
|
|
@@ -962,7 +983,7 @@ function findLeftoverDirs(extensionsDir) {
|
|
|
962
983
|
}
|
|
963
984
|
let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends DiagnoseRule {
|
|
964
985
|
validate(ctx) {
|
|
965
|
-
const configPath = ctx
|
|
986
|
+
const { configPath } = ctx;
|
|
966
987
|
if (!configPath) return { pass: true };
|
|
967
988
|
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
968
989
|
if (dirs.length === 0) return { pass: true };
|
|
@@ -972,7 +993,7 @@ let CleanupInstallBackupDirsRule = class CleanupInstallBackupDirsRule extends Di
|
|
|
972
993
|
};
|
|
973
994
|
}
|
|
974
995
|
repair(ctx) {
|
|
975
|
-
const configPath = ctx
|
|
996
|
+
const { configPath } = ctx;
|
|
976
997
|
if (!configPath) return;
|
|
977
998
|
const dirs = findLeftoverDirs(resolveExtensionsDir(configPath));
|
|
978
999
|
const failures = [];
|
|
@@ -1322,6 +1343,54 @@ const SECRETS_FILE_PATH = "/home/gem/workspace/.force/openclaw/miaoda-openclaw-s
|
|
|
1322
1343
|
const CONFIG_PATH = `${WORKSPACE_DIR}/openclaw.json`;
|
|
1323
1344
|
//#endregion
|
|
1324
1345
|
//#region src/logger.ts
|
|
1346
|
+
/**
|
|
1347
|
+
* Shared CLI log file. Every log line the CLI emits — whether through
|
|
1348
|
+
* `console.error` (rules, helpers, errors) or through the per-task
|
|
1349
|
+
* `makeLogger` (reset worker) — is tee'd here so operators have a single
|
|
1350
|
+
* file to tail when diagnosing a sandbox.
|
|
1351
|
+
*
|
|
1352
|
+
* `/tmp` is ephemeral on sandbox restart; we rely on that for rotation
|
|
1353
|
+
* (no size-based rotation implemented).
|
|
1354
|
+
*/
|
|
1355
|
+
const CLI_LOG_FILE = "/tmp/openclaw-diagnose/cli.log";
|
|
1356
|
+
/** Append one line to the shared cli.log. Swallows any fs error —
|
|
1357
|
+
* logging must never break the business flow. */
|
|
1358
|
+
function appendCliLog(line) {
|
|
1359
|
+
try {
|
|
1360
|
+
const dir = node_path.default.dirname(CLI_LOG_FILE);
|
|
1361
|
+
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1362
|
+
node_fs.default.appendFileSync(CLI_LOG_FILE, line);
|
|
1363
|
+
} catch {}
|
|
1364
|
+
}
|
|
1365
|
+
let stderrMirrorInstalled = false;
|
|
1366
|
+
/**
|
|
1367
|
+
* Install a process-wide `console.error` interceptor that mirrors each
|
|
1368
|
+
* line to BOTH the original stderr AND cli.log. Call once at CLI entry
|
|
1369
|
+
* before any subcommand dispatch; idempotent.
|
|
1370
|
+
*
|
|
1371
|
+
* Why console.error and not console.log: the CLI's stdout carries the
|
|
1372
|
+
* structured JSON result protocol consumed by sandbox_console and other
|
|
1373
|
+
* callers — any log line on stdout would corrupt JSON parsing. Rules,
|
|
1374
|
+
* helpers, and error paths therefore must route debug output through
|
|
1375
|
+
* console.error (stderr).
|
|
1376
|
+
*/
|
|
1377
|
+
function installStderrMirror() {
|
|
1378
|
+
if (stderrMirrorInstalled) return;
|
|
1379
|
+
stderrMirrorInstalled = true;
|
|
1380
|
+
const original = console.error.bind(console);
|
|
1381
|
+
console.error = (...args) => {
|
|
1382
|
+
original(...args);
|
|
1383
|
+
const body = args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ");
|
|
1384
|
+
appendCliLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${body}\n`);
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function safeStringify(v) {
|
|
1388
|
+
try {
|
|
1389
|
+
return JSON.stringify(v);
|
|
1390
|
+
} catch {
|
|
1391
|
+
return String(v);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1325
1394
|
function makeLogger(logFile) {
|
|
1326
1395
|
try {
|
|
1327
1396
|
const dir = node_path.default.dirname(logFile);
|
|
@@ -1332,9 +1401,70 @@ function makeLogger(logFile) {
|
|
|
1332
1401
|
try {
|
|
1333
1402
|
node_fs.default.appendFileSync(logFile, line);
|
|
1334
1403
|
} catch {}
|
|
1404
|
+
appendCliLog(line);
|
|
1335
1405
|
};
|
|
1336
1406
|
}
|
|
1337
1407
|
//#endregion
|
|
1408
|
+
//#region src/fs-utils.ts
|
|
1409
|
+
/**
|
|
1410
|
+
* Rename src → dst, falling back to `mv` (which handles cross-device copy)
|
|
1411
|
+
* when the kernel returns EXDEV.
|
|
1412
|
+
*
|
|
1413
|
+
* Sandbox filesystems can put sibling paths on different "devices" from
|
|
1414
|
+
* rename(2)'s point of view: bind mounts, overlayfs copy-up, and
|
|
1415
|
+
* mount-point children inside a single directory all trip EXDEV. Seen in
|
|
1416
|
+
* production when reset's atomic swap did
|
|
1417
|
+
* /home/gem/.npm-global/lib/node_modules/openclaw → openclaw.bak
|
|
1418
|
+
* and the openclaw subdir was a bind-mounted volume.
|
|
1419
|
+
*
|
|
1420
|
+
* Behavior:
|
|
1421
|
+
* - Happy path hits rename(2) — atomic, single syscall, microseconds.
|
|
1422
|
+
* - EXDEV path shells out to `mv`, which does rename() then copy+unlink
|
|
1423
|
+
* on failure. Non-atomic but correct; callers already have rollback
|
|
1424
|
+
* logic (install-openclaw restores from .bak) so loss of atomicity
|
|
1425
|
+
* only matters if the process dies mid-copy, and that's survivable.
|
|
1426
|
+
* - Any other error (ENOENT, EACCES, EBUSY...) rethrows as-is so callers
|
|
1427
|
+
* see the real problem instead of a misleading `mv` fallback failure.
|
|
1428
|
+
*/
|
|
1429
|
+
function moveSafe(src, dst) {
|
|
1430
|
+
try {
|
|
1431
|
+
node_fs.default.renameSync(src, dst);
|
|
1432
|
+
} catch (e) {
|
|
1433
|
+
if (e?.code !== "EXDEV") throw e;
|
|
1434
|
+
execCaptureErr(`mv ${shellQuote(src)} ${shellQuote(dst)}`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Run a shell command, re-throwing with stderr attached on failure.
|
|
1439
|
+
*
|
|
1440
|
+
* Node's `execSync(..., { stdio: 'ignore' })` swallows stderr entirely —
|
|
1441
|
+
* callers only see "Command failed: <cmd>" with no hint of the real error
|
|
1442
|
+
* (ENOSPC, EROFS, "unrecognized option", etc.). Production debugging on
|
|
1443
|
+
* sandboxed boxes is painful without the underlying message, so we pipe
|
|
1444
|
+
* stderr, capture it, and embed it in the thrown Error. stdout stays
|
|
1445
|
+
* suppressed because the commands we run here (tar/mv) are silent on
|
|
1446
|
+
* success.
|
|
1447
|
+
*/
|
|
1448
|
+
function execCaptureErr(cmd) {
|
|
1449
|
+
try {
|
|
1450
|
+
(0, node_child_process.execSync)(cmd, { stdio: [
|
|
1451
|
+
"ignore",
|
|
1452
|
+
"ignore",
|
|
1453
|
+
"pipe"
|
|
1454
|
+
] });
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
const stderr = e?.stderr;
|
|
1457
|
+
const stderrStr = (typeof stderr === "string" ? stderr : stderr?.toString("utf8") ?? "").trim();
|
|
1458
|
+
const base = e?.message ?? "command failed";
|
|
1459
|
+
throw new Error(stderrStr ? `${base}\nstderr: ${stderrStr}` : base);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/** POSIX single-quote shell escape. Paths with embedded quotes are rare but
|
|
1463
|
+
* the token-file path conventions in sandboxes don't guarantee cleanliness. */
|
|
1464
|
+
function shellQuote(s) {
|
|
1465
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
1466
|
+
}
|
|
1467
|
+
//#endregion
|
|
1338
1468
|
//#region src/reset-async.ts
|
|
1339
1469
|
/**
|
|
1340
1470
|
* Start an async reset task: spawn a detached child process and return the taskId.
|
|
@@ -1357,7 +1487,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
1357
1487
|
const dir = node_path.default.dirname(resultFile);
|
|
1358
1488
|
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1359
1489
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(initial), "utf-8");
|
|
1360
|
-
|
|
1490
|
+
moveSafe(tmpPath, resultFile);
|
|
1361
1491
|
const child = (0, node_child_process.spawn)(process.execPath, [
|
|
1362
1492
|
process.argv[1],
|
|
1363
1493
|
"reset",
|
|
@@ -1381,7 +1511,7 @@ function startAsyncReset(ctxBase64) {
|
|
|
1381
1511
|
};
|
|
1382
1512
|
const errTmpPath = resultFile + ".tmp";
|
|
1383
1513
|
node_fs.default.writeFileSync(errTmpPath, JSON.stringify(failResult));
|
|
1384
|
-
|
|
1514
|
+
moveSafe(errTmpPath, resultFile);
|
|
1385
1515
|
});
|
|
1386
1516
|
child.unref();
|
|
1387
1517
|
log(`spawned worker pid=${child.pid}`);
|
|
@@ -1389,10 +1519,16 @@ function startAsyncReset(ctxBase64) {
|
|
|
1389
1519
|
}
|
|
1390
1520
|
//#endregion
|
|
1391
1521
|
//#region src/oss/fetchManifest.ts
|
|
1522
|
+
const MANIFEST_PREFIX = "builtin/manifests/openclaw/recommended/";
|
|
1523
|
+
const MANIFEST_SUFFIX = ".json";
|
|
1392
1524
|
async function fetchManifest(ossFileMap, tag) {
|
|
1393
|
-
const key =
|
|
1525
|
+
const key = `${MANIFEST_PREFIX}${tag}${MANIFEST_SUFFIX}`;
|
|
1394
1526
|
const url = ossFileMap[key];
|
|
1395
|
-
if (!url)
|
|
1527
|
+
if (!url) {
|
|
1528
|
+
const available = Object.keys(ossFileMap).filter((k) => k.startsWith(MANIFEST_PREFIX) && k.endsWith(MANIFEST_SUFFIX)).map((k) => k.slice(39, -5));
|
|
1529
|
+
const availStr = available.length ? available.join(", ") : "(none)";
|
|
1530
|
+
throw new Error(`manifest signed URL missing for tag "${tag}" (key ${key}). Available tags in ossFileMap: ${availStr}. Either pass an available tag or update the studio_server TCC openclaw_upgrade_config supported_versions.`);
|
|
1531
|
+
}
|
|
1396
1532
|
const res = await fetch(url);
|
|
1397
1533
|
if (!res.ok) throw new Error(`fetch manifest failed: HTTP ${res.status} ${res.statusText}`);
|
|
1398
1534
|
return await res.json();
|
|
@@ -1430,7 +1566,7 @@ async function downloadWithCache(pkg, ossFileMap, opts = {}) {
|
|
|
1430
1566
|
console.error(`⚠ [downloadWithCache] INTEGRITY BYPASS for ${pkg.ossKey}: expected ${expected.slice(0, 12)}… got ${actual.slice(0, 12)}… — ${sourceLabel}. DO NOT use this flag in production.`);
|
|
1431
1567
|
} else throw new Error(`integrity mismatch for ${pkg.ossKey}: expected ${expected} got ${actual}`);
|
|
1432
1568
|
}
|
|
1433
|
-
|
|
1569
|
+
moveSafe(tmpFile, destFile);
|
|
1434
1570
|
return destFile;
|
|
1435
1571
|
} catch (e) {
|
|
1436
1572
|
try {
|
|
@@ -1453,10 +1589,10 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
1453
1589
|
force: true
|
|
1454
1590
|
});
|
|
1455
1591
|
const hadExisting = node_fs.default.existsSync(targetDir);
|
|
1456
|
-
if (hadExisting)
|
|
1592
|
+
if (hadExisting) moveSafe(targetDir, bakDir);
|
|
1457
1593
|
try {
|
|
1458
1594
|
node_fs.default.mkdirSync(targetDir, { recursive: true });
|
|
1459
|
-
(
|
|
1595
|
+
execCaptureErr(`tar -xzf '${tarball}' -C '${targetDir}' --strip-components=1`);
|
|
1460
1596
|
if (!node_fs.default.existsSync(node_path.default.join(targetDir, "package.json"))) throw new Error("extracted tarball missing package.json");
|
|
1461
1597
|
} catch (e) {
|
|
1462
1598
|
try {
|
|
@@ -1465,7 +1601,7 @@ async function installOpenclaw(openclawTag, ossFileMap, opts = {}) {
|
|
|
1465
1601
|
force: true
|
|
1466
1602
|
});
|
|
1467
1603
|
} catch {}
|
|
1468
|
-
if (hadExisting && node_fs.default.existsSync(bakDir))
|
|
1604
|
+
if (hadExisting && node_fs.default.existsSync(bakDir)) moveSafe(bakDir, targetDir);
|
|
1469
1605
|
throw e;
|
|
1470
1606
|
}
|
|
1471
1607
|
if (node_fs.default.existsSync(bakDir)) node_fs.default.rmSync(bakDir, {
|
|
@@ -1540,7 +1676,7 @@ function updatePluginInstalls(configPath, installedPkgs) {
|
|
|
1540
1676
|
} else skipped++;
|
|
1541
1677
|
const tmpPath = configPath + ".installs-tmp";
|
|
1542
1678
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
1543
|
-
|
|
1679
|
+
moveSafe(tmpPath, configPath);
|
|
1544
1680
|
console.error(`[install-extension] plugins.installs updated: ${updated} entry(ies) in ${configPath}` + (skipped > 0 ? ` (${skipped} package(s) without installMetadata skipped)` : ""));
|
|
1545
1681
|
}
|
|
1546
1682
|
function installOne(pkg, tarball, homeBase) {
|
|
@@ -1554,7 +1690,7 @@ function installOne(pkg, tarball, homeBase) {
|
|
|
1554
1690
|
});
|
|
1555
1691
|
node_fs.default.mkdirSync(stagingDir);
|
|
1556
1692
|
try {
|
|
1557
|
-
(
|
|
1693
|
+
execCaptureErr(`tar -xzf '${tarball}' -C '${stagingDir}' --strip-components=1`);
|
|
1558
1694
|
if (!node_fs.default.existsSync(node_path.default.join(stagingDir, "package.json"))) throw new Error(`extension tarball missing package.json: ${pkg.name}`);
|
|
1559
1695
|
} catch (e) {
|
|
1560
1696
|
try {
|
|
@@ -1566,8 +1702,8 @@ function installOne(pkg, tarball, homeBase) {
|
|
|
1566
1702
|
throw e;
|
|
1567
1703
|
}
|
|
1568
1704
|
const hadOld = node_fs.default.existsSync(destDir);
|
|
1569
|
-
if (hadOld)
|
|
1570
|
-
|
|
1705
|
+
if (hadOld) moveSafe(destDir, oldDir);
|
|
1706
|
+
moveSafe(stagingDir, destDir);
|
|
1571
1707
|
if (hadOld && node_fs.default.existsSync(oldDir)) node_fs.default.rmSync(oldDir, {
|
|
1572
1708
|
recursive: true,
|
|
1573
1709
|
force: true
|
|
@@ -1592,7 +1728,7 @@ async function downloadResource(tag, ossFileMap, opts) {
|
|
|
1592
1728
|
const format = (pkg.format ?? "").toLowerCase();
|
|
1593
1729
|
const lower = pkg.ossKey.toLowerCase();
|
|
1594
1730
|
if (format === "tgz" || lower.endsWith(".tgz") || lower.endsWith(".tar.gz")) {
|
|
1595
|
-
(
|
|
1731
|
+
execCaptureErr(`tar -xzf '${file}' -C '${extractDir}'`);
|
|
1596
1732
|
console.error(`[download-resource] ${opts.role}/${opts.name}: extracted to ${extractDir}`);
|
|
1597
1733
|
} else {
|
|
1598
1734
|
const basename = node_path.default.posix.basename(pkg.ossKey);
|
|
@@ -1634,7 +1770,7 @@ function writeResultFile(resultFile, result) {
|
|
|
1634
1770
|
if (!node_fs.default.existsSync(dir)) node_fs.default.mkdirSync(dir, { recursive: true });
|
|
1635
1771
|
const tmpPath = resultFile + ".tmp";
|
|
1636
1772
|
node_fs.default.writeFileSync(tmpPath, JSON.stringify(result), "utf-8");
|
|
1637
|
-
|
|
1773
|
+
moveSafe(tmpPath, resultFile);
|
|
1638
1774
|
}
|
|
1639
1775
|
function updateProgress(resultFile, step, startedAt) {
|
|
1640
1776
|
writeResultFile(resultFile, {
|
|
@@ -1946,7 +2082,8 @@ async function runReset(input, taskId, resultFile) {
|
|
|
1946
2082
|
process.exit(1);
|
|
1947
2083
|
}
|
|
1948
2084
|
let openclawTag;
|
|
1949
|
-
|
|
2085
|
+
if (resetData.openclawTag) openclawTag = resetData.openclawTag;
|
|
2086
|
+
else try {
|
|
1950
2087
|
openclawTag = getOpenclawTagFromOssFileMap(ossFileMap);
|
|
1951
2088
|
} catch (e) {
|
|
1952
2089
|
const err = e.message;
|
|
@@ -2340,7 +2477,8 @@ function buildResetInput(raw, configPathOverride) {
|
|
|
2340
2477
|
secretsContent: ctx.secrets.secretsContent,
|
|
2341
2478
|
providerKeyContent: ctx.secrets.providerKeyContent,
|
|
2342
2479
|
coreBackup: ctx.reset.coreBackup,
|
|
2343
|
-
ossFileMap: ctx.install.ossFileMap
|
|
2480
|
+
ossFileMap: ctx.install.ossFileMap,
|
|
2481
|
+
openclawTag: ctx.install.openclawTag
|
|
2344
2482
|
}
|
|
2345
2483
|
};
|
|
2346
2484
|
}
|
|
@@ -2400,6 +2538,7 @@ function getMultiFlag(args, name) {
|
|
|
2400
2538
|
return args.filter((a) => a.startsWith(prefix)).map((a) => a.slice(prefix.length));
|
|
2401
2539
|
}
|
|
2402
2540
|
async function main() {
|
|
2541
|
+
installStderrMirror();
|
|
2403
2542
|
switch (mode) {
|
|
2404
2543
|
case "check":
|
|
2405
2544
|
case "repair": {
|
package/package.json
CHANGED