@astrosheep/keiyaku 4.0.0 → 4.0.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/README.md +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { terminateProcessTree } from "./run.js";
|
|
1
|
+
import { spawnStdioProcess } from "./stdio.js";
|
|
3
2
|
function object(value) {
|
|
4
3
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
5
4
|
? value
|
|
@@ -9,37 +8,25 @@ function text(value) {
|
|
|
9
8
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
10
9
|
}
|
|
11
10
|
export class LineRpcProcess {
|
|
12
|
-
|
|
11
|
+
process;
|
|
13
12
|
pending = new Map();
|
|
14
13
|
notifications = new Set();
|
|
15
14
|
serverRequests = new Set();
|
|
16
|
-
exited;
|
|
17
15
|
nextId = 1;
|
|
18
16
|
stdout = "";
|
|
19
|
-
stderr = "";
|
|
20
17
|
closed = false;
|
|
21
18
|
constructor(input) {
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
27
|
-
windowsHide: true,
|
|
28
|
-
});
|
|
29
|
-
this.child.stdout.setEncoding("utf8");
|
|
30
|
-
this.child.stderr.setEncoding("utf8");
|
|
31
|
-
this.child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
32
|
-
this.child.stderr.on("data", (chunk) => { this.stderr = `${this.stderr}${chunk}`.slice(-4_000); });
|
|
33
|
-
this.child.on("error", (error) => this.fail(error));
|
|
34
|
-
this.exited = new Promise((resolve) => this.child.once("close", (code, signal) => {
|
|
19
|
+
this.process = spawnStdioProcess(input);
|
|
20
|
+
this.process.output.setEncoding("utf8");
|
|
21
|
+
this.process.output.on("data", (chunk) => this.consume(chunk));
|
|
22
|
+
void this.process.exited.then((exit) => {
|
|
35
23
|
this.closed = true;
|
|
36
|
-
this.fail(new Error(`line RPC process exited${code === null ? "" : ` with code ${code}`}`));
|
|
37
|
-
|
|
38
|
-
}));
|
|
24
|
+
this.fail(new Error(`line RPC process exited${exit.code === null ? "" : ` with code ${exit.code}`}`));
|
|
25
|
+
});
|
|
39
26
|
}
|
|
40
27
|
onNotification(listener) { this.notifications.add(listener); }
|
|
41
28
|
onServerRequest(listener) { this.serverRequests.add(listener); }
|
|
42
|
-
onExit(listener) { void this.exited.then(listener); }
|
|
29
|
+
onExit(listener) { void this.process.exited.then(listener); }
|
|
43
30
|
consume(chunk) {
|
|
44
31
|
this.stdout += chunk;
|
|
45
32
|
for (;;) {
|
|
@@ -98,7 +85,7 @@ export class LineRpcProcess {
|
|
|
98
85
|
const id = this.nextId++;
|
|
99
86
|
return new Promise((resolve, reject) => {
|
|
100
87
|
this.pending.set(id, { resolve, reject });
|
|
101
|
-
this.
|
|
88
|
+
this.process.input.write(`${JSON.stringify({ id, method, ...(params === undefined ? {} : { params }) })}\n`, (error) => {
|
|
102
89
|
if (error === null || error === undefined)
|
|
103
90
|
return;
|
|
104
91
|
this.pending.delete(id);
|
|
@@ -108,17 +95,24 @@ export class LineRpcProcess {
|
|
|
108
95
|
}
|
|
109
96
|
notify(method) {
|
|
110
97
|
if (!this.closed)
|
|
111
|
-
this.
|
|
98
|
+
this.process.input.write(`${JSON.stringify({ method })}\n`);
|
|
99
|
+
}
|
|
100
|
+
async endInputAndDrain(timeoutMs) {
|
|
101
|
+
if (this.closed) {
|
|
102
|
+
await this.process.exited;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.closed = true;
|
|
106
|
+
this.fail(new Error("line RPC process is closed"));
|
|
107
|
+
await this.process.endInputAndDrain(timeoutMs);
|
|
112
108
|
}
|
|
113
109
|
async close(force = false) {
|
|
114
110
|
if (this.closed) {
|
|
115
|
-
await this.exited;
|
|
111
|
+
await this.process.exited;
|
|
116
112
|
return;
|
|
117
113
|
}
|
|
118
114
|
this.closed = true;
|
|
119
115
|
this.fail(new Error("line RPC process is closed"));
|
|
120
|
-
this.
|
|
121
|
-
await terminateProcessTree(this.child.pid, force);
|
|
122
|
-
await this.exited;
|
|
116
|
+
await this.process.close(force);
|
|
123
117
|
}
|
|
124
118
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
1
2
|
type ProcessLaunch = Readonly<{
|
|
2
3
|
readonly argv: readonly string[];
|
|
3
4
|
readonly cwd?: string;
|
|
4
5
|
readonly env?: NodeJS.ProcessEnv;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
5
7
|
}>;
|
|
6
8
|
export type ProcessInput = ProcessLaunch & Readonly<{
|
|
7
|
-
readonly timeoutMs
|
|
9
|
+
readonly timeoutMs?: number;
|
|
8
10
|
}>;
|
|
9
11
|
type ProcessTerminal = Readonly<{
|
|
10
12
|
readonly kind: "terminal";
|
|
@@ -23,32 +25,32 @@ type ProcessSpawnError = Readonly<{
|
|
|
23
25
|
type ProcessUnknownExit = Readonly<{
|
|
24
26
|
readonly kind: "unknown-exit";
|
|
25
27
|
}>;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
diagnostic: string;
|
|
39
|
-
}>;
|
|
40
|
-
export type PutDownEvidence = "killed" | "already-dead" | "alive-after-sigkill" | "unavailable";
|
|
28
|
+
type ProcessCancelled = Readonly<{
|
|
29
|
+
readonly kind: "cancelled";
|
|
30
|
+
}>;
|
|
31
|
+
export type ProcessOutcome = ProcessTerminal | ProcessTimeout | ProcessSpawnError | ProcessUnknownExit | ProcessCancelled;
|
|
32
|
+
type ProcessStreamError = Readonly<{
|
|
33
|
+
readonly kind: "stream-error";
|
|
34
|
+
readonly diagnostic: string;
|
|
35
|
+
}>;
|
|
36
|
+
export type ProcessConsumption = Readonly<{
|
|
37
|
+
readonly outcome: ProcessOutcome | ProcessStreamError;
|
|
38
|
+
readonly pid: number | null;
|
|
39
|
+
}>;
|
|
41
40
|
export type DetachedProcessInput = Readonly<{
|
|
42
41
|
argv: readonly string[];
|
|
43
42
|
cwd: string;
|
|
44
43
|
env?: NodeJS.ProcessEnv;
|
|
45
44
|
log: string;
|
|
46
45
|
}>;
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
export type OwnedProcess = Readonly<{
|
|
47
|
+
pid: number;
|
|
48
|
+
terminate(force?: boolean): Promise<void>;
|
|
49
|
+
release(): void;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function terminateOwnedProcess(child: ChildProcess, force?: boolean): Promise<void>;
|
|
52
|
+
export declare function spawnDetachedProcess(input: DetachedProcessInput): Promise<OwnedProcess>;
|
|
52
53
|
export declare function runProcess(input: ProcessInput): Promise<ProcessOutcome>;
|
|
53
54
|
export declare function runProcessToExit(input: ProcessLaunch): Promise<ProcessOutcome>;
|
|
55
|
+
export declare function consumeProcessStdout(input: ProcessInput, consume: (chunk: Buffer) => void): Promise<ProcessConsumption>;
|
|
54
56
|
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import { promisify } from "node:util";
|
|
3
5
|
const TERMINATION_GRACE_MS = 250;
|
|
4
6
|
const STREAM_TAIL_BYTES = 16 * 1024;
|
|
5
7
|
function tailCapture(limit) {
|
|
@@ -31,25 +33,21 @@ function tailCapture(limit) {
|
|
|
31
33
|
},
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
|
-
function wait(milliseconds) {
|
|
35
|
-
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
36
|
-
}
|
|
37
36
|
function ignoreMissingProcess(error) {
|
|
38
37
|
if (error.code !== "ESRCH")
|
|
39
38
|
throw error;
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
taskkill.once("error", resolve);
|
|
48
|
-
taskkill.once("close", resolve);
|
|
40
|
+
const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
|
|
41
|
+
const execFileAsync = promisify(execFile);
|
|
42
|
+
async function terminateWindowsTree(pid) {
|
|
43
|
+
await execFileAsync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
44
|
+
timeout: WINDOWS_TERMINATION_TIMEOUT_MS,
|
|
45
|
+
windowsHide: true,
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
|
-
export async function
|
|
52
|
-
|
|
48
|
+
export async function terminateOwnedProcess(child, force = false) {
|
|
49
|
+
const pid = child.pid;
|
|
50
|
+
if (pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
53
51
|
return;
|
|
54
52
|
if (process.platform === "win32") {
|
|
55
53
|
await terminateWindowsTree(pid);
|
|
@@ -64,6 +62,11 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
64
62
|
}
|
|
65
63
|
return;
|
|
66
64
|
}
|
|
65
|
+
let exited = false;
|
|
66
|
+
const exit = new Promise((resolve) => {
|
|
67
|
+
child.once("exit", () => { exited = true; resolve(); });
|
|
68
|
+
child.once("close", () => { exited = true; resolve(); });
|
|
69
|
+
});
|
|
67
70
|
try {
|
|
68
71
|
process.kill(-pid, "SIGTERM");
|
|
69
72
|
}
|
|
@@ -71,7 +74,9 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
71
74
|
ignoreMissingProcess(error);
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
|
-
await
|
|
77
|
+
await Promise.race([exit, delay(TERMINATION_GRACE_MS)]);
|
|
78
|
+
if (exited || child.exitCode !== null || child.signalCode !== null)
|
|
79
|
+
return;
|
|
75
80
|
try {
|
|
76
81
|
process.kill(-pid, "SIGKILL");
|
|
77
82
|
}
|
|
@@ -79,28 +84,14 @@ export async function terminateProcessTree(pid, force = false) {
|
|
|
79
84
|
ignoreMissingProcess(error);
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
|
-
function processStartToken(pid) {
|
|
83
|
-
const command = process.platform === "win32"
|
|
84
|
-
? ["powershell.exe", "-NoProfile", "-Command", `(Get-CimInstance Win32_Process -Filter \"ProcessId=${pid}\").CreationDate`]
|
|
85
|
-
: ["ps", "-o", "lstart=", "-p", String(pid)];
|
|
86
|
-
const result = spawnSync(command[0], command.slice(1), { encoding: "utf8", windowsHide: true });
|
|
87
|
-
const token = result.status === 0 ? result.stdout.trim() : "";
|
|
88
|
-
return token.length === 0 ? null : token;
|
|
89
|
-
}
|
|
90
|
-
export function currentProcessCollar() {
|
|
91
|
-
const spawnedAt = processStartToken(process.pid);
|
|
92
|
-
if (spawnedAt === null)
|
|
93
|
-
throw new Error(`cannot read process start identity for ${process.pid}`);
|
|
94
|
-
return { pid: process.pid, processGroup: process.pid, spawnedAt };
|
|
95
|
-
}
|
|
96
87
|
export async function spawnDetachedProcess(input) {
|
|
97
|
-
const log =
|
|
88
|
+
const log = await open(input.log, "a");
|
|
98
89
|
try {
|
|
99
90
|
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
100
91
|
cwd: input.cwd,
|
|
101
92
|
env: input.env,
|
|
102
93
|
detached: true,
|
|
103
|
-
stdio: ["ignore", log, log],
|
|
94
|
+
stdio: ["ignore", log.fd, log.fd],
|
|
104
95
|
windowsHide: true,
|
|
105
96
|
});
|
|
106
97
|
await new Promise((resolve, reject) => {
|
|
@@ -109,46 +100,47 @@ export async function spawnDetachedProcess(input) {
|
|
|
109
100
|
});
|
|
110
101
|
if (child.pid === undefined)
|
|
111
102
|
throw new Error("detached process spawned without a pid");
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
const pid = child.pid;
|
|
104
|
+
let state = "active";
|
|
105
|
+
let termination;
|
|
106
|
+
const invalidate = () => { state = "inert"; };
|
|
107
|
+
child.once("exit", invalidate);
|
|
108
|
+
child.once("close", invalidate);
|
|
109
|
+
return {
|
|
110
|
+
pid,
|
|
111
|
+
terminate(force = false) {
|
|
112
|
+
if (state === "inert")
|
|
113
|
+
return Promise.resolve();
|
|
114
|
+
if (termination !== undefined)
|
|
115
|
+
return termination;
|
|
116
|
+
state = "terminating";
|
|
117
|
+
termination = terminateOwnedProcess(child, force).finally(invalidate);
|
|
118
|
+
return termination;
|
|
119
|
+
},
|
|
120
|
+
release: () => {
|
|
121
|
+
if (state === "inert")
|
|
122
|
+
return;
|
|
123
|
+
state = "inert";
|
|
124
|
+
child.unref();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
117
127
|
}
|
|
118
128
|
finally {
|
|
119
|
-
|
|
129
|
+
await log.close();
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return { kind: "unverifiable", diagnostic: error instanceof Error ? error.message : String(error) };
|
|
131
|
-
}
|
|
132
|
-
const current = processStartToken(collar.pid);
|
|
133
|
-
if (current === null && process.platform !== "win32")
|
|
134
|
-
return { kind: "alive" };
|
|
135
|
-
if (current === collar.spawnedAt)
|
|
136
|
-
return { kind: "alive" };
|
|
137
|
-
return { kind: "unverifiable", diagnostic: `process ${collar.pid} no longer matches its recorded start identity` };
|
|
138
|
-
}
|
|
139
|
-
export async function putDownProcessTree(collar) {
|
|
140
|
-
const before = probeProcessTree(collar);
|
|
141
|
-
if (before.kind === "gone")
|
|
142
|
-
return "already-dead";
|
|
143
|
-
if (before.kind === "unverifiable")
|
|
144
|
-
return "unavailable";
|
|
145
|
-
await terminateProcessTree(collar.pid);
|
|
146
|
-
const after = probeProcessTree(collar);
|
|
147
|
-
if (after.kind === "gone")
|
|
148
|
-
return "killed";
|
|
149
|
-
return after.kind === "alive" ? "alive-after-sigkill" : "unavailable";
|
|
132
|
+
function terminalOutcome(code, stdout, stderr, consuming) {
|
|
133
|
+
return {
|
|
134
|
+
kind: "terminal",
|
|
135
|
+
code,
|
|
136
|
+
stdout: consuming ? "" : stdout.text,
|
|
137
|
+
stderr: stderr.text,
|
|
138
|
+
truncated: (consuming ? false : stdout.truncated) || stderr.truncated,
|
|
139
|
+
};
|
|
150
140
|
}
|
|
151
|
-
async function executeProcess(input, timeoutMs) {
|
|
141
|
+
async function executeProcess(input, timeoutMs, consumeStdout) {
|
|
142
|
+
if (input.signal?.aborted === true)
|
|
143
|
+
return { outcome: { kind: "cancelled" }, pid: null };
|
|
152
144
|
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
153
145
|
cwd: input.cwd,
|
|
154
146
|
env: input.env,
|
|
@@ -158,43 +150,85 @@ async function executeProcess(input, timeoutMs) {
|
|
|
158
150
|
});
|
|
159
151
|
const stdout = tailCapture(STREAM_TAIL_BYTES);
|
|
160
152
|
const stderr = tailCapture(STREAM_TAIL_BYTES);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
let timedOut = false;
|
|
153
|
+
let stop;
|
|
154
|
+
let streamError;
|
|
164
155
|
let termination;
|
|
165
|
-
const requestStop = () => {
|
|
166
|
-
if (
|
|
156
|
+
const requestStop = (reason) => {
|
|
157
|
+
if (stop !== undefined)
|
|
158
|
+
return;
|
|
159
|
+
stop = reason;
|
|
160
|
+
termination = terminateOwnedProcess(child);
|
|
161
|
+
};
|
|
162
|
+
const failStream = (error) => {
|
|
163
|
+
if (streamError !== undefined)
|
|
167
164
|
return;
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
streamError = error;
|
|
166
|
+
requestStop("cancelled");
|
|
170
167
|
};
|
|
171
|
-
|
|
168
|
+
child.stdout.on("data", (chunk) => {
|
|
169
|
+
if (consumeStdout === undefined) {
|
|
170
|
+
stdout.append(chunk);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
consumeStdout(chunk);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
failStream(error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
child.stderr.on("data", (chunk) => stderr.append(chunk));
|
|
181
|
+
if (consumeStdout !== undefined) {
|
|
182
|
+
child.stdout.once("error", failStream);
|
|
183
|
+
child.stderr.once("error", failStream);
|
|
184
|
+
}
|
|
185
|
+
const cancel = () => requestStop("cancelled");
|
|
186
|
+
input.signal?.addEventListener("abort", cancel, { once: true });
|
|
187
|
+
const timeout = timeoutMs === undefined ? undefined : setTimeout(() => requestStop("timeout"), timeoutMs);
|
|
172
188
|
const terminal = await new Promise((resolve) => {
|
|
173
189
|
child.once("close", (code) => resolve({ kind: "closed", code }));
|
|
174
190
|
child.once("error", (error) => resolve({ kind: "spawn-error", error }));
|
|
175
191
|
});
|
|
176
192
|
if (timeout !== undefined)
|
|
177
193
|
clearTimeout(timeout);
|
|
194
|
+
input.signal?.removeEventListener("abort", cancel);
|
|
178
195
|
await termination;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
196
|
+
const pid = child.pid ?? null;
|
|
197
|
+
if (terminal.kind === "spawn-error") {
|
|
198
|
+
return { outcome: { kind: "spawn-error", diagnostic: terminal.error.message }, pid };
|
|
199
|
+
}
|
|
200
|
+
if (streamError !== undefined) {
|
|
201
|
+
return {
|
|
202
|
+
outcome: {
|
|
203
|
+
kind: "stream-error",
|
|
204
|
+
diagnostic: streamError instanceof Error ? streamError.message : String(streamError),
|
|
205
|
+
},
|
|
206
|
+
pid,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (stop !== undefined)
|
|
210
|
+
return { outcome: { kind: stop }, pid };
|
|
183
211
|
if (terminal.code === null)
|
|
184
|
-
return { kind: "unknown-exit" };
|
|
212
|
+
return { outcome: { kind: "unknown-exit" }, pid };
|
|
185
213
|
const capturedStdout = stdout.result();
|
|
186
214
|
const capturedStderr = stderr.result();
|
|
187
215
|
return {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
stdout: capturedStdout.text,
|
|
191
|
-
stderr: capturedStderr.text,
|
|
192
|
-
truncated: capturedStdout.truncated || capturedStderr.truncated,
|
|
216
|
+
outcome: terminalOutcome(terminal.code, capturedStdout, capturedStderr, consumeStdout !== undefined),
|
|
217
|
+
pid,
|
|
193
218
|
};
|
|
194
219
|
}
|
|
195
|
-
export function runProcess(input) {
|
|
196
|
-
|
|
220
|
+
export async function runProcess(input) {
|
|
221
|
+
const result = await executeProcess(input, input.timeoutMs);
|
|
222
|
+
if (result.outcome.kind === "stream-error")
|
|
223
|
+
throw new Error("buffered process produced a stream consumer error");
|
|
224
|
+
return result.outcome;
|
|
225
|
+
}
|
|
226
|
+
export async function runProcessToExit(input) {
|
|
227
|
+
const result = await executeProcess(input, undefined);
|
|
228
|
+
if (result.outcome.kind === "stream-error")
|
|
229
|
+
throw new Error("buffered process produced a stream consumer error");
|
|
230
|
+
return result.outcome;
|
|
197
231
|
}
|
|
198
|
-
export function
|
|
199
|
-
return executeProcess(input,
|
|
232
|
+
export function consumeProcessStdout(input, consume) {
|
|
233
|
+
return executeProcess(input, input.timeoutMs, consume);
|
|
200
234
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export type StdioProcessExit = Readonly<{
|
|
3
|
+
code: number | null;
|
|
4
|
+
signal: NodeJS.Signals | null;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}>;
|
|
7
|
+
export type StdioProcess = Readonly<{
|
|
8
|
+
input: Writable;
|
|
9
|
+
output: Readable;
|
|
10
|
+
exited: Promise<StdioProcessExit>;
|
|
11
|
+
endInputAndDrain(timeoutMs?: number): Promise<void>;
|
|
12
|
+
close(force?: boolean): Promise<void>;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function spawnStdioProcess(input: Readonly<{
|
|
15
|
+
argv: readonly [string, ...string[]];
|
|
16
|
+
cwd: string;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
}>): StdioProcess;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { terminateOwnedProcess } from "./run.js";
|
|
4
|
+
const DEFAULT_DRAIN_TIMEOUT_MS = 1_000;
|
|
5
|
+
export function spawnStdioProcess(input) {
|
|
6
|
+
const child = spawn(input.argv[0], input.argv.slice(1), {
|
|
7
|
+
cwd: input.cwd,
|
|
8
|
+
env: input.env ?? process.env,
|
|
9
|
+
detached: true,
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
+
windowsHide: true,
|
|
12
|
+
});
|
|
13
|
+
let stderr = "";
|
|
14
|
+
let inputEnded = false;
|
|
15
|
+
let forced;
|
|
16
|
+
child.stderr.setEncoding("utf8");
|
|
17
|
+
child.stderr.on("data", (chunk) => { stderr = `${stderr}${chunk}`.slice(-4_000); });
|
|
18
|
+
const exited = new Promise((resolve) => {
|
|
19
|
+
child.once("close", (code, signal) => resolve({ code, signal, stderr: stderr.trim() }));
|
|
20
|
+
child.once("error", (error) => resolve({ code: null, signal: null, stderr: error.message }));
|
|
21
|
+
});
|
|
22
|
+
const endInput = () => {
|
|
23
|
+
if (inputEnded)
|
|
24
|
+
return;
|
|
25
|
+
inputEnded = true;
|
|
26
|
+
child.stdin.end();
|
|
27
|
+
};
|
|
28
|
+
const forceClose = async () => {
|
|
29
|
+
forced ??= (async () => {
|
|
30
|
+
await terminateOwnedProcess(child, true);
|
|
31
|
+
await exited;
|
|
32
|
+
})();
|
|
33
|
+
await forced;
|
|
34
|
+
};
|
|
35
|
+
const endInputAndDrain = async (timeoutMs = DEFAULT_DRAIN_TIMEOUT_MS) => {
|
|
36
|
+
endInput();
|
|
37
|
+
if (forced !== undefined) {
|
|
38
|
+
await forced;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const timedOut = await Promise.race([
|
|
42
|
+
exited.then(() => false),
|
|
43
|
+
delay(timeoutMs, true, { ref: false }),
|
|
44
|
+
]);
|
|
45
|
+
if (timedOut)
|
|
46
|
+
await forceClose();
|
|
47
|
+
};
|
|
48
|
+
const close = async (force = false) => {
|
|
49
|
+
endInput();
|
|
50
|
+
if (force) {
|
|
51
|
+
await forceClose();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
await terminateOwnedProcess(child, false);
|
|
55
|
+
await exited;
|
|
56
|
+
};
|
|
57
|
+
return { input: child.stdin, output: child.stdout, exited, endInputAndDrain, close };
|
|
58
|
+
}
|
package/build/src/settings.d.ts
CHANGED
|
@@ -42,4 +42,10 @@ export type SettingsInput = Readonly<{
|
|
|
42
42
|
root?: string;
|
|
43
43
|
home?: string;
|
|
44
44
|
}>;
|
|
45
|
-
export declare
|
|
45
|
+
export declare class SettingsError extends Error {
|
|
46
|
+
readonly kind = "settings";
|
|
47
|
+
constructor(message: string);
|
|
48
|
+
}
|
|
49
|
+
export declare function settings(input?: SettingsInput): Promise<Settings>;
|
|
50
|
+
/** Read only the project scope owned by a materialized candidate snapshot. */
|
|
51
|
+
export declare function projectSettings(root: string): Promise<Settings>;
|
package/build/src/settings.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
export class SettingsError extends Error {
|
|
5
|
+
kind = "settings";
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SettingsError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
4
11
|
function object(value) {
|
|
5
12
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
13
|
}
|
|
@@ -20,10 +27,10 @@ function freezeJson(value) {
|
|
|
20
27
|
function bounded(error) {
|
|
21
28
|
return (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
22
29
|
}
|
|
23
|
-
function readScope(path) {
|
|
30
|
+
async function readScope(path) {
|
|
24
31
|
let bytes;
|
|
25
32
|
try {
|
|
26
|
-
bytes =
|
|
33
|
+
bytes = await readFile(path, "utf8");
|
|
27
34
|
}
|
|
28
35
|
catch (error) {
|
|
29
36
|
if (error.code === "ENOENT")
|
|
@@ -60,20 +67,7 @@ function namespaceEntries(name, scope, loaded) {
|
|
|
60
67
|
})),
|
|
61
68
|
};
|
|
62
69
|
}
|
|
63
|
-
|
|
64
|
-
if (!object(input))
|
|
65
|
-
throw new TypeError("settings input must be an object");
|
|
66
|
-
if (input.root !== undefined && (typeof input.root !== "string" || input.root.trim().length === 0)) {
|
|
67
|
-
throw new TypeError("settings root must be a nonblank string");
|
|
68
|
-
}
|
|
69
|
-
if (input.home !== undefined && (typeof input.home !== "string" || input.home.trim().length === 0)) {
|
|
70
|
-
throw new TypeError("settings home must be a nonblank string");
|
|
71
|
-
}
|
|
72
|
-
const home = resolve(input.home ?? join(homedir(), ".keiyaku"));
|
|
73
|
-
const user = readScope(join(home, "settings.json"));
|
|
74
|
-
const project = input.root === undefined
|
|
75
|
-
? { state: { kind: "absent" } }
|
|
76
|
-
: readScope(join(resolve(input.root), ".keiyaku", "settings.json"));
|
|
70
|
+
function settingsFromScopes(project, user) {
|
|
77
71
|
return Object.freeze({
|
|
78
72
|
scopes: Object.freeze({ project: project.state, user: user.state }),
|
|
79
73
|
namespace(name) {
|
|
@@ -97,3 +91,24 @@ export function settings(input = {}) {
|
|
|
97
91
|
},
|
|
98
92
|
});
|
|
99
93
|
}
|
|
94
|
+
export async function settings(input = {}) {
|
|
95
|
+
if (!object(input))
|
|
96
|
+
throw new TypeError("settings input must be an object");
|
|
97
|
+
if (input.root !== undefined && (typeof input.root !== "string" || input.root.trim().length === 0)) {
|
|
98
|
+
throw new TypeError("settings root must be a nonblank string");
|
|
99
|
+
}
|
|
100
|
+
if (input.home !== undefined && (typeof input.home !== "string" || input.home.trim().length === 0)) {
|
|
101
|
+
throw new TypeError("settings home must be a nonblank string");
|
|
102
|
+
}
|
|
103
|
+
const project = input.root === undefined
|
|
104
|
+
? { state: { kind: "absent" } }
|
|
105
|
+
: await readScope(join(resolve(input.root), ".keiyaku", "settings.json"));
|
|
106
|
+
const user = await readScope(join(resolve(input.home ?? join(homedir(), ".keiyaku")), "settings.json"));
|
|
107
|
+
return settingsFromScopes(project, user);
|
|
108
|
+
}
|
|
109
|
+
/** Read only the project scope owned by a materialized candidate snapshot. */
|
|
110
|
+
export async function projectSettings(root) {
|
|
111
|
+
if (typeof root !== "string" || root.trim().length === 0)
|
|
112
|
+
throw new TypeError("settings root must be a nonblank string");
|
|
113
|
+
return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), { state: { kind: "absent" } });
|
|
114
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type HeldSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
2
|
+
import { type GitRepository } from "../git/repository.js";
|
|
3
|
+
import type { TaskId } from "../task/identity.js";
|
|
4
|
+
export declare function settlementFencePath(repository: GitRepository, taskId: TaskId): string;
|
|
5
|
+
export declare function acquireTaskSettlementFence(repository: GitRepository, taskId: TaskId): Promise<HeldSqliteTransactionLock>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
4
|
+
import { commonGitDirectory } from "../git/repository.js";
|
|
5
|
+
export function settlementFencePath(repository, taskId) {
|
|
6
|
+
const locator = createHash("sha256").update(taskId).digest("hex");
|
|
7
|
+
return resolve(commonGitDirectory(repository), "keiyaku", "locks", "settlement", `${locator}.sqlite`);
|
|
8
|
+
}
|
|
9
|
+
export function acquireTaskSettlementFence(repository, taskId) {
|
|
10
|
+
return acquireSqliteTransactionLock({
|
|
11
|
+
path: settlementFencePath(repository, taskId),
|
|
12
|
+
mode: "immediate",
|
|
13
|
+
});
|
|
14
|
+
}
|