@a9n-shoji/rvw 0.2.0 → 0.2.1
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 +21 -0
- package/README.md +10 -6
- package/dist/cli.mjs +492 -307
- package/dist/cli.mjs.map +3 -3
- package/dist/web/assets/{DocumentViewer-CNgHpCuL.js → DocumentViewer-DvrDlIpR.js} +33 -33
- package/dist/web/assets/{markdown-source-map-_dS1q5Dg.js → MarkdownTable-DbDc9VoP.js} +2 -2
- package/dist/web/assets/WalkthroughViewer-aDUeKC3B.js +1 -0
- package/dist/web/assets/{abnfDiagram-N423BO3Z-0v-ndtqQ.js → abnfDiagram-N423BO3Z-fQDP4qYL.js} +1 -1
- package/dist/web/assets/architecture-TIHT7OUA-CuFS9GkN.js +1 -0
- package/dist/web/assets/{architectureDiagram-T3A2C74G-WSNJ-tZE.js → architectureDiagram-T3A2C74G-D3Aon3Nd.js} +1 -1
- package/dist/web/assets/{blockDiagram-VBNYF7ZC-lmMbEUwI.js → blockDiagram-VBNYF7ZC-DS1DcAqy.js} +1 -1
- package/dist/web/assets/{c4Diagram-5PPSVZJV-BrN5YVV8.js → c4Diagram-5PPSVZJV-BHQWz7l2.js} +1 -1
- package/dist/web/assets/channel-CTWz7tRI.js +1 -0
- package/dist/web/assets/{chunk-2GRJ4B5K-gt2JPyrH.js → chunk-2GRJ4B5K-CaahxDVT.js} +1 -1
- package/dist/web/assets/{chunk-4I5QYGJK-Cg5GZ_-7.js → chunk-4I5QYGJK-Bat5hdXD.js} +1 -1
- package/dist/web/assets/{chunk-5RXB4S5H-BdhRGvxW.js → chunk-5RXB4S5H-jyonN41f.js} +1 -1
- package/dist/web/assets/{chunk-6Q2QTUOP-B1DYWNVJ.js → chunk-6Q2QTUOP-BBo9TYzL.js} +1 -1
- package/dist/web/assets/{chunk-7Z6QIM7H-D2YImamZ.js → chunk-7Z6QIM7H-CGSwsKhC.js} +1 -1
- package/dist/web/assets/{chunk-GF5L2VYU-CWtwL_CM.js → chunk-GF5L2VYU-Cuca3bAY.js} +1 -1
- package/dist/web/assets/{chunk-I66GZJ75-DdoGlxMt.js → chunk-I66GZJ75-DRdqLSyH.js} +1 -1
- package/dist/web/assets/{chunk-JQJVKLGR-CVWAp3XP.js → chunk-JQJVKLGR-8xhLkhs2.js} +1 -1
- package/dist/web/assets/{chunk-KBJHAD2P-BBacwg3J.js → chunk-KBJHAD2P-oOV-3e5-.js} +1 -1
- package/dist/web/assets/{chunk-NSK5VX7P-1AsXiBV3.js → chunk-NSK5VX7P-CXit1l3q.js} +1 -1
- package/dist/web/assets/{chunk-QR6OTTB3-DL2s5pNp.js → chunk-QR6OTTB3-DcdCU-01.js} +1 -1
- package/dist/web/assets/{chunk-UBXNYLIW-mu3fkNQI.js → chunk-UBXNYLIW-i72Hgljr.js} +1 -1
- package/dist/web/assets/{chunk-W5SLKNZC-BAwhMBBb.js → chunk-W5SLKNZC-DetAQo7M.js} +1 -1
- package/dist/web/assets/{chunk-WRU74C26-C7IeXZaP.js → chunk-WRU74C26-DDUvJFNs.js} +1 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-BJQv9QuO.js +1 -0
- package/dist/web/assets/classDiagram-v2-OCEON4UE-BJQv9QuO.js +1 -0
- package/dist/web/assets/{cynefin-VYW2F7L2-DNisGvNv.js → cynefin-VYW2F7L2-fd-v6ani.js} +1 -1
- package/dist/web/assets/{cynefinDiagram-MW4NZA55-BA27gOTz.js → cynefinDiagram-MW4NZA55-DRsVpAC3.js} +1 -1
- package/dist/web/assets/{dagre-VZM6K2ZE-vo_1YAuM.js → dagre-VZM6K2ZE-BnRSs_fe.js} +1 -1
- package/dist/web/assets/{diagram-7IWD3JNH-DLLdUj0_.js → diagram-7IWD3JNH-D1g0PR4y.js} +1 -1
- package/dist/web/assets/{diagram-B4RE2ZJO-BV0WyNA-.js → diagram-B4RE2ZJO-DuDKsQLp.js} +1 -1
- package/dist/web/assets/{diagram-LBJQPF4R-DXxKSx2U.js → diagram-LBJQPF4R-COpzgGe7.js} +1 -1
- package/dist/web/assets/{diagram-Q27KOJAE-DsQ2GzIK.js → diagram-Q27KOJAE-DR2vRbPE.js} +1 -1
- package/dist/web/assets/{diagram-UB23O5K3-DfjElFP1.js → diagram-UB23O5K3-hs4VGPP7.js} +1 -1
- package/dist/web/assets/{ebnfDiagram-BXEA7PRR-cYu0XV1a.js → ebnfDiagram-BXEA7PRR-B_4NlK9q.js} +1 -1
- package/dist/web/assets/{erDiagram-JOGREHBK-DnsLBHaV.js → erDiagram-JOGREHBK-JyoZ3U-o.js} +1 -1
- package/dist/web/assets/eventmodeling-45OFAUF4-CfUhQK5g.js +1 -0
- package/dist/web/assets/flowDiagram-UKHOOZJN-FFZ82SPa.js +1 -0
- package/dist/web/assets/{ganttDiagram-PKOTCBZU-CVD5gq3E.js → ganttDiagram-PKOTCBZU-f37I0Twm.js} +1 -1
- package/dist/web/assets/{gitGraph-TEB2WS4Q-C_j0n-Kw.js → gitGraph-TEB2WS4Q-B_QWjr7j.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-qACevJmW.js → gitGraphDiagram-DS77QQ5N-AZsaC0hu.js} +1 -1
- package/dist/web/assets/{index-Coye1BY5.js → index-CY_JoUL3.js} +98 -98
- package/dist/web/assets/index-kK3hngnz.css +1 -0
- package/dist/web/assets/{info-DKCQHKI2-Cmkbz3Yi.js → info-DKCQHKI2-BRfisbEI.js} +1 -1
- package/dist/web/assets/{infoDiagram-6WML65LV-CDrAem7M.js → infoDiagram-6WML65LV-CaIb64N5.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-DXgCnFqW.js → ishikawaDiagram-WSZJBQD7-BoJrcTja.js} +1 -1
- package/dist/web/assets/{journeyDiagram-NVQOT4AX-1KW-I7DV.js → journeyDiagram-NVQOT4AX-CRR1dZy8.js} +1 -1
- package/dist/web/assets/{kanban-definition-27J2QSJJ-CCt75OuF.js → kanban-definition-27J2QSJJ-D5skDuP4.js} +1 -1
- package/dist/web/assets/{line-DQfkDi_r.js → line-BKKx0vSh.js} +1 -1
- package/dist/web/assets/{mermaid-parser.core-B7TMMtY0.js → mermaid-parser.core-DvZnIth0.js} +3 -3
- package/dist/web/assets/{mermaid.core-B93x-7Mk.js → mermaid.core-hykiOF_o.js} +4 -4
- package/dist/web/assets/{mindmap-definition-FAOFIHXS-Dyt28V6S.js → mindmap-definition-FAOFIHXS-BN2LQ4Yn.js} +1 -1
- package/dist/web/assets/{packet-7NZHBO7P-zQ0u35s3.js → packet-7NZHBO7P-CS5I0NlV.js} +1 -1
- package/dist/web/assets/{pegDiagram-VL7TDLO6-et0KHGIE.js → pegDiagram-VL7TDLO6-WvOdhbxo.js} +1 -1
- package/dist/web/assets/{pie-RZYD4A2V-uur86LkQ.js → pie-RZYD4A2V-BwNAZrQ1.js} +1 -1
- package/dist/web/assets/{pieDiagram-7S7Q4E2Y-c027WPTB.js → pieDiagram-7S7Q4E2Y-CbyrZjm7.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-BtSV4E-0.js → quadrantDiagram-CIZ2JOQS-CW9YBxAO.js} +1 -1
- package/dist/web/assets/{radar-I7S5WNFK-B_g0VZMF.js → radar-I7S5WNFK-DBoCLDNH.js} +1 -1
- package/dist/web/assets/{railroad-3IZDKUUU-VFLsM_Em.js → railroad-3IZDKUUU-BOjpYyDz.js} +1 -1
- package/dist/web/assets/railroad-abnf-AHOZXSZD-DQhDS2pf.js +1 -0
- package/dist/web/assets/railroad-ebnf-EBAXGLYW-Dtt2kOaN.js +1 -0
- package/dist/web/assets/railroad-peg-LSFZ7HO6-nfH8zno6.js +1 -0
- package/dist/web/assets/{railroadDiagram-AXF67PYL-uOtnxRbQ.js → railroadDiagram-AXF67PYL-BdOOOq8o.js} +1 -1
- package/dist/web/assets/{requirementDiagram-LRYGKXZP-DGSZAsj4.js → requirementDiagram-LRYGKXZP-DIYlSrTb.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-W5VNT64P-CCDk8Vxt.js → sankeyDiagram-W5VNT64P-Cc1IGxp_.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-SI44F4Z6-Ce-Jzdk5.js → sequenceDiagram-SI44F4Z6-CJsPO9Z9.js} +1 -1
- package/dist/web/assets/{stateDiagram-OKZ733FA-BYpYzyEa.js → stateDiagram-OKZ733FA-B033gpHZ.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-wS01qj3t.js +1 -0
- package/dist/web/assets/{swimlanes-SLNWSIFB-BL-CUc6d.js → swimlanes-SLNWSIFB-C0Tstg7e.js} +1 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-DoAqfWAd.js +8 -0
- package/dist/web/assets/{timeline-definition-Z64GVDOM-p9pkof_K.js → timeline-definition-Z64GVDOM-BA3y-16W.js} +1 -1
- package/dist/web/assets/{treeView-QDETBFTQ-dRhXTU9p.js → treeView-QDETBFTQ-BUNq-JXr.js} +1 -1
- package/dist/web/assets/{treemap-6X3UGDF4-CZHe3dIh.js → treemap-6X3UGDF4-FIR_NbUo.js} +1 -1
- package/dist/web/assets/{vennDiagram-T6HMQDX7-ipODJegx.js → vennDiagram-T6HMQDX7-IeFFjnlq.js} +1 -1
- package/dist/web/assets/{wardley-OPB4EBWU-CxHlVjkF.js → wardley-OPB4EBWU-DQAX4LKo.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-T6FBY63Y-YarkIncH.js → wardleyDiagram-T6FBY63Y-guGPPMAG.js} +1 -1
- package/dist/web/assets/{xychartDiagram-ELKLHX3M-Cw1M-7a4.js → xychartDiagram-ELKLHX3M-Cj0v359_.js} +1 -1
- package/dist/web/index.html +2 -2
- package/migrations/010_comment_post_references.sql +17 -0
- package/package.json +1 -1
- package/skills/rvw/SKILL.md +35 -3
- package/skills/rvw-walkthrough/SKILL.md +1 -1
- package/skills/rvw-watch-comments/SKILL.md +186 -67
- package/skills/rvw-watch-comments/scripts/auto-ack.mjs +211 -0
- package/skills/rvw-watch-comments/scripts/preflight.mjs +108 -0
- package/skills/rvw-watch-comments/scripts/rvw-command.mjs +80 -0
- package/skills/rvw-watch-comments/scripts/watch-driver.mjs +272 -0
- package/skills/rvw-watch-comments/scripts/watch-state.mjs +111 -8
- package/dist/web/assets/WalkthroughViewer-BVB3ETHG.js +0 -1
- package/dist/web/assets/architecture-TIHT7OUA-BjGWtjPU.js +0 -1
- package/dist/web/assets/channel-DkH22XO0.js +0 -1
- package/dist/web/assets/classDiagram-JCYQIIEL-C6Z4rmZR.js +0 -1
- package/dist/web/assets/classDiagram-v2-OCEON4UE-C6Z4rmZR.js +0 -1
- package/dist/web/assets/eventmodeling-45OFAUF4-BUAcQ5NU.js +0 -1
- package/dist/web/assets/flowDiagram-UKHOOZJN-hAe9ojjP.js +0 -1
- package/dist/web/assets/index-DFdYNMZE.css +0 -1
- package/dist/web/assets/railroad-abnf-AHOZXSZD-TVRi9_2L.js +0 -1
- package/dist/web/assets/railroad-ebnf-EBAXGLYW-B5B-JrS4.js +0 -1
- package/dist/web/assets/railroad-peg-LSFZ7HO6-CfIpa6ba.js +0 -1
- package/dist/web/assets/stateDiagram-v2-UEYNNEHI-BJFG9cb2.js +0 -1
- package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-BS8L8fb1.js +0 -8
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { runRvw } from "./rvw-command.mjs";
|
|
4
|
+
|
|
5
|
+
const REQUIRED_NODE = [24, 15, 0];
|
|
6
|
+
const REQUIRED_PROTOCOL = 3;
|
|
7
|
+
const REQUIRED_CAPABILITIES = [
|
|
8
|
+
"agent.transport",
|
|
9
|
+
"comment.watch",
|
|
10
|
+
"comment.read",
|
|
11
|
+
"comment.reply",
|
|
12
|
+
"comment.edit",
|
|
13
|
+
"comment.codeReferences",
|
|
14
|
+
"pullRequest.sync",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function nodeIsSupported(version) {
|
|
18
|
+
const actual = version.split(".").map(Number);
|
|
19
|
+
for (let index = 0; index < REQUIRED_NODE.length; index += 1) {
|
|
20
|
+
if ((actual[index] ?? 0) > REQUIRED_NODE[index]) return true;
|
|
21
|
+
if ((actual[index] ?? 0) < REQUIRED_NODE[index]) return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function commandSummary(result) {
|
|
27
|
+
return {
|
|
28
|
+
exitCode: result.code,
|
|
29
|
+
signal: result.signal,
|
|
30
|
+
output: result.json,
|
|
31
|
+
...(result.stderr.trim() ? { stderr: result.stderr.trim() } : {}),
|
|
32
|
+
...(!result.json && result.stdout.trim() ? { stdout: result.stdout.trim() } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const settled = await Promise.allSettled([
|
|
38
|
+
runRvw(["protocol", "--json"]),
|
|
39
|
+
runRvw(["agent", "status", "--json"]),
|
|
40
|
+
runRvw(["agent", "ping", "--json"]),
|
|
41
|
+
]);
|
|
42
|
+
const [protocolResult, statusResult, pingResult] = settled.map((result) =>
|
|
43
|
+
result.status === "fulfilled" ? result.value : null,
|
|
44
|
+
);
|
|
45
|
+
const protocol = protocolResult?.json;
|
|
46
|
+
const agentStatus = statusResult?.json;
|
|
47
|
+
const agentPing = pingResult?.json;
|
|
48
|
+
const capabilities = Array.isArray(protocol?.capabilities) ? protocol.capabilities : [];
|
|
49
|
+
const missingCapabilities = REQUIRED_CAPABILITIES.filter(
|
|
50
|
+
(capability) => !capabilities.includes(capability),
|
|
51
|
+
);
|
|
52
|
+
const checks = {
|
|
53
|
+
nodeVersion: nodeIsSupported(process.versions.node),
|
|
54
|
+
rvwBinary: protocolResult !== null,
|
|
55
|
+
appVersion: typeof protocol?.appVersion === "string" && protocol.appVersion.length > 0,
|
|
56
|
+
protocol: protocol?.protocolVersion === REQUIRED_PROTOCOL,
|
|
57
|
+
capabilities: missingCapabilities.length === 0,
|
|
58
|
+
agentStatus: agentStatus?.ok === true && agentStatus.selectedTransport !== "unavailable",
|
|
59
|
+
agentPingInspected: agentPing !== null && typeof agentPing === "object",
|
|
60
|
+
agentPingConnected: agentPing?.ok === true && agentPing.connected === true,
|
|
61
|
+
};
|
|
62
|
+
const ok =
|
|
63
|
+
checks.nodeVersion &&
|
|
64
|
+
checks.rvwBinary &&
|
|
65
|
+
checks.appVersion &&
|
|
66
|
+
checks.protocol &&
|
|
67
|
+
checks.capabilities &&
|
|
68
|
+
checks.agentStatus &&
|
|
69
|
+
checks.agentPingInspected;
|
|
70
|
+
const output = {
|
|
71
|
+
ok,
|
|
72
|
+
node: {
|
|
73
|
+
version: process.versions.node,
|
|
74
|
+
required: ">=24.15.0",
|
|
75
|
+
ok: checks.nodeVersion,
|
|
76
|
+
},
|
|
77
|
+
rvw: {
|
|
78
|
+
binary: protocolResult?.binary ?? process.env.RVW_BIN ?? "rvw",
|
|
79
|
+
appVersion: typeof protocol?.appVersion === "string" ? protocol.appVersion : null,
|
|
80
|
+
protocolVersion:
|
|
81
|
+
typeof protocol?.protocolVersion === "number" ? protocol.protocolVersion : null,
|
|
82
|
+
requiredProtocolVersion: REQUIRED_PROTOCOL,
|
|
83
|
+
capabilities,
|
|
84
|
+
requiredCapabilities: REQUIRED_CAPABILITIES,
|
|
85
|
+
missingCapabilities,
|
|
86
|
+
},
|
|
87
|
+
agent: {
|
|
88
|
+
status: statusResult ? commandSummary(statusResult) : null,
|
|
89
|
+
ping: pingResult ? commandSummary(pingResult) : null,
|
|
90
|
+
pingRequired: false,
|
|
91
|
+
},
|
|
92
|
+
checks,
|
|
93
|
+
errors: settled.flatMap((result, index) =>
|
|
94
|
+
result.status === "rejected"
|
|
95
|
+
? [
|
|
96
|
+
{
|
|
97
|
+
command: ["protocol", "agent status", "agent ping"][index],
|
|
98
|
+
error: String(result.reason),
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
: [],
|
|
102
|
+
),
|
|
103
|
+
};
|
|
104
|
+
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
105
|
+
if (!ok) process.exitCode = 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await main();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const MAX_CAPTURE_BYTES = 40 * 1024 * 1024;
|
|
4
|
+
|
|
5
|
+
function commandPrefix() {
|
|
6
|
+
const binary = process.env.RVW_BIN?.trim() || "rvw";
|
|
7
|
+
const rawPrefix = process.env.RVW_BIN_ARGS_JSON;
|
|
8
|
+
if (!rawPrefix) return { binary, prefix: [] };
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(rawPrefix);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`RVW_BIN_ARGS_JSON must be a JSON string array: ${String(error)}`, {
|
|
14
|
+
cause: error,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) {
|
|
18
|
+
throw new Error("RVW_BIN_ARGS_JSON must be a JSON string array");
|
|
19
|
+
}
|
|
20
|
+
return { binary, prefix: parsed };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function spawnRvw(args, options = {}) {
|
|
24
|
+
const { binary, prefix } = commandPrefix();
|
|
25
|
+
return spawn(binary, [...prefix, ...args], {
|
|
26
|
+
stdio: options.stdio ?? ["pipe", "pipe", "pipe"],
|
|
27
|
+
env: process.env,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runRvw(args, options = {}) {
|
|
32
|
+
const child = spawnRvw(args);
|
|
33
|
+
const stdout = [];
|
|
34
|
+
const stderr = [];
|
|
35
|
+
let stdoutBytes = 0;
|
|
36
|
+
let stderrBytes = 0;
|
|
37
|
+
child.stdout.on("data", (chunk) => {
|
|
38
|
+
stdoutBytes += chunk.length;
|
|
39
|
+
if (stdoutBytes <= MAX_CAPTURE_BYTES) stdout.push(chunk);
|
|
40
|
+
});
|
|
41
|
+
child.stderr.on("data", (chunk) => {
|
|
42
|
+
stderrBytes += chunk.length;
|
|
43
|
+
if (stderrBytes <= MAX_CAPTURE_BYTES) stderr.push(chunk);
|
|
44
|
+
});
|
|
45
|
+
if (options.input === undefined) child.stdin.end();
|
|
46
|
+
else
|
|
47
|
+
child.stdin.end(
|
|
48
|
+
typeof options.input === "string" ? options.input : JSON.stringify(options.input),
|
|
49
|
+
);
|
|
50
|
+
const status = await new Promise((resolve, reject) => {
|
|
51
|
+
child.once("error", reject);
|
|
52
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
53
|
+
});
|
|
54
|
+
if (stdoutBytes > MAX_CAPTURE_BYTES || stderrBytes > MAX_CAPTURE_BYTES) {
|
|
55
|
+
throw new Error("rvw command output exceeded 40 MiB");
|
|
56
|
+
}
|
|
57
|
+
const stdoutText = Buffer.concat(stdout).toString("utf8");
|
|
58
|
+
const stderrText = Buffer.concat(stderr).toString("utf8");
|
|
59
|
+
let json = null;
|
|
60
|
+
if (stdoutText.trim()) {
|
|
61
|
+
try {
|
|
62
|
+
json = JSON.parse(stdoutText);
|
|
63
|
+
} catch {
|
|
64
|
+
// The caller reports malformed output with the original stdout attached.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
args,
|
|
69
|
+
binary: commandPrefix().binary,
|
|
70
|
+
code: status.code,
|
|
71
|
+
signal: status.signal,
|
|
72
|
+
stdout: stdoutText,
|
|
73
|
+
stderr: stderrText,
|
|
74
|
+
json,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function successfulJson(result) {
|
|
79
|
+
return result.code === 0 && result.json && result.json.ok !== false;
|
|
80
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { StringDecoder } from "node:string_decoder";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawnRvw } from "./rvw-command.mjs";
|
|
8
|
+
|
|
9
|
+
const EXIT_WATCH = 20;
|
|
10
|
+
const EXIT_SEQUENCE = 21;
|
|
11
|
+
const EXIT_INGEST = 22;
|
|
12
|
+
const EXIT_AUTO_ACK = 23;
|
|
13
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const stateScript = path.join(scriptDirectory, "watch-state.mjs");
|
|
15
|
+
const autoAckScript = path.join(scriptDirectory, "auto-ack.mjs");
|
|
16
|
+
|
|
17
|
+
class DriverError extends Error {
|
|
18
|
+
constructor(message, exitCode, details = null) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.exitCode = exitCode;
|
|
21
|
+
this.details = details;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArguments(values) {
|
|
26
|
+
let state = null;
|
|
27
|
+
let autoAck = false;
|
|
28
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
29
|
+
const value = values[index];
|
|
30
|
+
if (value === "--auto-ack") {
|
|
31
|
+
autoAck = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (value === "--state") {
|
|
35
|
+
const next = values[index + 1];
|
|
36
|
+
if (!next || next.startsWith("--")) {
|
|
37
|
+
throw new DriverError("--state requires a path", EXIT_WATCH);
|
|
38
|
+
}
|
|
39
|
+
state = next;
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!value.startsWith("--") && state === null) {
|
|
44
|
+
state = value;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
throw new DriverError(`Unexpected argument: ${value}`, EXIT_WATCH);
|
|
48
|
+
}
|
|
49
|
+
if (!state) throw new DriverError("Pass the task state database path", EXIT_WATCH);
|
|
50
|
+
return { state: path.resolve(state), autoAck };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runNodeScript(script, args, input) {
|
|
54
|
+
const child = spawn(process.execPath, [script, ...args], {
|
|
55
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
56
|
+
});
|
|
57
|
+
const stdout = [];
|
|
58
|
+
const stderr = [];
|
|
59
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
60
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
61
|
+
child.stdin.end(input === undefined ? undefined : JSON.stringify(input));
|
|
62
|
+
const status = await new Promise((resolve, reject) => {
|
|
63
|
+
child.once("error", reject);
|
|
64
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
65
|
+
});
|
|
66
|
+
const stdoutText = Buffer.concat(stdout).toString("utf8");
|
|
67
|
+
const stderrText = Buffer.concat(stderr).toString("utf8");
|
|
68
|
+
let json = null;
|
|
69
|
+
try {
|
|
70
|
+
json = JSON.parse(stdoutText);
|
|
71
|
+
} catch {
|
|
72
|
+
// The caller selects the appropriate driver exit contract.
|
|
73
|
+
}
|
|
74
|
+
return { ...status, stdout: stdoutText, stderr: stderrText, json };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function stateCommand(state, command, args = [], input) {
|
|
78
|
+
const result = await runNodeScript(stateScript, [command, "--state", state, ...args], input);
|
|
79
|
+
if (result.code !== 0 || !result.json?.ok) {
|
|
80
|
+
throw new DriverError(`watch-state ${command} failed`, EXIT_INGEST, result);
|
|
81
|
+
}
|
|
82
|
+
return result.json;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function write(value) {
|
|
86
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function dispatchAutoAck(state, pullRequest) {
|
|
90
|
+
const pending = await stateCommand(state, "list");
|
|
91
|
+
if (!pending.pending.some((batch) => batch.pullRequest === pullRequest)) {
|
|
92
|
+
write({ ok: true, type: "queued", pullRequest, reason: "batch-not-yet-eligible" });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const result = await runNodeScript(autoAckScript, [
|
|
96
|
+
"--state",
|
|
97
|
+
state,
|
|
98
|
+
"--pull-request",
|
|
99
|
+
pullRequest,
|
|
100
|
+
]);
|
|
101
|
+
if (result.code !== 0 || !result.json?.ok) {
|
|
102
|
+
throw new DriverError(`auto-ack failed for ${pullRequest}`, EXIT_AUTO_ACK, result);
|
|
103
|
+
}
|
|
104
|
+
write({ ...result.json, type: "batch-acknowledged" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function dispatchPendingAutoAcks(state) {
|
|
108
|
+
const pending = await stateCommand(state, "list");
|
|
109
|
+
for (const batch of pending.pending) {
|
|
110
|
+
await dispatchAutoAck(state, batch.pullRequest);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function processFrame(state, frame, autoAck, pullRequests) {
|
|
115
|
+
if (!frame || typeof frame !== "object" || typeof frame.type !== "string") {
|
|
116
|
+
throw new DriverError("Invalid RFC 7464 frame", EXIT_SEQUENCE, frame);
|
|
117
|
+
}
|
|
118
|
+
if (frame.type === "error") {
|
|
119
|
+
throw new DriverError("rvw comment watch returned an error frame", EXIT_WATCH, frame);
|
|
120
|
+
}
|
|
121
|
+
const ingested = await stateCommand(state, "ingest", [], frame);
|
|
122
|
+
if (frame.type === "ready") {
|
|
123
|
+
write({
|
|
124
|
+
ok: true,
|
|
125
|
+
type: "watch-ready",
|
|
126
|
+
cursor: ingested.cursor,
|
|
127
|
+
databaseId: frame.databaseId,
|
|
128
|
+
anchoredAtCurrent: frame.anchoredAtCurrent,
|
|
129
|
+
autoAck,
|
|
130
|
+
});
|
|
131
|
+
} else if (frame.type === "comment-posted" && ingested.status === "queued") {
|
|
132
|
+
if (autoAck) pullRequests.add(frame.event.pullRequestUrl);
|
|
133
|
+
else {
|
|
134
|
+
write({
|
|
135
|
+
ok: true,
|
|
136
|
+
type: "pending",
|
|
137
|
+
pullRequest: frame.event.pullRequestUrl,
|
|
138
|
+
commentRef: frame.event.commentRef,
|
|
139
|
+
cursor: ingested.cursor,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function runWatchOnce(state, autoAck, stopping) {
|
|
146
|
+
const current = await stateCommand(state, "status");
|
|
147
|
+
const args = ["comment", "watch"];
|
|
148
|
+
if (current.cursor) args.push("--after", current.cursor);
|
|
149
|
+
args.push("--interval", "1", "--json-seq");
|
|
150
|
+
const child = spawnRvw(args);
|
|
151
|
+
stopping.child = child;
|
|
152
|
+
let stderr = "";
|
|
153
|
+
child.stderr.setEncoding("utf8");
|
|
154
|
+
child.stderr.on("data", (chunk) => {
|
|
155
|
+
stderr += chunk;
|
|
156
|
+
process.stderr.write(chunk);
|
|
157
|
+
});
|
|
158
|
+
const statusPromise = new Promise((resolve, reject) => {
|
|
159
|
+
child.once("error", reject);
|
|
160
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
161
|
+
});
|
|
162
|
+
child.stdin.end();
|
|
163
|
+
const decoder = new StringDecoder("utf8");
|
|
164
|
+
let buffered = "";
|
|
165
|
+
let readySeen = false;
|
|
166
|
+
let stoppedSeen = false;
|
|
167
|
+
try {
|
|
168
|
+
for await (const chunk of child.stdout) {
|
|
169
|
+
buffered += decoder.write(chunk);
|
|
170
|
+
const lines = buffered.split("\n");
|
|
171
|
+
buffered = lines.pop() ?? "";
|
|
172
|
+
const pullRequests = new Set();
|
|
173
|
+
for (const rawLine of lines) {
|
|
174
|
+
if (!rawLine) continue;
|
|
175
|
+
if (!rawLine.startsWith("\u001e")) {
|
|
176
|
+
throw new DriverError("RFC 7464 frame does not start with RS", EXIT_SEQUENCE, rawLine);
|
|
177
|
+
}
|
|
178
|
+
let frame;
|
|
179
|
+
try {
|
|
180
|
+
frame = JSON.parse(rawLine.slice(1));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new DriverError("RFC 7464 frame is not valid JSON", EXIT_SEQUENCE, String(error));
|
|
183
|
+
}
|
|
184
|
+
readySeen ||= frame.type === "ready";
|
|
185
|
+
stoppedSeen ||= frame.type === "stopped";
|
|
186
|
+
await processFrame(state, frame, autoAck, pullRequests);
|
|
187
|
+
}
|
|
188
|
+
for (const pullRequest of pullRequests) await dispatchAutoAck(state, pullRequest);
|
|
189
|
+
}
|
|
190
|
+
buffered += decoder.end();
|
|
191
|
+
if (buffered.trim()) {
|
|
192
|
+
throw new DriverError(
|
|
193
|
+
"rvw comment watch ended with a truncated frame",
|
|
194
|
+
EXIT_SEQUENCE,
|
|
195
|
+
buffered,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
const status = await statusPromise;
|
|
199
|
+
stopping.child = null;
|
|
200
|
+
return { ...status, stderr, readySeen, stoppedSeen };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
child.kill("SIGTERM");
|
|
203
|
+
await statusPromise.catch(() => undefined);
|
|
204
|
+
stopping.child = null;
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function retryDelay(attempt) {
|
|
210
|
+
const base = Number(process.env.RVW_WATCH_DRIVER_RETRY_MS ?? "1000");
|
|
211
|
+
const safeBase = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
212
|
+
return Math.min(safeBase * 2 ** Math.max(0, attempt - 1), 30_000);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function delay(milliseconds) {
|
|
216
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function main() {
|
|
220
|
+
const { state, autoAck } = parseArguments(process.argv.slice(2));
|
|
221
|
+
const stopping = { requested: false, child: null };
|
|
222
|
+
const stop = () => {
|
|
223
|
+
stopping.requested = true;
|
|
224
|
+
stopping.child?.kill("SIGTERM");
|
|
225
|
+
};
|
|
226
|
+
process.once("SIGINT", stop);
|
|
227
|
+
process.once("SIGTERM", stop);
|
|
228
|
+
const maxRestarts = Number(process.env.RVW_WATCH_DRIVER_MAX_RESTARTS ?? "5");
|
|
229
|
+
let restarts = 0;
|
|
230
|
+
try {
|
|
231
|
+
while (!stopping.requested) {
|
|
232
|
+
const startedAt = Date.now();
|
|
233
|
+
if (autoAck) await dispatchPendingAutoAcks(state);
|
|
234
|
+
const result = await runWatchOnce(state, autoAck, stopping);
|
|
235
|
+
if (stopping.requested) return;
|
|
236
|
+
if (Date.now() - startedAt >= 30_000) restarts = 0;
|
|
237
|
+
restarts += 1;
|
|
238
|
+
if (restarts > maxRestarts) {
|
|
239
|
+
throw new DriverError("rvw comment watch exceeded its reconnect limit", EXIT_WATCH, result);
|
|
240
|
+
}
|
|
241
|
+
const delayMs = retryDelay(restarts);
|
|
242
|
+
write({
|
|
243
|
+
ok: true,
|
|
244
|
+
type: "reconnecting",
|
|
245
|
+
attempt: restarts,
|
|
246
|
+
delayMs,
|
|
247
|
+
lastExitCode: result.code,
|
|
248
|
+
lastSignal: result.signal,
|
|
249
|
+
readySeen: result.readySeen,
|
|
250
|
+
});
|
|
251
|
+
await delay(delayMs);
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
process.off("SIGINT", stop);
|
|
255
|
+
process.off("SIGTERM", stop);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
await main();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const exitCode = error instanceof DriverError ? error.exitCode : EXIT_WATCH;
|
|
263
|
+
process.stderr.write(
|
|
264
|
+
`${JSON.stringify({
|
|
265
|
+
ok: false,
|
|
266
|
+
error: error instanceof Error ? error.message : String(error),
|
|
267
|
+
details: error?.details ?? null,
|
|
268
|
+
exitCode,
|
|
269
|
+
})}\n`,
|
|
270
|
+
);
|
|
271
|
+
process.exitCode = exitCode;
|
|
272
|
+
}
|
|
@@ -11,20 +11,37 @@ function fail(message) {
|
|
|
11
11
|
|
|
12
12
|
function parseOptions(values) {
|
|
13
13
|
const result = {};
|
|
14
|
+
const flags = new Set(["follow"]);
|
|
14
15
|
for (let index = 0; index < values.length; index += 1) {
|
|
15
16
|
const key = values[index];
|
|
16
17
|
if (!key?.startsWith("--")) fail(`Unexpected argument: ${key ?? ""}`);
|
|
18
|
+
const name = key.slice(2);
|
|
17
19
|
const value = values[index + 1];
|
|
18
|
-
if (value === undefined || value.startsWith("--"))
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
if (value === undefined || value.startsWith("--")) {
|
|
21
|
+
if (!flags.has(name)) fail(`${key} requires a value`);
|
|
22
|
+
result[name] = true;
|
|
23
|
+
} else {
|
|
24
|
+
result[name] = value;
|
|
25
|
+
index += 1;
|
|
26
|
+
}
|
|
21
27
|
}
|
|
22
28
|
return result;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
function required(options, key) {
|
|
26
32
|
const value = options[key];
|
|
27
|
-
if (
|
|
33
|
+
if (typeof value !== "string" || value.length === 0) fail(`--${key} is required`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function positiveIntegerOption(options, key, defaultValue) {
|
|
38
|
+
const raw = options[key];
|
|
39
|
+
if (raw === undefined) return defaultValue;
|
|
40
|
+
if (typeof raw !== "string" || !/^\d+$/.test(raw)) {
|
|
41
|
+
fail(`--${key} must be a positive integer`);
|
|
42
|
+
}
|
|
43
|
+
const value = Number(raw);
|
|
44
|
+
if (!Number.isSafeInteger(value) || value < 1) fail(`--${key} must be a positive integer`);
|
|
28
45
|
return value;
|
|
29
46
|
}
|
|
30
47
|
|
|
@@ -39,6 +56,10 @@ function write(value) {
|
|
|
39
56
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
40
57
|
}
|
|
41
58
|
|
|
59
|
+
function waitFor(milliseconds) {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
function openState(statePath, create) {
|
|
43
64
|
const absolute = path.resolve(statePath);
|
|
44
65
|
if (create) {
|
|
@@ -376,6 +397,34 @@ function listPending(database) {
|
|
|
376
397
|
});
|
|
377
398
|
}
|
|
378
399
|
|
|
400
|
+
async function waitForPending(database, options) {
|
|
401
|
+
const intervalMs = positiveIntegerOption(options, "interval-ms", 250);
|
|
402
|
+
const follow = options.follow === true;
|
|
403
|
+
let wasNonEmpty = false;
|
|
404
|
+
do {
|
|
405
|
+
const pending = listPending(database);
|
|
406
|
+
const isNonEmpty = pending.length > 0;
|
|
407
|
+
if (isNonEmpty && !wasNonEmpty) {
|
|
408
|
+
write({
|
|
409
|
+
ok: true,
|
|
410
|
+
type: "pending",
|
|
411
|
+
pullRequests: pending.map((batch) => batch.pullRequest),
|
|
412
|
+
pending,
|
|
413
|
+
});
|
|
414
|
+
if (!follow) return;
|
|
415
|
+
}
|
|
416
|
+
wasNonEmpty = isNonEmpty;
|
|
417
|
+
await waitFor(intervalMs);
|
|
418
|
+
} while (follow || !wasNonEmpty);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function normalizeWriteKey(writeKey) {
|
|
422
|
+
if (typeof writeKey !== "string" || !/^[^/\s]+\/[^/\s]+$/.test(writeKey)) {
|
|
423
|
+
fail("--write-key must be owner/repository");
|
|
424
|
+
}
|
|
425
|
+
return writeKey.toLowerCase();
|
|
426
|
+
}
|
|
427
|
+
|
|
379
428
|
function createBatch(database, pullRequestUrl, now) {
|
|
380
429
|
const batchId = randomUUID();
|
|
381
430
|
const events = database
|
|
@@ -405,10 +454,7 @@ function createBatch(database, pullRequestUrl, now) {
|
|
|
405
454
|
}
|
|
406
455
|
|
|
407
456
|
function claim(database, pullRequestUrl, writeKey) {
|
|
408
|
-
|
|
409
|
-
fail("--write-key must be owner/repository");
|
|
410
|
-
}
|
|
411
|
-
const canonicalWriteKey = writeKey?.toLowerCase();
|
|
457
|
+
const canonicalWriteKey = writeKey === undefined ? undefined : normalizeWriteKey(writeKey);
|
|
412
458
|
return transaction(database, () => {
|
|
413
459
|
const now = new Date().toISOString();
|
|
414
460
|
const active = database
|
|
@@ -481,6 +527,36 @@ function claim(database, pullRequestUrl, writeKey) {
|
|
|
481
527
|
});
|
|
482
528
|
}
|
|
483
529
|
|
|
530
|
+
function reserveWrite(database, leaseId, writeKey) {
|
|
531
|
+
const canonicalWriteKey = normalizeWriteKey(writeKey);
|
|
532
|
+
return transaction(database, () => {
|
|
533
|
+
const batch = database
|
|
534
|
+
.prepare("SELECT * FROM batches WHERE lease_id = ? AND status = 'in_flight'")
|
|
535
|
+
.get(leaseId);
|
|
536
|
+
if (!batch) fail("Active lease was not found");
|
|
537
|
+
if (batch.write_key !== null && batch.write_key !== canonicalWriteKey) {
|
|
538
|
+
fail(`Active lease already owns repository ${batch.write_key}`);
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
database
|
|
542
|
+
.prepare("UPDATE batches SET write_key = ?, updated_at = ? WHERE id = ?")
|
|
543
|
+
.run(canonicalWriteKey, new Date().toISOString(), batch.id);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (String(error).includes("UNIQUE constraint failed")) {
|
|
546
|
+
fail(`Another write-capable batch owns repository ${canonicalWriteKey}`);
|
|
547
|
+
}
|
|
548
|
+
throw error;
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
leaseId,
|
|
552
|
+
batchId: batch.id,
|
|
553
|
+
pullRequest: batch.pull_request_url,
|
|
554
|
+
writeKey: canonicalWriteKey,
|
|
555
|
+
status: batch.write_key === canonicalWriteKey ? "existing" : "reserved",
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
484
560
|
function acknowledge(database, leaseId, input) {
|
|
485
561
|
if (typeof input.commentRef !== "string" || input.commentRef.length === 0) {
|
|
486
562
|
fail("commentRef is required");
|
|
@@ -635,6 +711,21 @@ function status(database) {
|
|
|
635
711
|
const unbatched = database
|
|
636
712
|
.prepare("SELECT count(*) AS count FROM events WHERE status = 'pending' AND batch_id IS NULL")
|
|
637
713
|
.get();
|
|
714
|
+
const inFlightBatches = database
|
|
715
|
+
.prepare(
|
|
716
|
+
`SELECT id, pull_request_url, attempts, lease_id, write_key, updated_at
|
|
717
|
+
FROM batches WHERE status = 'in_flight' ORDER BY created_at`,
|
|
718
|
+
)
|
|
719
|
+
.all()
|
|
720
|
+
.map((batch) => ({
|
|
721
|
+
batchId: batch.id,
|
|
722
|
+
leaseId: batch.lease_id,
|
|
723
|
+
pullRequest: batch.pull_request_url,
|
|
724
|
+
attempts: Number(batch.attempts),
|
|
725
|
+
writeKey: batch.write_key,
|
|
726
|
+
updatedAt: batch.updated_at,
|
|
727
|
+
operations: batchStatusOperations(database, batch.id),
|
|
728
|
+
}));
|
|
638
729
|
return {
|
|
639
730
|
taskId: getMeta(database, "task_id"),
|
|
640
731
|
databaseId: getMeta(database, "database_id"),
|
|
@@ -649,6 +740,7 @@ function status(database) {
|
|
|
649
740
|
quarantined: batchCounts.quarantined ?? 0,
|
|
650
741
|
unbatchedEvents: Number(unbatched.count),
|
|
651
742
|
},
|
|
743
|
+
inFlightBatches,
|
|
652
744
|
quarantinedBatches: quarantinedBatches(database),
|
|
653
745
|
};
|
|
654
746
|
}
|
|
@@ -683,6 +775,10 @@ async function main() {
|
|
|
683
775
|
write({ ok: true, pending: listPending(database) });
|
|
684
776
|
return;
|
|
685
777
|
}
|
|
778
|
+
if (command === "wait") {
|
|
779
|
+
await waitForPending(database, options);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
686
782
|
if (command === "claim") {
|
|
687
783
|
write({
|
|
688
784
|
ok: true,
|
|
@@ -690,6 +786,13 @@ async function main() {
|
|
|
690
786
|
});
|
|
691
787
|
return;
|
|
692
788
|
}
|
|
789
|
+
if (command === "reserve-write") {
|
|
790
|
+
write({
|
|
791
|
+
ok: true,
|
|
792
|
+
...reserveWrite(database, required(options, "lease"), required(options, "write-key")),
|
|
793
|
+
});
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
693
796
|
if (command === "complete") {
|
|
694
797
|
write({ ok: true, ...complete(database, required(options, "lease"), await readInput()) });
|
|
695
798
|
return;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{B as t,F as n,H as r,L as i,M as a,N as o,P as s,T as c,U as l,V as u,b as d,c as f,d as p,g as m,l as h,o as g,s as _,w as v,x as y,y as b,z as x}from"./index-Coye1BY5.js";import{a as S,i as C,n as w,r as T,t as E}from"./markdown-source-map-_dS1q5Dg.js";var D=e(l(),1),O=r(),k=2400,A={...d,protocols:{...d.protocols,href:[...d.protocols?.href??[],`rvw-ref`]}};function j(e){return e?.startsWith(`rvw-ref:`)?e.slice(8):null}function M(e){return D.Children.toArray(e).map(e=>typeof e==`string`||typeof e==`number`?String(e):(0,D.isValidElement)(e)?M(e.props.children):``).join(``)}function N(e){return e.startLine===null||e.endLine===null?null:e.startLine===e.endLine?`L${e.startLine}`:`L${e.startLine}–${e.endLine}`}function P(e){let t=N(e);return t?`${e.path}:${t}`:e.path}function F(e,t){return!!(e&&t&&e.startLine===t.startLine&&e.endLine===t.endLine)}function I(){return(0,O.jsx)(`svg`,{"aria-hidden":`true`,viewBox:`0 0 16 16`,width:`16`,height:`16`,children:(0,O.jsx)(`path`,{fill:`currentColor`,d:`M5.5 1.75A1.75 1.75 0 0 1 7.25 0h1.5a1.75 1.75 0 0 1 1.75 1.75V2h3a.75.75 0 0 1 0 1.5h-.64l-.68 10.03A2.5 2.5 0 0 1 9.69 16H6.31a2.5 2.5 0 0 1-2.49-2.47L3.14 3.5H2.5a.75.75 0 0 1 0-1.5h3v-.25Zm1.5 0V2h2v-.25a.25.25 0 0 0-.25-.25h-1.5a.25.25 0 0 0-.25.25ZM4.65 3.5l.67 9.96c.02.58.46 1.04.99 1.04h3.38c.53 0 .97-.46.99-1.04l.67-9.96h-6.7Z`})})}function L({source:e,sourceRange:t,commented:n,bindings:r,references:i,themePreference:o,onOpenReference:s,onCommentRange:c,commentComposer:l}){let u=(0,D.useRef)(null),d=!!l;(0,D.useLayoutEffect)(()=>{if(!d)return;let e=window.requestAnimationFrame(()=>{u.current?.scrollIntoView({block:`nearest`,inline:`nearest`})});return()=>window.cancelAnimationFrame(e)},[d]);let f=(0,D.useCallback)(e=>{if(!(e instanceof Element))return;let t=e.closest(`[data-walkthrough-reference-id]`)?.dataset.walkthroughReferenceId;return t?i.get(t):void 0},[i]),p=(0,D.useCallback)(e=>{for(let t of e.querySelectorAll(`g.node, g.classGroup, g[data-id], g[id^='classId-']`)){let e=t.dataset.id??Object.keys(r).find(e=>t.id===e||t.id.includes(`-${e}`)||t.id.includes(`-${e}-`)||t.id.endsWith(`-${e}`));if(!e)continue;let n=r[e],a=n?i.get(n):void 0;a&&(t.classList.add(`walkthrough-diagram-node--linked`),t.setAttribute(`role`,`button`),t.setAttribute(`tabindex`,`0`),t.setAttribute(`aria-label`,`${a.label}をコードで開く`),t.dataset.walkthroughReferenceId=a.id)}},[r,i]);return(0,O.jsxs)(`div`,{className:`walkthrough-diagram-shell${n?` has-comment`:``}`,...t?{"data-rvw-navigation-start-line":t.startLine,"data-rvw-navigation-end-line":t.endLine}:{},children:[(0,O.jsxs)(`div`,{className:`walkthrough-diagram-toolbar`,children:[(0,O.jsx)(`span`,{children:`Mermaid diagram`}),(0,O.jsx)(`span`,{children:`nodeを選択して開く · Cmd/Ctrlで反対のペイン`}),t&&(0,O.jsxs)(`button`,{className:`button--quiet walkthrough-diagram-comment`,onPointerDown:e=>{e.button===0&&(e.preventDefault(),c(t))},onClick:e=>{e.detail===0&&c(t)},children:[(0,O.jsx)(a,{}),`図全体へコメント`]})]}),(0,O.jsx)(h,{className:`walkthrough-diagram`,source:e,themePreference:o,renderIdPrefix:`rvwWalkthrough`,errorClassName:`walkthrough-diagram-error`,onRendered:p,onPointerDown:e=>{let t=f(e.target);t&&(e.preventDefault(),e.stopPropagation(),s(t,e.metaKey||e.ctrlKey))},onKeyDown:e=>{if(e.key!==`Enter`&&e.key!==` `)return;let t=f(e.target);t&&(e.preventDefault(),s(t,!1))}}),l&&(0,O.jsx)(`div`,{className:`walkthrough-diagram-comment-composer`,ref:u,children:l})]})}var R=(0,D.createContext)(null);function z({range:e,pending:t,error:n,onCancel:r,onSubmit:i}){let[a,s]=(0,D.useState)(``),c=e.startLine===e.endLine?`L${e.startLine}へコメント`:`L${e.startLine}–${e.endLine}へコメント`;return(0,O.jsx)(o,{body:a,label:c,pending:t,error:n,validationError:void 0,placement:`line`,onBodyChange:s,onCancel:r,onSubmit:()=>i(a)})}var B=({children:e,node:t,...n})=>{let r=(0,D.useContext)(R),i=D.Children.count(e)===1?D.Children.only(e):null;if(!r||!(0,D.isValidElement)(i)||i.props.className!==`language-mermaid`)return(0,O.jsx)(`pre`,{...n,children:e});let a=T(t);return(0,O.jsx)(L,{source:M(i.props.children).trim(),sourceRange:a,commented:!!(a&&r.activeCommentId&&r.placedComments.some(({comment:e,placement:t})=>e.id===r.activeCommentId&&t.range&&t.range.startLine<=a.endLine&&t.range.endLine>=a.startLine)),bindings:r.diagramBindings,references:r.references,themePreference:r.themePreference,onOpenReference:r.onOpenReference,onCommentRange:r.onCommentRange,commentComposer:a&&F(a,r.diagramCommentRange)?(0,O.jsx)(z,{range:a,pending:r.diagramCommentPending,error:r.diagramCommentError,onCancel:r.onCancelDiagramComment,onSubmit:e=>r.onSubmitDiagramComment(a,e)},`${a.startLine}:${a.endLine}`):null})},V=(0,D.memo)(function({body:e,diagramBindings:t,references:n,placedComments:r,activeCommentId:i,selectedRange:a,selectionComposerOpen:o,diagramCommentRange:s,markdownSourceOid:l,themePreference:u,onOpenReference:d,onOpenRepositoryLink:h,onCommentActiveChange:g,onCommentRange:x,diagramCommentPending:T,diagramCommentError:E,onCancelDiagramComment:k,onSubmitDiagramComment:M}){let F=(0,D.useRef)(null),I=(0,D.useMemo)(()=>r.map(({comment:e,placement:t})=>({id:e.id,range:t.range})),[r]),L=(0,D.useMemo)(()=>new Map(r.map(({comment:e,placement:t})=>[e.id,{comment:e,placement:t}])),[r]),z=(0,D.useCallback)(({node:e,children:t,...n})=>{let r=w(e);return r.length===0?(0,O.jsx)(`div`,{...n,children:t}):(0,O.jsx)(`div`,{className:`markdown-inline-comments`,children:r.map(e=>{let t=L.get(e);return t?(0,O.jsx)(f,{comment:t.comment,variant:`inline`,placement:t.placement,markdownSourceOid:l,themePreference:u,onActiveChange:g,onOpenRepositoryLink:h},e):null})})},[L,l,g,h,u]);return(0,O.jsx)(R.Provider,{value:{diagramBindings:t,references:n,placedComments:r,activeCommentId:i,diagramCommentRange:s,themePreference:u,onOpenReference:d,onCommentRange:x,diagramCommentPending:T,diagramCommentError:E,onCancelDiagramComment:k,onSubmitDiagramComment:M},children:(0,O.jsx)(`article`,{className:`walkthrough-markdown`,children:(0,O.jsx)(v,{rehypePlugins:[y,[b,A],[S,{annotations:I,activeCommentId:i,selectedRange:a,composerOpen:o}]],remarkPlugins:[m],urlTransform:e=>e.startsWith(`rvw-ref:`)?e:c(e),components:{div:z,table:({children:e,node:t,...n})=>(0,O.jsx)(`div`,{className:`markdown-table-scroll`,children:(0,O.jsx)(`table`,{...C(t),...n,children:e})}),a:({href:e,children:t,node:r,...i})=>{let a=j(e),o=a?n.get(a):void 0;return o?(0,O.jsxs)(`button`,{className:`walkthrough-inline-reference`,title:P(o),onPointerDown:e=>{e.button===0&&(F.current={x:e.clientX,y:e.clientY})},onPointerUp:e=>{let t=F.current;F.current=null,t&&(Math.hypot(e.clientX-t.x,e.clientY-t.y)>4||(e.preventDefault(),e.stopPropagation(),d(o,e.metaKey||e.ctrlKey)))},onPointerCancel:()=>{F.current=null},onClick:e=>{e.preventDefault(),e.detail===0&&d(o,!1)},onContextMenu:e=>{(e.ctrlKey||e.metaKey)&&e.preventDefault()},children:[(0,O.jsx)(_,{path:o.path,kind:`file`}),(0,O.jsx)(`span`,{children:t}),(0,O.jsx)(`small`,{children:N(o)??`File`})]}):(0,O.jsx)(`a`,{...C(r),...i,href:e,children:t})},img:({alt:e,title:t,node:n})=>(0,O.jsx)(p,{alt:e,title:t,sourceAttributes:C(n)}),pre:B},children:e})})})});function H({walkthrough:e,comments:r,activeCommentId:c,navigationTarget:l,onNavigationApplied:d,themePreference:p,onCommentActiveChange:m,onOpenReference:h,onOpenRepositoryLink:_,onDeleted:v}){let y=u(),b=(0,D.useRef)(null),S=(0,D.useRef)(null),C=(0,D.useRef)(d);C.current=d;let[w,T]=(0,D.useState)(!1),[A,j]=(0,D.useState)(null),[M,N]=(0,D.useState)(null),[P,F]=(0,D.useState)(null),[L,R]=(0,D.useState)(``),[z,B]=(0,D.useState)(null),H=(0,D.useRef)(0),U=(0,D.useRef)(null),ee=(0,D.useMemo)(()=>new Map(e.references.map(e=>[e.id,e])),[e.references]);(0,D.useLayoutEffect)(()=>{if(!l||S.current===l.requestId)return;let e=l.requestId,t=window.requestAnimationFrame(()=>{let t=b.current;if(!t)return;if(l.line===null){t.scrollIntoView({behavior:`auto`,block:`start`,inline:`nearest`}),S.current=e,C.current(e);return}let n=String(l.line),r=t.querySelector(`[data-rvw-navigation-start-line="${n}"]`)??t.querySelector(`[data-rvw-source-start-line="${n}"]`),i=r?.closest(`details:not([open])`);i&&(i.open=!0),r&&(r.scrollIntoView({behavior:`auto`,block:`center`,inline:`nearest`}),S.current=e,C.current(e))});return()=>window.cancelAnimationFrame(t)},[l]);let W=(0,D.useCallback)((t,n)=>{H.current+=1;let r=H.current;U.current!==null&&(window.clearTimeout(U.current),U.current=null),B(null),h(e,t,n).then(e=>{r!==H.current||!e||(B(e),U.current=window.setTimeout(()=>{B(null),U.current=null},k))})},[h,e]);(0,D.useEffect)(()=>()=>{H.current+=1,U.current!==null&&window.clearTimeout(U.current)},[]);let G=r.filter(t=>t.target.kind===`walkthrough`&&t.target.walkthroughId===e.id),K=G.reduce((e,t)=>e+t.posts.length,0),q=t({queryKey:[`walkthrough-comment-placements`,e.id,e.body,G.map(e=>`${e.id}:${e.updatedAt}`)],queryFn:async()=>{let t=new URLSearchParams({kind:`walkthrough`,pullRequestId:e.pullRequestId,walkthroughId:e.id});return await Promise.all(G.map(async e=>({comment:e,placement:(await n(`/api/comments/${e.id}/placement?${t.toString()}`)).placement})))},enabled:G.length>0}),J=(0,D.useMemo)(()=>q.data??[],[q.data]),te=(0,D.useMemo)(()=>J.filter(({placement:e})=>!e.outdated&&e.range!==null),[J]),Y=(0,D.useMemo)(()=>J.filter(({comment:e,placement:t})=>e.target.kind===`walkthrough`&&e.target.startLine===null||t.outdated),[J]),X=x({mutationFn:async({range:t,body:r})=>await n(`/api/comments`,i({pullRequestId:e.pullRequestId,target:{kind:`walkthrough`,walkthroughId:e.id,startLine:t?.startLine??null,endLine:t?.endLine??null},body:r,authorLabel:`You`})),onSuccess:async()=>{window.getSelection()?.removeAllRanges(),R(``),T(!1),j(null),N(null),F(null),await y.invalidateQueries({queryKey:[`comments`]}),await y.invalidateQueries({queryKey:[`change-sequence`]})}}),Z=x({mutationFn:async()=>await n(`/api/pull-requests/${e.pullRequestId}/walkthroughs/${e.id}`,{...i({}),method:`DELETE`}),onSuccess:async()=>{v(e),await Promise.all([y.invalidateQueries({queryKey:[`walkthroughs`]}),y.invalidateQueries({queryKey:[`walkthrough`]}),y.invalidateQueries({queryKey:[`comments`]}),y.invalidateQueries({queryKey:[`change-sequence`]})])}}),Q=G.filter(e=>e.resolvedAt===null).length,ne=()=>{let e=G.length===0?`紐づくコメントはありません。`:`紐づくコメント ${G.length}件と投稿 ${K}件も削除されます。`;window.confirm(`このウォークスルーを削除します。\n${e}\nコピー済みの参照は無効になります。\n\nこの操作は元に戻せません。`)&&Z.mutate()},re=A?A.startLine===A.endLine?`L${A.startLine}`:`L${A.startLine}–${A.endLine}`:null,$=()=>{window.getSelection()?.removeAllRanges(),X.reset(),R(``),j(null),N(null),F(null)},ie=e=>{X.reset(),R(``),T(!1),j(null),N(e),F(`diagram`)},ae=A?(0,O.jsx)(o,{body:L,label:`${re??`選択範囲`}へコメント`,pending:X.isPending,error:X.error,validationError:void 0,placement:`line`,onBodyChange:R,onCancel:$,onSubmit:()=>X.mutate({range:A,body:L})}):null,oe=(0,O.jsx)(V,{body:e.body,diagramBindings:e.diagramBindings,references:ee,placedComments:te,activeCommentId:c,selectedRange:P===`selection`?A:null,selectionComposerOpen:P===`selection`,diagramCommentRange:M,markdownSourceOid:e.sourceOid,themePreference:p,onOpenReference:W,onOpenRepositoryLink:_,onCommentActiveChange:m,onCommentRange:ie,diagramCommentPending:X.isPending,diagramCommentError:X.error,onCancelDiagramComment:$,onSubmitDiagramComment:(e,t)=>X.mutate({range:e,body:t})});return(0,O.jsxs)(`div`,{className:`walkthrough-viewer`,ref:b,children:[(0,O.jsxs)(`header`,{className:`walkthrough-viewer-header`,children:[(0,O.jsxs)(`div`,{className:`walkthrough-viewer-title`,children:[(0,O.jsx)(`span`,{className:`walkthrough-viewer-icon`,children:(0,O.jsx)(g,{})}),(0,O.jsxs)(`div`,{className:`walkthrough-viewer-heading-copy`,children:[(0,O.jsx)(`span`,{className:`walkthrough-kicker`,children:`Agent-provided walkthrough`}),(0,O.jsx)(`h2`,{title:e.title,children:e.title})]})]}),(0,O.jsxs)(`div`,{className:`walkthrough-viewer-actions`,children:[(0,O.jsxs)(`button`,{className:`comment-icon-button walkthrough-comment-button`,"aria-label":`ウォークスルー全体へコメント`,title:`ウォークスルー全体へコメント`,"aria-pressed":w,onClick:()=>{X.reset(),R(``),j(null),N(null),F(null),T(e=>!e)},children:[(0,O.jsx)(a,{}),Q>0&&(0,O.jsx)(`span`,{children:Q})]}),(0,O.jsx)(`button`,{className:`comment-icon-button walkthrough-delete-button`,"aria-label":`ウォークスルーを削除`,title:`ウォークスルーを削除`,disabled:Z.isPending,onClick:ne,children:(0,O.jsx)(I,{})})]}),z&&(0,O.jsx)(`div`,{className:`walkthrough-reference-notice`,role:`status`,"aria-live":`polite`,children:z})]}),(0,O.jsx)(s,{error:Z.error}),(0,O.jsx)(s,{error:q.error}),(w||Y.length>0)&&(0,O.jsxs)(`div`,{className:`walkthrough-comment-area`,children:[w&&(0,O.jsx)(o,{body:L,label:`ウォークスルー全体へコメント`,pending:X.isPending,error:X.error,validationError:void 0,placement:`file`,onBodyChange:R,onCancel:()=>{X.reset(),R(``),T(!1)},onSubmit:()=>X.mutate({range:null,body:L})}),Y.map(({comment:t,placement:n})=>(0,O.jsx)(f,{comment:t,variant:`inline`,placement:n,markdownSourceOid:e.sourceOid,themePreference:p,onActiveChange:m,onOpenRepositoryLink:_},t.id))]}),(0,O.jsx)(E,{className:`walkthrough-markdown-surface`,selectedRange:A,composerOpen:P===`selection`,onSelection:e=>{if(!e){j(null),F(e=>e===`selection`?null:e);return}X.reset(),R(``),T(!1),j(e),N(null),F(null)},onOpenComposer:()=>F(`selection`),composer:ae,children:oe})]})}export{H as WalkthroughViewer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./chunk-KEIR6QF5-BICK3FdT.js";import{M as e}from"./mermaid-parser.core-B7TMMtY0.js";export{e as createArchitectureServices};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{at as e,ot as t}from"./chunk-I66GZJ75-DdoGlxMt.js";var n=(n,r)=>t.lang.round(e.parse(n)[r]);export{n as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import"./src-B6xuSHsQ.js";import"./chunk-I66GZJ75-DdoGlxMt.js";import"./chunk-NSK5VX7P-1AsXiBV3.js";import"./chunk-4I5QYGJK-Cg5GZ_-7.js";import"./chunk-WRU74C26-C7IeXZaP.js";import"./chunk-7BUUIJ7U-Bb538aSH.js";import"./chunk-UBXNYLIW-mu3fkNQI.js";import"./chunk-2GRJ4B5K-gt2JPyrH.js";import"./chunk-XXDRQBXY-DqAYeljN.js";import"./chunk-KBJHAD2P-BBacwg3J.js";import"./chunk-W5SLKNZC-BAwhMBBb.js";import"./chunk-QR6OTTB3-DL2s5pNp.js";import"./chunk-7Z6QIM7H-D2YImamZ.js";import{i as t,n,r,t as i}from"./chunk-GF5L2VYU-CWtwL_CM.js";import"./mermaid.core-B93x-7Mk.js";var a={parser:n,get db(){return new i},renderer:r,styles:t,init:e(e=>{e.class||={},e.class.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
|