toolroll 0.8.0 → 0.8.2
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 +40 -0
- package/README.md +5 -5
- package/dist/agent-onboard.d.ts +45 -18
- package/dist/agent-onboard.js +146 -57
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +14 -13
- package/dist/checkout-cleanup.d.ts +84 -0
- package/dist/checkout-cleanup.js +245 -0
- package/dist/daemon.d.ts +6 -2
- package/dist/daemon.js +6 -5
- package/dist/first-run.d.ts +8 -2
- package/dist/first-run.js +12 -8
- package/dist/install-method.d.ts +1 -1
- package/dist/install-method.js +4 -0
- package/dist/mcp.js +26 -0
- package/dist/operate.d.ts +2 -4
- package/dist/operate.js +167 -30
- package/dist/prompt.d.ts +11 -0
- package/dist/prompt.js +41 -0
- package/dist/serve.js +103 -28
- package/dist/storage-ui.d.ts +10 -0
- package/dist/storage-ui.js +67 -0
- package/dist/storage.d.ts +15 -1
- package/dist/storage.js +62 -7
- package/dist/store.d.ts +6 -6
- package/dist/store.js +23 -20
- package/dist/toolroll-update-cli.js +7 -3
- package/dist/toolroll-update-ui.d.ts +2 -0
- package/dist/toolroll-update-ui.js +7 -6
- package/dist/toolroll-update.d.ts +69 -15
- package/dist/toolroll-update.js +572 -110
- package/dist/worktree.d.ts +24 -10
- package/dist/worktree.js +84 -44
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { installMethod } from "./install-method.js";
|
|
|
11
11
|
import { isNewer, latestVersionNow } from "./releases.js";
|
|
12
12
|
import { databasePath } from "./store.js";
|
|
13
13
|
import { PACKAGE_VERSION } from "./version.js";
|
|
14
|
-
import { currentRuntime, machineSystem, readRuntimeUpdate, requestRuntimeUpdateCancel, resumeRuntimeUpdate, retireUpdateJob,
|
|
14
|
+
import { currentRuntime, lastCompletedUpdate, machineSystem, readRuntimeUpdate, requestRuntimeUpdateCancel, resumeRuntimeUpdate, retireUpdateJob, startRuntimeRollback, startRuntimeUpdate, } from "./toolroll-update.js";
|
|
15
15
|
/** Loaded on first use (as backup.ts and store.ts do), so modules that only import this one (the console,
|
|
16
16
|
* and tests that load it in a browser-like environment) never need `node:sqlite` itself. */
|
|
17
17
|
function sqlite() {
|
|
@@ -90,9 +90,13 @@ export async function runUpdateCommand(args, write, deps = {}) {
|
|
|
90
90
|
write("This Toolroll runs from a source checkout. Update it with git; toolroll update replaces installed releases only.");
|
|
91
91
|
return 1;
|
|
92
92
|
}
|
|
93
|
+
if (method.kind === "desktop") {
|
|
94
|
+
write("This Toolroll is the Toolroll app, which updates as a whole app. Update it from the Toolroll app.");
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
93
97
|
if (has("rollback")) {
|
|
94
|
-
const last =
|
|
95
|
-
if (!last
|
|
98
|
+
const last = lastCompletedUpdate(stateDir);
|
|
99
|
+
if (!last) {
|
|
96
100
|
write("There is no completed update to roll back.");
|
|
97
101
|
return 1;
|
|
98
102
|
}
|
|
@@ -24,6 +24,8 @@ export type UpdatesView = {
|
|
|
24
24
|
version: string;
|
|
25
25
|
notes: string[];
|
|
26
26
|
} | null;
|
|
27
|
+
/** The version `toolroll update --rollback` returns to: the last completed update's, while it is what runs. */
|
|
28
|
+
rollbackTo: string | null;
|
|
27
29
|
csrf: string;
|
|
28
30
|
};
|
|
29
31
|
/** The steps, each done, current, failed or waiting. Also the live region's fragment. */
|
|
@@ -59,17 +59,18 @@ export function updatesHtml(view, notice) {
|
|
|
59
59
|
const newer = latest !== null && newerThan(latest, view.current);
|
|
60
60
|
const state = view.method.kind === "npx" ? `<h2>Toolroll ${e(view.current)}</h2><p class="meta">npx runs the latest release each time, so this is current.</p>`
|
|
61
61
|
: view.method.kind === "source" ? `<h2>Toolroll ${e(view.current)}</h2><p class="meta">This runs from a source checkout. Update it with git.</p>`
|
|
62
|
-
: newer ? `<h2>Toolroll ${e(latest)} is available</h2><p class="meta">You have ${e(view.current)}.
|
|
63
|
-
:
|
|
64
|
-
:
|
|
65
|
-
: `<h2>Toolroll ${e(view.current)}
|
|
66
|
-
|
|
62
|
+
: newer && view.method.kind === "desktop" ? `<h2>Toolroll ${e(latest)} is available</h2><p class="meta">You have ${e(view.current)}. Update it from the Toolroll app.</p>`
|
|
63
|
+
: newer ? `<h2>Toolroll ${e(latest)} is available</h2><p class="meta">You have ${e(view.current)}. Running work finishes first, and your current version is kept so you can go back.</p>`
|
|
64
|
+
: "off" in view.latest ? `<h2>Toolroll ${e(view.current)}</h2><p class="meta">Update checks are off.</p><form method="get" action="/settings/updates" class="update-actions"><input type="hidden" name="check" value="now"><button type="submit">Check now</button></form>`
|
|
65
|
+
: latest === null ? `<h2>Toolroll ${e(view.current)}</h2><p class="meta">Couldn’t check for a newer release: ${e("problem" in view.latest ? view.latest.problem : "")}</p>`
|
|
66
|
+
: `<h2>Toolroll ${e(view.current)} is up to date</h2>`;
|
|
67
|
+
const form = newer && !["npx", "source", "desktop"].includes(view.method.kind)
|
|
67
68
|
? `<form method="post" action="/settings/updates">${csrf}<input type="hidden" name="version" value="${e(latest)}">` +
|
|
68
69
|
`<label class="step-up">Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>` +
|
|
69
70
|
`<div class="update-actions"><button type="submit" name="when" value="now" class="primary">Update now</button>` +
|
|
70
71
|
`<button type="submit" name="when" value="when-idle">When idle</button><button type="submit" name="when" value="tonight">Tonight (03:00)</button></div></form>`
|
|
71
72
|
: "";
|
|
72
|
-
const rollback =
|
|
73
|
+
const rollback = view.rollbackTo !== null ? `<p class="meta">To go back to ${e(view.rollbackTo)}: <code>toolroll update --rollback</code></p>` : "";
|
|
73
74
|
return `<section class="updates">${note}${whatsNew}${j ? outcomeHtml(j) : ""}<div class="card" data-update-state="${newer ? "available" : "current"}">${state}${form}${rollback}</div></section>`;
|
|
74
75
|
}
|
|
75
76
|
export function newerThan(a, b) {
|
|
@@ -2,6 +2,7 @@ import type { DatabaseSync } from "node:sqlite";
|
|
|
2
2
|
import { type SupervisorRunner } from "./daemon.js";
|
|
3
3
|
export declare const PROVENANCE_REPOSITORY = "https://github.com/ap9000/toolroll";
|
|
4
4
|
export declare const PROVENANCE_WORKFLOW = ".github/workflows/publish.yml";
|
|
5
|
+
export declare const PROVENANCE_ISSUER = "https://token.actions.githubusercontent.com";
|
|
5
6
|
/** The steps a person sees, in order. */
|
|
6
7
|
export declare const UPDATE_STEPS: readonly ["verifying", "draining", "backing-up", "rehearsing", "switching", "restarting", "health"];
|
|
7
8
|
export type UpdateStep = typeof UPDATE_STEPS[number];
|
|
@@ -66,6 +67,13 @@ export type RuntimeUpdateJournal = {
|
|
|
66
67
|
unit: string;
|
|
67
68
|
pids: number[];
|
|
68
69
|
};
|
|
70
|
+
/** Each repo's watch daemon that runs this version, recorded the same way: switched with the service, and stopped and
|
|
71
|
+
* restarted with it only when launchd had it loaded before the update (`loaded` absent: 0.8.1 stopped every one). */
|
|
72
|
+
watches?: {
|
|
73
|
+
unit: string;
|
|
74
|
+
pids: number[];
|
|
75
|
+
loaded?: boolean;
|
|
76
|
+
}[];
|
|
69
77
|
/** Recorded before each change so a resumed or failed run knows what to put back. */
|
|
70
78
|
switched?: {
|
|
71
79
|
links: {
|
|
@@ -76,10 +84,21 @@ export type RuntimeUpdateJournal = {
|
|
|
76
84
|
path: string;
|
|
77
85
|
saved: string;
|
|
78
86
|
} | null;
|
|
87
|
+
watches?: {
|
|
88
|
+
path: string;
|
|
89
|
+
saved: string;
|
|
90
|
+
}[];
|
|
79
91
|
databaseRestored?: boolean;
|
|
80
92
|
};
|
|
81
|
-
/** What the live database held when a failed run restored its backup: nothing written is lost. */
|
|
93
|
+
/** What the live database held when a failed run restored its backup: nothing written is lost. The newest of `kept`. */
|
|
82
94
|
keptAside?: string;
|
|
95
|
+
/** The live database could not be read, so it was moved aside whole rather than copied. */
|
|
96
|
+
keptAsideUnreadable?: boolean;
|
|
97
|
+
/** Every copy kept aside, oldest first: a retried restore adds one and never drops the pointer to an earlier one. */
|
|
98
|
+
kept?: {
|
|
99
|
+
path: string;
|
|
100
|
+
unreadable: boolean;
|
|
101
|
+
}[];
|
|
83
102
|
/** The restore put the backup back: a retried restore never puts it back again (what was written since belongs to
|
|
84
103
|
* the restored version and would be lost), and it keeps a fresh copy aside before every attempt until then. */
|
|
85
104
|
restoredDatabase?: boolean;
|
|
@@ -107,8 +126,18 @@ export type UpdateSystem = {
|
|
|
107
126
|
commands: (from: RuntimeRef) => string[];
|
|
108
127
|
/** The background service's definition, when one runs `from`. */
|
|
109
128
|
serviceUnit: (from: RuntimeRef) => string | null;
|
|
129
|
+
/** Every repo's watch daemon definition that runs `from`. */
|
|
130
|
+
watchUnits: (from: RuntimeRef) => string[];
|
|
131
|
+
/** npm's own Sigstore verifier: the bundle's certificate chains to Sigstore, its signature is in the transparency
|
|
132
|
+
* log, and the certificate names exactly this identity. */
|
|
133
|
+
sigstore: (bundle: unknown, identity: {
|
|
134
|
+
issuer: string;
|
|
135
|
+
identity: string;
|
|
136
|
+
}) => Promise<void>;
|
|
110
137
|
/** The processes the service runs now. */
|
|
111
138
|
servicePids: (unit: string) => Promise<number[]>;
|
|
139
|
+
/** Whether launchd has the service loaded now. */
|
|
140
|
+
serviceLoaded: (unit: string) => Promise<boolean>;
|
|
112
141
|
/** Unload the service; resolves once launchd no longer has it. */
|
|
113
142
|
stopService: (unit: string) => Promise<void>;
|
|
114
143
|
/** Load and start the service from its definition on disk. */
|
|
@@ -118,8 +147,11 @@ export type UpdateSystem = {
|
|
|
118
147
|
healthTimeoutMs?: number;
|
|
119
148
|
/** How long stopped service processes may take to exit. */
|
|
120
149
|
exitTimeoutMs?: number;
|
|
121
|
-
/**
|
|
122
|
-
|
|
150
|
+
/** Bytes free for this user in the folder `dir` is on. */
|
|
151
|
+
freeBytes?: (dir: string) => number;
|
|
152
|
+
/** Fault injection for state-machine tests, never selectable by a flag. `kept-aside`: the live database was just
|
|
153
|
+
* kept aside, before the backup is put back. */
|
|
154
|
+
checkpoint?: (phase: RuntimePhase | "kept-aside") => void;
|
|
123
155
|
};
|
|
124
156
|
export type UpdateOutcome = {
|
|
125
157
|
ok: boolean;
|
|
@@ -128,16 +160,23 @@ export type UpdateOutcome = {
|
|
|
128
160
|
journal: RuntimeUpdateJournal | null;
|
|
129
161
|
};
|
|
130
162
|
export declare const runtimeUpdateTerminal: (phase: RuntimePhase) => boolean;
|
|
131
|
-
export declare function readRuntimeUpdate(stateDir: string): RuntimeUpdateJournal | null;
|
|
163
|
+
export declare function readRuntimeUpdate(stateDir: string, file?: string): RuntimeUpdateJournal | null;
|
|
164
|
+
/** The update `--rollback` returns from: the last one that completed, whatever was attempted since. */
|
|
165
|
+
export declare function lastCompletedUpdate(stateDir: string): RuntimeUpdateJournal | null;
|
|
132
166
|
/** Running work by name, for a refusal a person can act on. */
|
|
133
167
|
export declare function runningWorkWords(db: DatabaseSync): string | null;
|
|
134
|
-
|
|
135
|
-
* Toolroll repository and its publish workflow. Anything else is refused.
|
|
136
|
-
* npm's own check (UpdateSystem.install) verifies the statement's signature. */
|
|
137
|
-
export declare function checkProvenance(attestations: unknown, version: string, sha512Hex: string): {
|
|
168
|
+
export type Provenance = {
|
|
138
169
|
repository: string;
|
|
139
170
|
workflow: string;
|
|
171
|
+
issuer: string;
|
|
172
|
+
identity: string;
|
|
173
|
+
bundle: unknown;
|
|
140
174
|
};
|
|
175
|
+
/** The npm provenance for exactly these bytes of Toolroll itself must be signed by a certificate that names the
|
|
176
|
+
* Toolroll repository, its publish workflow and GitHub Actions as the issuer. The statement's own claims name
|
|
177
|
+
* nobody: only the certificate, whose key must have signed the statement. Sigstore's chain and transparency log are
|
|
178
|
+
* then checked by npm's own verifier (UpdateSystem.sigstore) for the identity returned here. */
|
|
179
|
+
export declare function checkProvenance(attestations: unknown, version: string, sha512Hex: string): Provenance;
|
|
141
180
|
/** Headlines of this version's changelog section: its bold lead phrases. */
|
|
142
181
|
export declare function releaseNotes(changelog: string, version: string): string[];
|
|
143
182
|
/** Every rename here: what is renamed is flushed first (a file itself, a link
|
|
@@ -184,7 +223,10 @@ export declare function startRuntimeRollback(o: {
|
|
|
184
223
|
export declare function resumeRuntimeUpdate(stateDir: string, system: UpdateSystem, id?: string): Promise<UpdateOutcome>;
|
|
185
224
|
/** A prepared update whose job could not start. */
|
|
186
225
|
export declare function abandonRuntimeUpdate(stateDir: string, id: string, why: string, now: Date): void;
|
|
187
|
-
|
|
226
|
+
/** `locked`: a test's seam, called once the updater's lock is held and before the journal is read again. */
|
|
227
|
+
export declare function requestRuntimeUpdateCancel(stateDir: string, now?: Date, seams?: {
|
|
228
|
+
locked?: () => void;
|
|
229
|
+
}): string;
|
|
188
230
|
/** Keep the newest release-* runtimes (and any in `keep`), at most KEEP_RUNTIMES; deploy-browser's browser-* and
|
|
189
231
|
* the rollback-* records are not this updater's to remove. */
|
|
190
232
|
export declare function pruneRuntimes(stateDir: string, keep: readonly string[]): string[];
|
|
@@ -196,30 +238,41 @@ export type RuntimeUpdateStatus = {
|
|
|
196
238
|
version: string;
|
|
197
239
|
notes: string[];
|
|
198
240
|
} | null;
|
|
241
|
+
/** The last completed update `--rollback` returns from, whatever was attempted since. */
|
|
242
|
+
lastUpdate: {
|
|
243
|
+
from: string;
|
|
244
|
+
to: string;
|
|
245
|
+
} | null;
|
|
199
246
|
};
|
|
200
247
|
export declare function runtimeUpdateStatus(stateDir: string): RuntimeUpdateStatus;
|
|
201
248
|
export declare function markWhatsNewSeen(stateDir: string): void;
|
|
202
249
|
type Exec = (command: string, args: string[], options?: {
|
|
203
250
|
cwd?: string;
|
|
204
251
|
timeout?: number;
|
|
252
|
+
input?: string;
|
|
205
253
|
}) => {
|
|
206
254
|
status: number | null;
|
|
207
255
|
stdout: string;
|
|
208
256
|
stderr: string;
|
|
209
257
|
};
|
|
210
258
|
export declare function currentRuntime(version: string): RuntimeRef;
|
|
211
|
-
/** Seams for tests: npm and ps (`exec`), launchctl (`run`), and
|
|
259
|
+
/** Seams for tests: npm, node and ps (`exec`), launchctl (`run`), process liveness, and launchd's waits (`sleep`). */
|
|
212
260
|
export type MachineSeams = {
|
|
213
261
|
exec?: Exec;
|
|
214
262
|
run?: SupervisorRunner;
|
|
215
263
|
alive?: (pid: number) => boolean;
|
|
264
|
+
sleep?: (ms: number) => Promise<void>;
|
|
265
|
+
execPath?: string;
|
|
216
266
|
};
|
|
217
267
|
export declare function machineSystem(home?: string, env?: Record<string, string | undefined>, seams?: MachineSeams): UpdateSystem;
|
|
218
|
-
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
|
|
222
|
-
|
|
268
|
+
/** npm's own Sigstore verifier. Found from npm itself (the `npm` on PATH, then the one beside node), never only from
|
|
269
|
+
* `npm root --global`: with a custom global prefix (npm's advice for permission errors) that folder holds the global
|
|
270
|
+
* packages, not npm. */
|
|
271
|
+
export declare function findSigstoreVerifier(o: {
|
|
272
|
+
path: string;
|
|
273
|
+
execPath: string;
|
|
274
|
+
npmRoot: () => string | null;
|
|
275
|
+
}): string | null;
|
|
223
276
|
export declare function launchRuntimeUpdate(args: {
|
|
224
277
|
databaseFile: string;
|
|
225
278
|
id: string;
|
|
@@ -228,6 +281,7 @@ export declare function launchRuntimeUpdate(args: {
|
|
|
228
281
|
home?: string;
|
|
229
282
|
run?: SupervisorRunner;
|
|
230
283
|
platform?: NodeJS.Platform;
|
|
284
|
+
npmBin?: () => Promise<string | null>;
|
|
231
285
|
}): Promise<void>;
|
|
232
286
|
/** The job's last act: its definition goes, so nothing can start it again. Only the definition for this id. */
|
|
233
287
|
export declare function retireUpdateJob(id: string, home?: string): void;
|