toolroll 0.9.90 → 0.9.91
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/CHANGELOG.md +19 -0
- package/dist/release-adapters.d.ts +2 -0
- package/dist/release-adapters.js +30 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.91 — 2026-11-04
|
|
4
|
+
|
|
5
|
+
- **A deploy checks your HTTPS console before it changes anything.** If the
|
|
6
|
+
console you reach Toolroll through (over Tailscale, say) doesn't answer, the
|
|
7
|
+
deploy now stops before it pauses new work or stops the running service,
|
|
8
|
+
and says to bring the console back. It used to find out only at the end,
|
|
9
|
+
with the new version already running and new work paused.
|
|
10
|
+
- **A release finishes a deploy that stopped after the new version started.**
|
|
11
|
+
If a deploy's last check failed once the new version was already running
|
|
12
|
+
(an HTTPS console that didn't answer, say), `toolroll release` sent you to
|
|
13
|
+
`toolroll release --recover-deploy`, which sent you back, and new work
|
|
14
|
+
stayed paused. Rerunning the release now finishes the deploy: it checks
|
|
15
|
+
the new service, both consoles and the project workers, then lifts the
|
|
16
|
+
pause.
|
|
17
|
+
- **A resumed release reads its task with its own build.** It used to call
|
|
18
|
+
whichever `toolroll` came first on your PATH, which until the release
|
|
19
|
+
relinks it is still the previous version and can't open a database the
|
|
20
|
+
deploy just upgraded.
|
|
21
|
+
|
|
3
22
|
## 0.9.90 — 2026-11-04
|
|
4
23
|
|
|
5
24
|
- **Type `toolroll` and you're in.** At your own terminal, bare `toolroll`
|
|
@@ -62,6 +62,8 @@ export type AdapterOptions = {
|
|
|
62
62
|
formula?: string;
|
|
63
63
|
/** How long the deployment itself may run, in minutes. */
|
|
64
64
|
deployMinutes?: number;
|
|
65
|
+
/** The Toolroll CLI the release calls, as [program, ...leading args]; by default this runtime's own bin.js. */
|
|
66
|
+
cli?: readonly string[];
|
|
65
67
|
run?: Exec;
|
|
66
68
|
fetch?: typeof fetch;
|
|
67
69
|
say?: (line: string) => void;
|
package/dist/release-adapters.js
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, realpath
|
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { basename, delimiter, dirname, join } from "node:path";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
13
14
|
import { parse as parseYaml } from "yaml";
|
|
14
15
|
import { sleep } from "./sleep.js";
|
|
15
16
|
import { callLabel, readOnlyCall, retryCall, retryingExec, writeWithRecheck } from "./release-net.js";
|
|
@@ -50,6 +51,13 @@ export const exec = (command, args, options = {}) => new Promise(done => {
|
|
|
50
51
|
child.stdin?.end();
|
|
51
52
|
});
|
|
52
53
|
/** Without NODE_OPTIONS, which a test runner or debugger may have left for this process. */
|
|
54
|
+
/** This runtime's own CLI (dist/bin.js beside this module) when it has one; from source, `toolroll` as installed. */
|
|
55
|
+
const ownCli = () => {
|
|
56
|
+
const bin = join(dirname(fileURLToPath(import.meta.url)), "bin.js");
|
|
57
|
+
return existsSync(bin) ? [process.execPath, bin] : ["toolroll"];
|
|
58
|
+
};
|
|
59
|
+
/** Journal phases the deployment's finish accepts (scripts/deploy-phases.mjs finish): the new service runs, unproven. */
|
|
60
|
+
const FINISHES_FORWARD = Object.freeze(["started", "healthy"]);
|
|
53
61
|
const cleanEnv = (extra = {}) => {
|
|
54
62
|
const env = { ...process.env, ...extra };
|
|
55
63
|
delete env["NODE_OPTIONS"];
|
|
@@ -244,8 +252,11 @@ export function releaseAdapters(options) {
|
|
|
244
252
|
/** A write by gh, never retried by itself. */
|
|
245
253
|
const ghWrite = (remainingMs, ...args) => run(tools.gh, args, { cwd: "/", env: gitEnv, timeoutMs: Math.min(120_000, remainingMs) });
|
|
246
254
|
const ghJson = async (what, ...args) => JSON.parse(await must(gh(...args), what));
|
|
255
|
+
// The build running this release, never a PATH lookup: until the link step, `toolroll` on PATH is still the previous
|
|
256
|
+
// build, which refuses a database the deployment migrated, so a release resumed after its deploy couldn't read its task.
|
|
257
|
+
const cli = options.cli ?? ownCli();
|
|
247
258
|
const toolroll = async (...args) => {
|
|
248
|
-
const result = await run(
|
|
259
|
+
const result = await run(cli[0], [...cli.slice(1), ...args, "--json"], { env: cleanEnv(), timeoutMs: 120_000 });
|
|
249
260
|
try {
|
|
250
261
|
return object(JSON.parse(result.stdout));
|
|
251
262
|
}
|
|
@@ -562,6 +573,24 @@ export function releaseAdapters(options) {
|
|
|
562
573
|
const running = (options.processes ?? processes)().filter(one => one.command.includes("deploy-browser.mjs") && one.command.includes(stage));
|
|
563
574
|
if (running.length > 0)
|
|
564
575
|
return { ok: false, message: `the deployment is still running (process ${running.map(one => one.pid).join(", ")}); let it finish, then rerun` };
|
|
576
|
+
// The new service started (deploy-rescue.ts FORWARD_ONLY): recovery refuses to roll it back, so the release
|
|
577
|
+
// resumes it forward with the deployment's own finish (health, both consoles, leases, then the pause lifted).
|
|
578
|
+
// Finishing deploys, so the candidate is proven first, as for a deploy.
|
|
579
|
+
const phase = (() => { try {
|
|
580
|
+
return object(JSON.parse(readFileSync(join(stage, "deployment.json"), "utf8")))["phase"];
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
return undefined;
|
|
584
|
+
} })();
|
|
585
|
+
if (FINISHES_FORWARD.includes(String(phase))) {
|
|
586
|
+
const proven = proveCandidate(runId);
|
|
587
|
+
if (!proven.ok)
|
|
588
|
+
return stopped(stage, proven.message);
|
|
589
|
+
const finished = await deployScript(worktree, stage, runId, proven, ["--yes", "--phase", "finish"]);
|
|
590
|
+
if (finished.code === 0)
|
|
591
|
+
return { ok: true };
|
|
592
|
+
return stopped(stage, `${tail(finished)} (the deployment journal is at ${String(phase)}; finishing it forward stopped; ${join(stage, "deployment.json")})`);
|
|
593
|
+
}
|
|
565
594
|
// Recovery needs no approval, but it runs only the recorded commit's own script, extracted by this runtime.
|
|
566
595
|
const recorded = recordedCommit(runId);
|
|
567
596
|
if (recorded === null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolroll",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.91",
|
|
4
4
|
"description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|