@astrosheep/keiyaku 4.5.7 → 4.5.9
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/README.md +10 -0
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +3 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +12 -11
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +33 -12
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +35 -96
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +28 -49
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +21 -15
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +148 -89
- package/build/src/akuma/abort.d.ts +2 -1
- package/build/src/akuma/abort.js +6 -23
- package/build/src/akuma/akuma-handle.js +4 -2
- package/build/src/akuma/akuma-observe.d.ts +2 -0
- package/build/src/akuma/akuma-observe.js +1 -0
- package/build/src/akuma/akuma-product.js +9 -7
- package/build/src/akuma/akuma.d.ts +392 -9
- package/build/src/akuma/akuma.js +31 -1
- package/build/src/akuma/allowed.d.ts +1 -1
- package/build/src/akuma/allowed.js +1 -0
- package/build/src/akuma/archetype.js +1 -1
- package/build/src/akuma/body.d.ts +8 -5
- package/build/src/akuma/body.js +12 -5
- package/build/src/akuma/call-request.d.ts +80 -0
- package/build/src/akuma/call-request.js +211 -0
- package/build/src/akuma/heart/facts.d.ts +16 -78
- package/build/src/akuma/heart/facts.js +11 -0
- package/build/src/akuma/heart/index.d.ts +3 -2
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/request-authority.d.ts +5 -4
- package/build/src/akuma/heart/request-authority.js +20 -34
- package/build/src/akuma/heart/request-rows.js +12 -23
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +5 -13
- package/build/src/akuma/index.d.ts +1 -1
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/projection-read.d.ts +1 -0
- package/build/src/akuma/projection-read.js +8 -1
- package/build/src/akuma/projection.d.ts +907 -137
- package/build/src/akuma/projection.js +193 -9
- package/build/src/akuma/provider.d.ts +30 -3
- package/build/src/akuma/provider.js +111 -0
- package/build/src/akuma/providers/acp/core.d.ts +2 -2
- package/build/src/akuma/providers/acp/core.js +13 -6
- package/build/src/akuma/providers/acp/index.js +5 -4
- package/build/src/akuma/providers/claude/events.d.ts +2 -2
- package/build/src/akuma/providers/claude/events.js +16 -3
- package/build/src/akuma/providers/claude/index.d.ts +3 -3
- package/build/src/akuma/providers/claude/index.js +16 -5
- package/build/src/akuma/providers/codex-app-server/events.d.ts +2 -2
- package/build/src/akuma/providers/codex-app-server/events.js +25 -4
- package/build/src/akuma/providers/codex-app-server/index.js +18 -6
- package/build/src/akuma/providers/grok-build/index.js +5 -4
- package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
- package/build/src/akuma/providers/pi/index.js +72 -58
- package/build/src/akuma/request-lifecycle.d.ts +4 -10
- package/build/src/akuma/request-lifecycle.js +7 -5
- package/build/src/akuma/request-rendezvous.d.ts +26 -0
- package/build/src/akuma/request-rendezvous.js +100 -0
- package/build/src/akuma/request-serve.d.ts +6 -66
- package/build/src/akuma/request-serve.js +83 -210
- package/build/src/akuma/request-wire.d.ts +109 -115
- package/build/src/akuma/request-wire.js +63 -318
- package/build/src/akuma/requests.d.ts +19 -47
- package/build/src/akuma/requests.js +51 -107
- package/build/src/akuma/turn-drive.d.ts +5 -3
- package/build/src/akuma/turn-drive.js +39 -11
- package/build/src/akuma-body.d.ts +7 -2
- package/build/src/akuma-body.js +68 -46
- package/build/src/body/amend.js +18 -19
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +2 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
- package/build/src/cli/commands/akuma-invoke.js +17 -9
- package/build/src/cli/commands/akuma.js +8 -8
- package/build/src/cli/commands/contract-invoke.d.ts +3 -2
- package/build/src/cli/commands/contract-invoke.js +42 -30
- package/build/src/cli/commands/contract.d.ts +16 -20
- package/build/src/cli/commands/contract.js +58 -25
- package/build/src/cli/commands/install.d.ts +2 -1
- package/build/src/cli/commands/install.js +3 -2
- package/build/src/cli/commands/task-invoke.d.ts +3 -0
- package/build/src/cli/commands/task-invoke.js +23 -140
- package/build/src/cli/commands/task.js +54 -20
- package/build/src/cli/invoke.js +39 -25
- package/build/src/cli/main.js +2 -1
- package/build/src/cli/parse.d.ts +2 -2
- package/build/src/cli/parse.js +56 -27
- package/build/src/cli/render/akuma-activity.d.ts +3 -0
- package/build/src/cli/render/akuma-activity.js +45 -29
- package/build/src/cli/render/akuma-tool.d.ts +5 -1
- package/build/src/cli/render/akuma.js +4 -8
- package/build/src/cli/render/audit.js +1 -1
- package/build/src/cli/render/catalog.js +80 -21
- package/build/src/cli/render/contract-observation.d.ts +2 -0
- package/build/src/cli/render/contract-observation.js +11 -0
- package/build/src/cli/render/contract.js +35 -62
- package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
- package/build/src/cli/render/kanshi-akuma.js +132 -0
- package/build/src/cli/render/kanshi.js +131 -213
- package/build/src/cli/render/receipt.d.ts +1 -2
- package/build/src/cli/render/receipt.js +76 -26
- package/build/src/cli/render/refusal.js +2 -10
- package/build/src/cli/render/region.js +5 -2
- package/build/src/cli/render/settings.d.ts +1 -1
- package/build/src/cli/render/settings.js +27 -8
- package/build/src/cli/render/task.js +22 -5
- package/build/src/cli/render/terminal.d.ts +32 -1
- package/build/src/cli/render/terminal.js +77 -1
- package/build/src/cli/result.d.ts +3 -3
- package/build/src/cli/runtime.js +4 -2
- package/build/src/cli/selectors.d.ts +2 -1
- package/build/src/cli/selectors.js +4 -3
- package/build/src/cli/square-edge.js +120 -36
- package/build/src/cli/usage.js +2 -1
- package/build/src/contract-guidance.d.ts +5 -0
- package/build/src/contract-guidance.js +121 -0
- package/build/src/contract-worktree.d.ts +0 -1
- package/build/src/contract-worktree.js +32 -83
- package/build/src/coordination/sqlite-transaction-lock.d.ts +1 -0
- package/build/src/coordination/sqlite-transaction-lock.js +5 -0
- package/build/src/core/facts/codec.d.ts +2 -0
- package/build/src/core/facts/codec.js +4 -0
- package/build/src/core/verbs/placement.d.ts +6 -1
- package/build/src/core/verbs/placement.js +5 -3
- package/build/src/dispatch/index.d.ts +0 -2
- package/build/src/dispatch/index.js +54 -55
- package/build/src/git/admission.js +2 -2
- package/build/src/git/hooks.d.ts +9 -15
- package/build/src/git/hooks.js +23 -317
- package/build/src/git/nuke.d.ts +2 -1
- package/build/src/git/nuke.js +39 -21
- package/build/src/git/private-state-seat.d.ts +12 -0
- package/build/src/git/private-state-seat.js +39 -0
- package/build/src/git/process.d.ts +6 -0
- package/build/src/git/process.js +26 -11
- package/build/src/git/read-observation.js +29 -20
- package/build/src/git/reconcile.d.ts +13 -1
- package/build/src/git/reconcile.js +70 -11
- package/build/src/git/ref-migration.d.ts +22 -0
- package/build/src/git/ref-migration.js +55 -0
- package/build/src/git/repository.d.ts +10 -6
- package/build/src/git/repository.js +16 -8
- package/build/src/git/target-placement.d.ts +1 -0
- package/build/src/git/target-placement.js +5 -0
- package/build/src/git/tender.d.ts +1 -0
- package/build/src/git/terminal-reconcile.d.ts +1 -1
- package/build/src/git/terminal-reconcile.js +22 -20
- package/build/src/git/workspace.d.ts +14 -0
- package/build/src/git/workspace.js +44 -0
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +16 -16
- package/build/src/kanshi/report.d.ts +2 -2
- package/build/src/library/address.d.ts +9 -3
- package/build/src/library/address.js +3 -2
- package/build/src/library/akuma-creation.d.ts +4 -3
- package/build/src/library/akuma-creation.js +19 -13
- package/build/src/library/audit.d.ts +14 -6
- package/build/src/library/audit.js +13 -23
- package/build/src/library/catalog.d.ts +3 -1
- package/build/src/library/catalog.js +20 -6
- package/build/src/library/composition.d.ts +385 -0
- package/build/src/library/composition.js +28 -0
- package/build/src/library/configuration.js +8 -2
- package/build/src/library/continuation.js +66 -30
- package/build/src/library/contract-bind.js +10 -2
- package/build/src/library/contract-forwarding-reconciliation-result.d.ts +102 -0
- package/build/src/library/contract-forwarding-reconciliation-result.js +92 -0
- package/build/src/library/contract-forwarding-result.d.ts +4464 -0
- package/build/src/library/contract-forwarding-result.js +521 -0
- package/build/src/library/contract-forwarding.d.ts +71 -0
- package/build/src/library/contract-forwarding.js +114 -0
- package/build/src/library/contract-handle.d.ts +38 -0
- package/build/src/library/contract-handle.js +314 -0
- package/build/src/library/contract-operations.d.ts +214 -69
- package/build/src/library/contract-operations.js +189 -118
- package/build/src/library/contract-types.d.ts +7 -3
- package/build/src/library/contract.d.ts +21 -34
- package/build/src/library/contract.js +29 -287
- package/build/src/library/fleet-result.d.ts +1580 -0
- package/build/src/library/fleet-result.js +98 -0
- package/build/src/library/fleet.d.ts +112 -54
- package/build/src/library/fleet.js +211 -71
- package/build/src/library/input.d.ts +3 -1
- package/build/src/library/input.js +11 -1
- package/build/src/library/keiyaku.d.ts +388 -20
- package/build/src/library/keiyaku.js +13 -21
- package/build/src/library/mutation.d.ts +3 -3
- package/build/src/library/mutation.js +5 -9
- package/build/src/library/nuke.d.ts +1 -1
- package/build/src/library/nuke.js +4 -3
- package/build/src/library/reconcile.d.ts +4 -0
- package/build/src/library/reconcile.js +2 -0
- package/build/src/library/refusal.d.ts +0 -9
- package/build/src/library/refusal.js +0 -7
- package/build/src/protocol/amend.js +56 -50
- package/build/src/protocol/attempt.d.ts +2 -0
- package/build/src/protocol/attempt.js +8 -1
- package/build/src/protocol/completion.js +7 -0
- package/build/src/protocol/deliver.js +6 -2
- package/build/src/protocol/operations.d.ts +4 -1
- package/build/src/protocol/operations.js +5 -2
- package/build/src/protocol/placement.d.ts +1 -0
- package/build/src/protocol/placement.js +52 -38
- package/build/src/protocol/read/observation.d.ts +1 -4
- package/build/src/protocol/read/observation.js +2 -15
- package/build/src/protocol/reintegrate.js +62 -58
- package/build/src/protocol/review.js +5 -0
- package/build/src/protocol/run.js +17 -13
- package/build/src/runtime/proc/run.d.ts +9 -0
- package/build/src/runtime/proc/run.js +51 -4
- package/build/src/runtime/proc/termination.js +26 -22
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settlement/holder.d.ts +2 -1
- package/build/src/settlement/holder.js +15 -4
- package/build/src/settlement/settle.js +99 -33
- package/build/src/task/board.d.ts +68 -27
- package/build/src/task/board.js +31 -1
- package/build/src/task/catalog.d.ts +4 -0
- package/build/src/task/catalog.js +23 -0
- package/build/src/task/compose-language.js +33 -7
- package/build/src/task/compose-parser.d.ts +1 -1
- package/build/src/task/compose-parser.js +1 -1
- package/build/src/task/compose.js +2 -0
- package/build/src/task/index.d.ts +12 -4
- package/build/src/task/index.js +97 -16
- package/build/src/task/mutation-result.d.ts +923 -0
- package/build/src/task/mutation-result.js +140 -0
- package/build/src/task/mutation.d.ts +552 -28
- package/build/src/task/mutation.js +304 -97
- package/build/src/task/operations.d.ts +1 -4
- package/build/src/task/operations.js +0 -6
- package/build/src/task/public.d.ts +2 -0
- package/build/src/task/public.js +1 -0
- package/build/src/workspace-place.d.ts +7 -2
- package/build/src/workspace-place.js +34 -24
- package/build/src/world.d.ts +1 -0
- package/build/src/world.js +11 -0
- package/package.json +8 -6
- package/build/src/akuma/request-execution.d.ts +0 -8
- package/build/src/akuma/request-execution.js +0 -98
package/build/src/git/hooks.js
CHANGED
|
@@ -1,169 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { replaceFileDurably } from "../coordination/durable-file.js";
|
|
5
|
-
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
6
|
-
import { runProcess, runProcessToExit } from "../runtime/proc/run.js";
|
|
7
|
-
const MARKER_VERSION = 1;
|
|
8
|
-
function object(value) {
|
|
9
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
-
}
|
|
11
|
-
function exactKeys(value, keys, coordinate) {
|
|
12
|
-
const actual = Object.keys(value).sort();
|
|
13
|
-
const expected = [...keys].sort();
|
|
14
|
-
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
15
|
-
throw new Error(`${coordinate} has invalid fields`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function decodeCommand(value, coordinate) {
|
|
19
|
-
if (!object(value))
|
|
20
|
-
throw new Error(`${coordinate} must be an object`);
|
|
21
|
-
exactKeys(value, ["argv", "timeoutMs"], coordinate);
|
|
22
|
-
if (!Array.isArray(value.argv) || value.argv.length === 0 || !value.argv.every((item) => typeof item === "string")) {
|
|
23
|
-
throw new Error(`${coordinate}.argv must be a nonempty string array`);
|
|
24
|
-
}
|
|
25
|
-
if (value.argv[0].trim().length === 0)
|
|
26
|
-
throw new Error(`${coordinate}.argv[0] must be nonblank`);
|
|
27
|
-
if (!Number.isSafeInteger(value.timeoutMs) ||
|
|
28
|
-
value.timeoutMs < 1 ||
|
|
29
|
-
value.timeoutMs > 2_147_483_647) {
|
|
30
|
-
throw new Error(`${coordinate}.timeoutMs is invalid`);
|
|
31
|
-
}
|
|
32
|
-
return { argv: value.argv, timeoutMs: value.timeoutMs };
|
|
33
|
-
}
|
|
34
|
-
function decodeCommands(value, coordinate) {
|
|
35
|
-
if (!Array.isArray(value))
|
|
36
|
-
throw new Error(`${coordinate} must be an array`);
|
|
37
|
-
return value.map((item, index) => decodeCommand(item, `${coordinate}[${index}]`));
|
|
38
|
-
}
|
|
39
|
-
function decodeFailure(value, coordinate) {
|
|
40
|
-
if (!object(value) || typeof value.kind !== "string")
|
|
41
|
-
throw new Error(`${coordinate} is invalid`);
|
|
42
|
-
switch (value.kind) {
|
|
43
|
-
case "exit":
|
|
44
|
-
exactKeys(value, ["kind", "code", "stdout", "stderr", "truncated"], coordinate);
|
|
45
|
-
if (!Number.isInteger(value.code) ||
|
|
46
|
-
typeof value.stdout !== "string" ||
|
|
47
|
-
typeof value.stderr !== "string" ||
|
|
48
|
-
typeof value.truncated !== "boolean") {
|
|
49
|
-
throw new Error(`${coordinate} is invalid`);
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
kind: "exit",
|
|
53
|
-
code: value.code,
|
|
54
|
-
stdout: value.stdout,
|
|
55
|
-
stderr: value.stderr,
|
|
56
|
-
truncated: value.truncated,
|
|
57
|
-
};
|
|
58
|
-
case "timeout":
|
|
59
|
-
case "unknown-exit":
|
|
60
|
-
exactKeys(value, ["kind"], coordinate);
|
|
61
|
-
return { kind: value.kind };
|
|
62
|
-
case "spawn-error":
|
|
63
|
-
exactKeys(value, ["kind", "diagnostic"], coordinate);
|
|
64
|
-
if (typeof value.diagnostic !== "string")
|
|
65
|
-
throw new Error(`${coordinate}.diagnostic must be a string`);
|
|
66
|
-
return { kind: "spawn-error", diagnostic: value.diagnostic };
|
|
67
|
-
default:
|
|
68
|
-
throw new Error(`${coordinate}.kind is invalid`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
function decodeProgress(value, commands, coordinate) {
|
|
72
|
-
if (!object(value) || typeof value.status !== "string")
|
|
73
|
-
throw new Error(`${coordinate} is invalid`);
|
|
74
|
-
if (value.status === "ok") {
|
|
75
|
-
exactKeys(value, ["status"], coordinate);
|
|
76
|
-
return { status: "ok" };
|
|
77
|
-
}
|
|
78
|
-
if (value.status === "pending") {
|
|
79
|
-
exactKeys(value, ["status", "next"], coordinate);
|
|
80
|
-
if (!Number.isInteger(value.next) || value.next < 0 || value.next > commands) {
|
|
81
|
-
throw new Error(`${coordinate}.next is invalid`);
|
|
82
|
-
}
|
|
83
|
-
return { status: "pending", next: value.next };
|
|
84
|
-
}
|
|
85
|
-
if (value.status === "failed") {
|
|
86
|
-
exactKeys(value, ["status", "command", "failure"], coordinate);
|
|
87
|
-
if (!Number.isInteger(value.command) || value.command < 0 || value.command >= commands) {
|
|
88
|
-
throw new Error(`${coordinate}.command is invalid`);
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
status: "failed",
|
|
92
|
-
command: value.command,
|
|
93
|
-
failure: decodeFailure(value.failure, `${coordinate}.failure`),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`${coordinate}.status is invalid`);
|
|
97
|
-
}
|
|
98
|
-
function decodeMarker(bytes) {
|
|
99
|
-
const value = JSON.parse(bytes);
|
|
100
|
-
if (!object(value))
|
|
101
|
-
throw new Error("worktree hook marker must be an object");
|
|
102
|
-
exactKeys(value, ["version", "commands", "create", "destroy"], "worktree hook marker");
|
|
103
|
-
if (value.version !== MARKER_VERSION)
|
|
104
|
-
throw new Error(`worktree hook marker version must be ${MARKER_VERSION}`);
|
|
105
|
-
if (!object(value.commands))
|
|
106
|
-
throw new Error("worktree hook marker commands must be an object");
|
|
107
|
-
exactKeys(value.commands, ["create", "destroy"], "worktree hook marker commands");
|
|
108
|
-
const commands = {
|
|
109
|
-
create: decodeCommands(value.commands.create, "worktree hook marker commands.create"),
|
|
110
|
-
destroy: decodeCommands(value.commands.destroy, "worktree hook marker commands.destroy"),
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
version: MARKER_VERSION,
|
|
114
|
-
commands,
|
|
115
|
-
create: decodeProgress(value.create, commands.create.length, "worktree hook marker create"),
|
|
116
|
-
destroy: decodeProgress(value.destroy, commands.destroy.length, "worktree hook marker destroy"),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
export function hookMarkerPath(administrationDirectory) {
|
|
120
|
-
return join(administrationDirectory, "keiyaku", "hooks.json");
|
|
121
|
-
}
|
|
122
|
-
async function readMarker(administrationDirectory) {
|
|
123
|
-
try {
|
|
124
|
-
return decodeMarker(await readFile(hookMarkerPath(administrationDirectory), "utf8"));
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
if (error.code === "ENOENT")
|
|
128
|
-
return null;
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function hookFailureDiagnostic(phase, progress) {
|
|
133
|
-
const failure = progress.failure;
|
|
134
|
-
const detail = failure.kind === "exit"
|
|
135
|
-
? `exit=${failure.code}`
|
|
136
|
-
: failure.kind === "spawn-error"
|
|
137
|
-
? failure.diagnostic
|
|
138
|
-
: failure.kind;
|
|
139
|
-
return `worktree-hook-failed ${phase} command=${progress.command} ${detail}`;
|
|
140
|
-
}
|
|
141
|
-
/** Read the durable hook marker without executing commands or taking locks. */
|
|
142
|
-
export async function observeWorktreeHookMarker(administrationDirectory) {
|
|
143
|
-
const marker = await readMarker(administrationDirectory);
|
|
144
|
-
if (marker === null)
|
|
145
|
-
return { kind: "absent" };
|
|
146
|
-
if (marker.create.status === "failed")
|
|
147
|
-
return { kind: "failed", diagnostic: hookFailureDiagnostic("create", marker.create) };
|
|
148
|
-
if (marker.destroy.status === "failed")
|
|
149
|
-
return { kind: "failed", diagnostic: hookFailureDiagnostic("destroy", marker.destroy) };
|
|
150
|
-
if (marker.create.status === "pending" || marker.destroy.status === "pending")
|
|
151
|
-
return { kind: "pending" };
|
|
152
|
-
return { kind: "ok" };
|
|
153
|
-
}
|
|
154
|
-
async function writeMarker(administrationDirectory, marker) {
|
|
155
|
-
const path = hookMarkerPath(administrationDirectory);
|
|
156
|
-
await mkdir(dirname(path), { recursive: true });
|
|
157
|
-
await replaceFileDurably(path, `${JSON.stringify(marker)}\n`);
|
|
158
|
-
}
|
|
159
|
-
function freshMarker(commands, createPending) {
|
|
160
|
-
return {
|
|
161
|
-
version: MARKER_VERSION,
|
|
162
|
-
commands,
|
|
163
|
-
create: createPending ? { status: "pending", next: 0 } : { status: "ok" },
|
|
164
|
-
destroy: { status: "pending", next: 0 },
|
|
165
|
-
};
|
|
166
|
-
}
|
|
1
|
+
import { runProcess } from "../runtime/proc/run.js";
|
|
167
2
|
function failedOutcome(outcome) {
|
|
168
3
|
if (outcome.kind === "terminal") {
|
|
169
4
|
return outcome.code === 0
|
|
@@ -195,162 +30,33 @@ export async function runHookCommands(worktree, commands, signal) {
|
|
|
195
30
|
}
|
|
196
31
|
return { kind: "ok" };
|
|
197
32
|
}
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const value = await lstat(path);
|
|
207
|
-
if (!value.isFile() || value.isSymbolicLink())
|
|
208
|
-
throw new Error(`worktree hook custody is not a regular file: ${path}`);
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
if (error.code === "ENOENT")
|
|
213
|
-
return false;
|
|
214
|
-
throw error;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/** Remove validated Keiyaku hook state after its worktree has been reset. */
|
|
218
|
-
export async function nukeWorktreeHookResidue(administrationDirectory) {
|
|
219
|
-
const marker = hookMarkerPath(administrationDirectory);
|
|
220
|
-
const lock = hookExecutionLockPath(administrationDirectory);
|
|
221
|
-
const markerPresent = await regularFile(marker);
|
|
222
|
-
const lockPresent = await regularFile(lock);
|
|
223
|
-
if (!markerPresent && !lockPresent)
|
|
224
|
-
return;
|
|
225
|
-
const held = await acquireSqliteTransactionLock({ path: lock, mode: "immediate" });
|
|
226
|
-
try {
|
|
227
|
-
const currentMarker = await regularFile(marker);
|
|
228
|
-
if (currentMarker)
|
|
229
|
-
await readMarker(administrationDirectory);
|
|
230
|
-
if (currentMarker)
|
|
231
|
-
await unlink(marker);
|
|
232
|
-
}
|
|
233
|
-
finally {
|
|
234
|
-
held.close();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
function decodeRunnerInput(encoded) {
|
|
238
|
-
const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
239
|
-
if (!object(value))
|
|
240
|
-
throw new Error("Hook runner input must be an object");
|
|
241
|
-
exactKeys(value, ["administrationDirectory", "worktree", "phase", "command"], "Hook runner input");
|
|
242
|
-
if (typeof value.administrationDirectory !== "string" || value.administrationDirectory.length === 0) {
|
|
243
|
-
throw new Error("Hook runner administrationDirectory must be nonempty");
|
|
244
|
-
}
|
|
245
|
-
if (typeof value.worktree !== "string" || value.worktree.length === 0) {
|
|
246
|
-
throw new Error("Hook runner worktree must be nonempty");
|
|
247
|
-
}
|
|
248
|
-
if (value.phase !== "create" && value.phase !== "destroy")
|
|
249
|
-
throw new Error("Hook runner phase is invalid");
|
|
250
|
-
if (!Number.isSafeInteger(value.command) || value.command < 0) {
|
|
251
|
-
throw new Error("Hook runner command must be a nonnegative safe integer");
|
|
252
|
-
}
|
|
253
|
-
return {
|
|
254
|
-
administrationDirectory: value.administrationDirectory,
|
|
255
|
-
worktree: value.worktree,
|
|
256
|
-
phase: value.phase,
|
|
257
|
-
command: value.command,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
async function executePendingCommand(input) {
|
|
261
|
-
const held = await acquireSqliteTransactionLock({
|
|
262
|
-
path: hookExecutionLockPath(input.administrationDirectory),
|
|
263
|
-
mode: "immediate",
|
|
264
|
-
});
|
|
265
|
-
try {
|
|
266
|
-
const marker = await readMarker(input.administrationDirectory);
|
|
267
|
-
if (marker === null)
|
|
268
|
-
throw new Error("worktree hook marker disappeared before execution");
|
|
269
|
-
const progress = marker[input.phase];
|
|
270
|
-
if (progress.status !== "pending" || progress.next !== input.command)
|
|
271
|
-
return;
|
|
272
|
-
const command = marker.commands[input.phase][input.command];
|
|
273
|
-
if (command === undefined)
|
|
274
|
-
throw new Error("worktree hook marker pending index is out of range");
|
|
275
|
-
const result = await runHookCommands(input.worktree, [command]);
|
|
276
|
-
if (result.kind === "cancelled")
|
|
277
|
-
throw new Error("managed hook runner cancelled without a signal");
|
|
278
|
-
const failure = result.kind === "failed" ? result.failure : null;
|
|
279
|
-
const next = input.command + 1;
|
|
280
|
-
await writeMarker(input.administrationDirectory, withProgress(marker, input.phase, failure === null
|
|
281
|
-
? next === marker.commands[input.phase].length
|
|
282
|
-
? { status: "ok" }
|
|
283
|
-
: { status: "pending", next }
|
|
284
|
-
: { status: "failed", command: input.command, failure }));
|
|
285
|
-
}
|
|
286
|
-
finally {
|
|
287
|
-
held.close();
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
function runnerFailure(outcome) {
|
|
291
|
-
if (outcome.kind === "terminal") {
|
|
292
|
-
const detail = outcome.stderr.trim() || outcome.stdout.trim();
|
|
293
|
-
return new Error(detail.length === 0 ? `Hook runner exited ${outcome.code}` : `Hook runner exited ${outcome.code}: ${detail}`);
|
|
294
|
-
}
|
|
295
|
-
if (outcome.kind === "spawn-error")
|
|
296
|
-
return new Error(`Hook runner failed to spawn: ${outcome.diagnostic}`);
|
|
297
|
-
return new Error(`Hook runner ended with ${outcome.kind}`);
|
|
298
|
-
}
|
|
299
|
-
async function runPendingCommand(input) {
|
|
300
|
-
const encoded = Buffer.from(JSON.stringify(input), "utf8").toString("base64url");
|
|
301
|
-
const outcome = await runProcessToExit({
|
|
302
|
-
argv: [process.execPath, ...process.execArgv, fileURLToPath(import.meta.url), "--run-hook", encoded],
|
|
303
|
-
cwd: dirname(fileURLToPath(import.meta.url)),
|
|
304
|
-
});
|
|
305
|
-
if (outcome.kind !== "terminal" || outcome.code !== 0)
|
|
306
|
-
throw runnerFailure(outcome);
|
|
307
|
-
}
|
|
308
|
-
async function runPhase(worktree, administrationDirectory, hooks, phase, retryHooks) {
|
|
309
|
-
let marker = await readMarker(administrationDirectory);
|
|
310
|
-
if (marker === null) {
|
|
311
|
-
marker = freshMarker(hooks, phase === "create");
|
|
312
|
-
await writeMarker(administrationDirectory, marker);
|
|
313
|
-
}
|
|
314
|
-
let retryFailure = retryHooks && marker[phase].status === "failed";
|
|
315
|
-
for (;;) {
|
|
316
|
-
marker = await readMarker(administrationDirectory);
|
|
317
|
-
if (marker === null)
|
|
318
|
-
throw new Error("worktree hook marker disappeared during execution");
|
|
319
|
-
const progress = marker[phase];
|
|
320
|
-
if (progress.status === "ok")
|
|
321
|
-
return null;
|
|
322
|
-
if (progress.status === "failed") {
|
|
323
|
-
if (!retryFailure) {
|
|
324
|
-
return {
|
|
33
|
+
async function runPhase(worktree, hooks, phase) {
|
|
34
|
+
const runs = [];
|
|
35
|
+
for (const [command, value] of hooks.entries()) {
|
|
36
|
+
const outcome = await runProcess({ argv: value.argv, timeoutMs: value.timeoutMs, cwd: worktree });
|
|
37
|
+
if (outcome.kind === "cancelled") {
|
|
38
|
+
return {
|
|
39
|
+
runs,
|
|
40
|
+
lag: {
|
|
325
41
|
kind: "worktree-hook-failed",
|
|
326
42
|
phase,
|
|
327
43
|
path: worktree,
|
|
328
|
-
command
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
await writeMarker(administrationDirectory, withProgress(marker, phase, { status: "pending", next: progress.command }));
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (progress.next === marker.commands[phase].length) {
|
|
337
|
-
await writeMarker(administrationDirectory, withProgress(marker, phase, { status: "ok" }));
|
|
338
|
-
continue;
|
|
44
|
+
command,
|
|
45
|
+
name: value.name,
|
|
46
|
+
failure: { kind: "unknown-exit" },
|
|
47
|
+
},
|
|
48
|
+
};
|
|
339
49
|
}
|
|
340
|
-
|
|
50
|
+
const failure = failedOutcome(outcome);
|
|
51
|
+
if (failure !== null)
|
|
52
|
+
return { runs, lag: { kind: "worktree-hook-failed", phase, path: worktree, command, name: value.name, failure } };
|
|
53
|
+
runs.push(value.name);
|
|
341
54
|
}
|
|
55
|
+
return { runs, lag: null };
|
|
342
56
|
}
|
|
343
|
-
export function runCreateHooks(worktree,
|
|
344
|
-
return runPhase(worktree,
|
|
345
|
-
}
|
|
346
|
-
export function runDestroyHooks(worktree, administrationDirectory, hooks, retryHooks) {
|
|
347
|
-
return runPhase(worktree, administrationDirectory, hooks, "destroy", retryHooks);
|
|
57
|
+
export function runCreateHooks(worktree, hooks) {
|
|
58
|
+
return runPhase(worktree, hooks.create, "create");
|
|
348
59
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
process.argv[2] === "--run-hook") {
|
|
352
|
-
const encoded = process.argv[3];
|
|
353
|
-
if (encoded === undefined)
|
|
354
|
-
throw new Error("Hook runner input is missing");
|
|
355
|
-
await executePendingCommand(decodeRunnerInput(encoded));
|
|
60
|
+
export function runDestroyHooks(worktree, hooks) {
|
|
61
|
+
return runPhase(worktree, hooks.destroy, "destroy");
|
|
356
62
|
}
|
package/build/src/git/nuke.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { WorldRoot } from "../world.js";
|
|
2
|
-
|
|
2
|
+
import { type GitRepository } from "./process.js";
|
|
3
|
+
export declare function nukeGit(world: WorldRoot, gitPath?: string, options?: Readonly<Pick<GitRepository, "onPrivateStateSeatContention">>): Promise<void>;
|
package/build/src/git/nuke.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { access, lstat, realpath, rm } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, relative, sep } from "node:path";
|
|
3
|
-
import { nukeEmptyPlaceAuthority, readPlaceRegister, releaseManagedWorktrees } from "../workspace-place.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { CANDIDATE_PIN_REF_NAMESPACE, DELIVERY_REF_NAMESPACE, GIT_REF,
|
|
3
|
+
import { nukeEmptyPlaceAuthority, readPlaceRegister, releaseManagedWorktrees, withPlaceAuthorityFence, } from "../workspace-place.js";
|
|
4
|
+
import { GitPlumbingError, runGit } from "./process.js";
|
|
5
|
+
import { confirmPrivateStatePublication, withPrivateStatePublicationSeat } from "./private-state-seat.js";
|
|
6
|
+
import { CANDIDATE_PIN_REF_NAMESPACE, DELIVERY_REF_NAMESPACE, GIT_REF, LEGACY_CANDIDATE_PIN_REF_NAMESPACE, LEGACY_DELIVERY_REF_NAMESPACE, NoGitWorldError, readRef, registeredWorktrees, repositoryAt, worktreeGitDirectory, } from "./repository.js";
|
|
7
7
|
import { worktreePath } from "./workspace.js";
|
|
8
8
|
async function managedCustody(repository) {
|
|
9
9
|
const register = await readPlaceRegister(repository);
|
|
@@ -66,8 +66,6 @@ async function removeUnregisteredResidue(repository, entry) {
|
|
|
66
66
|
if (!(await unregisteredResidueBelongsToRepository(repository, entry.path))) {
|
|
67
67
|
throw new Error(`managed Place path has foreign custody: ${entry.path}`);
|
|
68
68
|
}
|
|
69
|
-
const administrationDirectory = await worktreeGitDirectory(repository, entry.path);
|
|
70
|
-
await nukeWorktreeHookResidue(administrationDirectory);
|
|
71
69
|
await rm(entry.path, { recursive: true, force: true });
|
|
72
70
|
try {
|
|
73
71
|
await access(entry.path);
|
|
@@ -85,7 +83,6 @@ async function removeManagedWorktree(repository, entry) {
|
|
|
85
83
|
return await removeUnregisteredResidue(repository, entry);
|
|
86
84
|
if (registered.branch !== null)
|
|
87
85
|
return false;
|
|
88
|
-
await nukeWorktreeHookResidue(await worktreeGitDirectory(repository, entry.path));
|
|
89
86
|
await runGit(repository, ["worktree", "remove", "--force", entry.path]);
|
|
90
87
|
if ((await registeredWorktrees(repository)).some((candidate) => candidate.path === entry.path)) {
|
|
91
88
|
throw new Error(`managed Place worktree remains registered: ${entry.path}`);
|
|
@@ -103,12 +100,28 @@ async function removeManagedWorktree(repository, entry) {
|
|
|
103
100
|
async function deleteRefAt(repository, ref, expectedOid) {
|
|
104
101
|
await runGit(repository, ["update-ref", "--no-deref", "-d", ref, expectedOid]);
|
|
105
102
|
}
|
|
103
|
+
async function deleteObservedStateRef(repository, expectedOid, seat) {
|
|
104
|
+
try {
|
|
105
|
+
await deleteRefAt(repository, GIT_REF, expectedOid);
|
|
106
|
+
confirmPrivateStatePublication(seat);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (!(error instanceof GitPlumbingError) || error.pid === null || error.pid <= 0 || error.status !== null)
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
if ((await readRef(repository, GIT_REF)) === null) {
|
|
114
|
+
confirmPrivateStatePublication(seat);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
throw new Error("state-ref deletion outcome is unknown");
|
|
118
|
+
}
|
|
106
119
|
async function removeOwnedRefs(repository) {
|
|
107
120
|
const roots = [
|
|
108
121
|
DELIVERY_REF_NAMESPACE,
|
|
109
122
|
CANDIDATE_PIN_REF_NAMESPACE,
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
LEGACY_DELIVERY_REF_NAMESPACE,
|
|
124
|
+
LEGACY_CANDIDATE_PIN_REF_NAMESPACE,
|
|
112
125
|
];
|
|
113
126
|
for (const root of roots) {
|
|
114
127
|
const refs = (await runGit(repository, ["for-each-ref", "--format=%(refname)", root]))
|
|
@@ -122,24 +135,29 @@ async function removeOwnedRefs(repository) {
|
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
|
-
export async function nukeGit(world, gitPath = "git") {
|
|
138
|
+
export async function nukeGit(world, gitPath = "git", options) {
|
|
126
139
|
let repository;
|
|
127
140
|
try {
|
|
128
|
-
repository = await repositoryAt(world, gitPath);
|
|
141
|
+
repository = { ...(await repositoryAt(world, gitPath)), ...options };
|
|
129
142
|
}
|
|
130
143
|
catch (error) {
|
|
131
144
|
if (error instanceof NoGitWorldError)
|
|
132
145
|
return;
|
|
133
146
|
throw error;
|
|
134
147
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
await
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
await withPlaceAuthorityFence(repository, async (placeFence) => {
|
|
149
|
+
const custody = await managedCustody(repository);
|
|
150
|
+
await withPrivateStatePublicationSeat(repository, async (seat) => {
|
|
151
|
+
const state = await readRef(repository, GIT_REF);
|
|
152
|
+
if (state !== null)
|
|
153
|
+
await deleteObservedStateRef(repository, state, seat);
|
|
154
|
+
for (const entry of custody.entries) {
|
|
155
|
+
if (await removeManagedWorktree(repository, entry)) {
|
|
156
|
+
await releaseManagedWorktrees(repository, [entry.contract], placeFence);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await nukeEmptyPlaceAuthority(repository, placeFence);
|
|
160
|
+
await removeOwnedRefs(repository);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
145
163
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { GitRepository } from "./process.js";
|
|
2
|
+
declare const privateStateSeat: unique symbol;
|
|
3
|
+
/** Proves an admission's private-root observation was made while the shared seat was held. */
|
|
4
|
+
export type PrivateStatePublicationSeat = Readonly<{
|
|
5
|
+
readonly [privateStateSeat]: true;
|
|
6
|
+
}>;
|
|
7
|
+
/** Record a known or exact-read-back-proven private-root publication. */
|
|
8
|
+
export declare function confirmPrivateStatePublication(seat: PrivateStatePublicationSeat): void;
|
|
9
|
+
export declare function privateStatePublicationSeatPath(repository: GitRepository): string;
|
|
10
|
+
/** Serialize cooperating writers of the one private Git state root. */
|
|
11
|
+
export declare function withPrivateStatePublicationSeat<T>(repository: GitRepository, action: (seat: PrivateStatePublicationSeat) => T | Promise<T>): Promise<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
3
|
+
const PRIVATE_STATE_SEAT = "private-state.sqlite";
|
|
4
|
+
const privateStateSeat = Symbol("private-state-seat");
|
|
5
|
+
const confirmedPublications = new WeakSet();
|
|
6
|
+
/** Record a known or exact-read-back-proven private-root publication. */
|
|
7
|
+
export function confirmPrivateStatePublication(seat) {
|
|
8
|
+
confirmedPublications.add(seat);
|
|
9
|
+
}
|
|
10
|
+
export function privateStatePublicationSeatPath(repository) {
|
|
11
|
+
return join(repository.commonDirectory, "keiyaku", "locks", PRIVATE_STATE_SEAT);
|
|
12
|
+
}
|
|
13
|
+
/** Serialize cooperating writers of the one private Git state root. */
|
|
14
|
+
export async function withPrivateStatePublicationSeat(repository, action) {
|
|
15
|
+
const held = await acquireSqliteTransactionLock({
|
|
16
|
+
path: privateStatePublicationSeatPath(repository),
|
|
17
|
+
mode: "immediate",
|
|
18
|
+
...(repository.signal === undefined ? {} : { signal: repository.signal }),
|
|
19
|
+
...(repository.onPrivateStateSeatContention === undefined
|
|
20
|
+
? {}
|
|
21
|
+
: { onContended: repository.onPrivateStateSeatContention }),
|
|
22
|
+
});
|
|
23
|
+
const seat = { [privateStateSeat]: true };
|
|
24
|
+
let completed = false;
|
|
25
|
+
try {
|
|
26
|
+
const result = await action(seat);
|
|
27
|
+
completed = true;
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
try {
|
|
32
|
+
held.close();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!(completed && confirmedPublications.has(seat)))
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -9,7 +9,13 @@ export type GitRepository = Readonly<{
|
|
|
9
9
|
readonly primaryWorktree: string;
|
|
10
10
|
/** The canonical common Git directory pinned when this capability is created. */
|
|
11
11
|
readonly commonDirectory: string;
|
|
12
|
+
/** A caller-owned cancellation boundary for this capability view. */
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
/** Test-only observation that this capability is waiting for the private-state seat. */
|
|
15
|
+
readonly onPrivateStateSeatContention?: () => void;
|
|
12
16
|
}>;
|
|
17
|
+
/** Give one caller's Git capability view its cancellation boundary. */
|
|
18
|
+
export declare function withGitAbortSignal(repository: GitRepository, signal: AbortSignal | undefined): GitRepository;
|
|
13
19
|
export declare class GitPlumbingError extends Error {
|
|
14
20
|
readonly stdout: Buffer;
|
|
15
21
|
readonly stderr: Buffer;
|
package/build/src/git/process.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { consumeProcessStdout, spawnCancellableProcess } from "../runtime/proc/run.js";
|
|
2
|
+
/** Give one caller's Git capability view its cancellation boundary. */
|
|
3
|
+
export function withGitAbortSignal(repository, signal) {
|
|
4
|
+
return signal === undefined ? repository : { ...repository, signal };
|
|
5
|
+
}
|
|
3
6
|
export class GitPlumbingError extends Error {
|
|
4
7
|
stdout;
|
|
5
8
|
stderr;
|
|
@@ -29,16 +32,20 @@ function commandError(command, error) {
|
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
async function executeGit(repository, args, input, environment) {
|
|
35
|
+
if (repository.signal?.aborted === true) {
|
|
36
|
+
throw commandError(args, { message: "git process cancelled", status: null });
|
|
37
|
+
}
|
|
32
38
|
const stdout = [];
|
|
33
39
|
const stderr = [];
|
|
34
40
|
let stderrBytes = 0;
|
|
35
41
|
let inputError;
|
|
36
|
-
const
|
|
42
|
+
const owned = spawnCancellableProcess({
|
|
43
|
+
argv: [repository.gitPath, ...args],
|
|
37
44
|
cwd: repository.effectiveCwd,
|
|
38
45
|
...(environment === undefined ? {} : { env: { ...process.env, ...environment } }),
|
|
39
|
-
|
|
40
|
-
windowsHide: true,
|
|
46
|
+
...(repository.signal === undefined ? {} : { signal: repository.signal }),
|
|
41
47
|
});
|
|
48
|
+
const child = owned.child;
|
|
42
49
|
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
43
50
|
child.stderr.on("data", (chunk) => {
|
|
44
51
|
if (stderrBytes >= GIT_STDERR_BYTES)
|
|
@@ -51,15 +58,22 @@ async function executeGit(repository, args, input, environment) {
|
|
|
51
58
|
inputError = error;
|
|
52
59
|
});
|
|
53
60
|
child.stdin.end(input);
|
|
54
|
-
const terminal = await
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
const terminal = await Promise.race([
|
|
62
|
+
new Promise((resolveTerminal) => {
|
|
63
|
+
child.once("error", (error) => resolveTerminal({ code: null, error }));
|
|
64
|
+
child.once("close", (code) => resolveTerminal({ code }));
|
|
65
|
+
}),
|
|
66
|
+
owned.terminationFailure,
|
|
67
|
+
]);
|
|
68
|
+
await owned.waitTermination();
|
|
58
69
|
const output = Buffer.concat(stdout);
|
|
59
|
-
if (terminal.code === 0 && inputError === undefined)
|
|
70
|
+
if (!owned.cancelled() && terminal.code === 0 && inputError === undefined)
|
|
60
71
|
return output;
|
|
61
72
|
throw commandError(args, {
|
|
62
|
-
message:
|
|
73
|
+
message: (owned.cancelled() ? "git process cancelled" : undefined) ??
|
|
74
|
+
terminal.error?.message ??
|
|
75
|
+
inputError?.message ??
|
|
76
|
+
`git exited with status ${terminal.code ?? "unknown"}`,
|
|
63
77
|
stdout: output,
|
|
64
78
|
stderr: Buffer.concat(stderr),
|
|
65
79
|
status: terminal.code,
|
|
@@ -73,6 +87,7 @@ export async function consumeGitStdout(repository, args, consume) {
|
|
|
73
87
|
const result = await consumeProcessStdout({
|
|
74
88
|
argv: [repository.gitPath, ...args],
|
|
75
89
|
cwd: repository.effectiveCwd,
|
|
90
|
+
...(repository.signal === undefined ? {} : { signal: repository.signal }),
|
|
76
91
|
}, consume);
|
|
77
92
|
const { outcome } = result;
|
|
78
93
|
if (outcome.kind === "terminal" && outcome.code === 0)
|