fix: v2.3.1 — correct version, say why a plugin update failed, gitignored files in Files
- v2.3.0's tree reported 2.2.1, so every install saw an update forever; package.json, the lockfile, openapi.yaml, the CLAUDE.md marker and the snapshot now read 2.3.1 - A failed plugin-update step records the tail of the command's stdout and stderr plus its exit code or signal (failureText); plugin-bootstrap.js prints its refusal on stdout, which the verdict used to drop. Update all uses the same text - updates.test.js never applies an update: it skips when the checkout is behind a release (apply ran npm run setup and pruned dev dependencies) - Files tree lists gitignored files and folders, named in orange (addIgnored in git-diff.js); change-tree rows indent in whole steps
This commit is contained in:
@@ -26,6 +26,8 @@ const fs = require("fs");
|
||||
const { rotateIfLarge } = require("./log-rotate");
|
||||
const path = require("path");
|
||||
const { execFileSync, spawnSync } = require("child_process");
|
||||
// What a failed command printed — plugin-bootstrap.js says why on STDOUT.
|
||||
const { failureText } = require("./plugin-update-helper");
|
||||
|
||||
const [, , runtimeDirArg, planJson] = process.argv;
|
||||
|
||||
@@ -229,7 +231,7 @@ function main() {
|
||||
} catch (err) {
|
||||
// A marketplace that can't be refreshed (offline, auth) does not stop the
|
||||
// batch: the plugins may still have updates from the marketplaces that did.
|
||||
finishStep(mktStep, "failed", { error: String(err.stderr || err.message).slice(0, 2000) });
|
||||
finishStep(mktStep, "failed", { error: failureText(err).slice(0, 2000) });
|
||||
}
|
||||
|
||||
const self = plan.selfPlugin;
|
||||
@@ -243,7 +245,7 @@ function main() {
|
||||
run(claude, ["plugin", "update", id, "-y"], 600_000);
|
||||
finishStep(step, "ok");
|
||||
} catch (err) {
|
||||
finishStep(step, "failed", { error: String(err.stderr || err.message).slice(0, 2000) });
|
||||
finishStep(step, "failed", { error: failureText(err).slice(0, 2000) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +255,7 @@ function main() {
|
||||
try {
|
||||
run(claude, ["plugin", "update", selfId, "-y"], 600_000);
|
||||
} catch (err) {
|
||||
finishStep(step, "failed", { error: String(err.stderr || err.message).slice(0, 2000) });
|
||||
finishStep(step, "failed", { error: failureText(err).slice(0, 2000) });
|
||||
reloadPanes();
|
||||
state.status = "failed";
|
||||
state.finished_at = new Date().toISOString();
|
||||
@@ -281,7 +283,7 @@ function main() {
|
||||
} catch (err) {
|
||||
log(`bootstrap failed: ${err.message}`);
|
||||
state.status = "failed";
|
||||
state.bootstrap_error = String(err.stderr || err.message).slice(0, 2000);
|
||||
state.bootstrap_error = failureText(err).slice(0, 2000);
|
||||
state.note = "the old server was stopped; run /agenthub-update to recover";
|
||||
flush();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
* runtime directory (outside the plugin cache, so it survives the swap) and the
|
||||
* new server reports it back as `last_plugin_update`. Without that, a failed
|
||||
* chain would look identical to a slow one: the tab would poll a server that
|
||||
* never comes back and say nothing about why.
|
||||
* never comes back and say nothing about why. A failed step's verdict carries
|
||||
* what the command printed (`failureText`): `plugin-bootstrap.js` says why it
|
||||
* refused on STDOUT (`bootstrap: locked`), and execFileSync's own message is
|
||||
* only "Command failed: <argv>".
|
||||
*
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
@@ -35,8 +38,10 @@ const { execFileSync, spawnSync } = require("child_process");
|
||||
|
||||
const [, , marketplace, plugin, oldRoot, runtimeDirArg] = process.argv;
|
||||
|
||||
const RESULT_FILE = path.join(runtimeDirArg, "plugin-update.json");
|
||||
const LOG_FILE = path.join(runtimeDirArg, "plugin-update.log");
|
||||
// `|| ""`: required by a test (for `failureText`) there is no argv, and only
|
||||
// main() — which a require never runs — writes these.
|
||||
const RESULT_FILE = path.join(runtimeDirArg || "", "plugin-update.json");
|
||||
const LOG_FILE = path.join(runtimeDirArg || "", "plugin-update.log");
|
||||
|
||||
// Names come from the local cache PATH, never from an HTTP body, but they still
|
||||
// reach an argv — so they are re-validated here rather than trusted across the
|
||||
@@ -120,14 +125,44 @@ function run(cmd, args, timeout) {
|
||||
|
||||
function runOnce(cmd, args, timeout) {
|
||||
log(`$ ${cmd} ${args.join(" ")}`);
|
||||
const out = execFileSync(cmd, args, {
|
||||
encoding: "utf8",
|
||||
timeout,
|
||||
maxBuffer: 10_000_000,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
if (out) log(out.trim().slice(0, 4000));
|
||||
return out;
|
||||
try {
|
||||
const out = execFileSync(cmd, args, {
|
||||
encoding: "utf8",
|
||||
timeout,
|
||||
maxBuffer: 10_000_000,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
if (out) log(out.trim().slice(0, 4000));
|
||||
return out;
|
||||
} catch (err) {
|
||||
log(`failed (${howItEnded(err)})`);
|
||||
for (const stream of [err.stdout, err.stderr]) {
|
||||
if (stream) log(String(stream).trim().slice(-4000));
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function howItEnded(err) {
|
||||
if (err.signal) return `killed by ${err.signal}`;
|
||||
return err.status != null ? `exit ${err.status}` : err.code || "did not run";
|
||||
}
|
||||
|
||||
/**
|
||||
* A failed command's verdict text: the last lines it printed on stdout and
|
||||
* stderr, then how it ended. Falls back to execFileSync's message ("Command
|
||||
* failed: …", or ENOENT) only when the command printed nothing.
|
||||
*/
|
||||
function failureText(err) {
|
||||
const tail = (s) =>
|
||||
String(s || "")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.slice(-5)
|
||||
.join("\n");
|
||||
const said = [tail(err.stdout), tail(err.stderr)].filter(Boolean);
|
||||
if (!said.length) return `${String(err.message || err).split("\n")[0]} (${howItEnded(err)})`;
|
||||
return [...said, `(${howItEnded(err)})`].join("\n");
|
||||
}
|
||||
|
||||
function main() {
|
||||
@@ -149,14 +184,14 @@ function main() {
|
||||
try {
|
||||
run(claude, ["plugin", "marketplace", "update", marketplace], 300_000);
|
||||
} catch (err) {
|
||||
record({ ok: false, step: "marketplace_update", error: String(err.stderr || err.message) });
|
||||
record({ ok: false, step: "marketplace_update", error: failureText(err) });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
run(claude, ["plugin", "update", `${plugin}@${marketplace}`, "-y"], 600_000);
|
||||
} catch (err) {
|
||||
record({ ok: false, step: "plugin_update", error: String(err.stderr || err.message) });
|
||||
record({ ok: false, step: "plugin_update", error: failureText(err) });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,7 +236,7 @@ function main() {
|
||||
record({
|
||||
ok: false,
|
||||
step: "bootstrap",
|
||||
error: String(err.stderr || err.message),
|
||||
error: failureText(err),
|
||||
plugin_root: newRoot,
|
||||
note: "the old server was stopped; run /agenthub-update to recover",
|
||||
});
|
||||
@@ -211,10 +246,14 @@ function main() {
|
||||
record({ ok: true, step: "done", plugin_root: newRoot });
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (err) {
|
||||
// A helper that dies without a verdict is the failure mode this file exists
|
||||
// to prevent, so even an unexpected throw gets recorded.
|
||||
record({ ok: false, step: "unexpected", error: err.message || String(err) });
|
||||
module.exports = { failureText };
|
||||
|
||||
if (require.main === module) {
|
||||
try {
|
||||
main();
|
||||
} catch (err) {
|
||||
// A helper that dies without a verdict is the failure mode this file exists
|
||||
// to prevent, so even an unexpected throw gets recorded.
|
||||
record({ ok: false, step: "unexpected", error: err.message || String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user