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:
@@ -463,20 +463,32 @@ describe("listCommits / readFileAt / listPaths / listTree", () => {
|
||||
|
||||
it("lists untracked-but-not-ignored files and marks what changed, folders included", async () => {
|
||||
write(LANE, "src/main.ts", "m\n");
|
||||
write(LANE, ".gitignore", "*.log\n");
|
||||
write(LANE, ".gitignore", "*.log\nvendor/\n");
|
||||
gc(LANE, "add", "-A");
|
||||
gc(LANE, "commit", "-qm", "src");
|
||||
write(LANE, "a.txt", "modified\n");
|
||||
write(LANE, "new.txt", "fresh\n");
|
||||
write(LANE, "gen/deep/out.txt", "wholly untracked folder\n");
|
||||
write(LANE, "noise.log", "ignored\n");
|
||||
write(LANE, "vendor/lib/x.js", "ignored folder\n");
|
||||
|
||||
const root = await listTree(LANE, {});
|
||||
const by = Object.fromEntries(root.entries.map((e) => [e.name, e]));
|
||||
assert.equal(by["a.txt"].status, " M");
|
||||
assert.equal(by["new.txt"].status, "??");
|
||||
assert.equal(by["keep.txt"].status, undefined);
|
||||
assert.equal(by["noise.log"], undefined, "an ignored file stays out of the tree");
|
||||
assert.equal(by["noise.log"].ignored, true, "an ignored file is listed, marked");
|
||||
assert.equal(by.vendor.ignored, true);
|
||||
assert.equal(by.vendor.dir, true);
|
||||
assert.equal(by[".git"], undefined, "git's own dir is never listed");
|
||||
assert.equal(by["a.txt"].ignored, undefined);
|
||||
assert.equal(by.src.ignored, undefined);
|
||||
// Inside an ignored folder, which `ls-files --ignored` cannot list.
|
||||
const lib = await listTree(LANE, { subdir: "vendor/lib" });
|
||||
assert.deepEqual(
|
||||
lib.entries.map((e) => [e.name, e.ignored]),
|
||||
[["x.js", true]]
|
||||
);
|
||||
assert.equal(by.gen.changed, true);
|
||||
assert.equal(by.src.changed, undefined);
|
||||
|
||||
|
||||
@@ -169,3 +169,36 @@ describe("plugin-update-helper name guard", () => {
|
||||
assert.ok(!Number.isNaN(Date.parse(v.finished_at)));
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin-update-helper failureText", () => {
|
||||
// Required, not spawned: the helper only runs main() as a script.
|
||||
const { failureText } = require("../../scripts/plugin-update-helper");
|
||||
|
||||
it("names why the bootstrap refused, which it prints on stdout", () => {
|
||||
const err = Object.assign(new Error("Command failed: node plugin-bootstrap.js --force"), {
|
||||
status: 1,
|
||||
stdout: "installing…\nbootstrap: locked\n",
|
||||
stderr: "",
|
||||
});
|
||||
assert.equal(failureText(err), "installing…\nbootstrap: locked\n(exit 1)");
|
||||
});
|
||||
|
||||
it("keeps only the last lines, stdout then stderr", () => {
|
||||
const err = Object.assign(new Error("x"), {
|
||||
status: 2,
|
||||
stdout: ["1", "2", "3", "4", "5", "6", "7"].join("\n"),
|
||||
stderr: "boom",
|
||||
});
|
||||
assert.equal(failureText(err), "3\n4\n5\n6\n7\nboom\n(exit 2)");
|
||||
});
|
||||
|
||||
it("says a killed command was killed, and falls back to the message when nothing was printed", () => {
|
||||
const err = Object.assign(new Error("Command failed: node b.js --force\n"), {
|
||||
status: null,
|
||||
signal: "SIGTERM",
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
});
|
||||
assert.equal(failureText(err), "Command failed: node b.js --force (killed by SIGTERM)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,7 +91,17 @@ describe("POST /api/updates/check", () => {
|
||||
});
|
||||
|
||||
describe("POST /api/updates/apply", () => {
|
||||
it("declines with a reason instead of pulling/restarting when there is nothing to apply", async () => {
|
||||
it("declines with a reason instead of pulling/restarting when there is nothing to apply", async (t) => {
|
||||
// Never let this test APPLY anything. When this checkout really is behind
|
||||
// a release (or reports a version older than the newest tag), the route
|
||||
// fast-forwards the repo, runs `npm run setup` — which under the shell's
|
||||
// NODE_ENV=production prunes every dev dependency — and SIGTERMs the
|
||||
// process to restart it. That happened once; ask first and stand down.
|
||||
const status = await httpFetch("/api/updates/status");
|
||||
if (status.body.update_available) {
|
||||
t.skip(`this checkout is behind ${status.body.latest_release}; not applying it in a test`);
|
||||
return;
|
||||
}
|
||||
// The test repo checkout has no update pending (or isn't fast-forwardable
|
||||
// from a bare test run), so this exercises the decline path only — it must
|
||||
// never touch the working tree or process in a test run.
|
||||
|
||||
Reference in New Issue
Block a user