review-workspace 1.2.0 → 1.3.0
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/dist/bundle/round.js +71 -0
- package/dist/cli.js +19 -1
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, relative } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* A stable cross-round identity for one MR: repo slug + number, e.g.
|
|
5
|
+
* "founda/application/xds-viewer-ui!367".
|
|
6
|
+
*/
|
|
7
|
+
export function mrKeyFor(comparison) {
|
|
8
|
+
if (!comparison.repository || !comparison.number) {
|
|
9
|
+
throw new Error('comparison.repository and comparison.number are required to compute an mrKey');
|
|
10
|
+
}
|
|
11
|
+
return `${comparison.repository}!${comparison.number}`;
|
|
12
|
+
}
|
|
13
|
+
function readReviewDocument(bundlePath) {
|
|
14
|
+
return JSON.parse(readFileSync(join(bundlePath, 'review.json'), 'utf-8'));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reads a bundle directory name shaped like `<name>-r{N}` and splits it into
|
|
18
|
+
* the chain's base name and that bundle's round number. A name with no
|
|
19
|
+
* `-r{N}` suffix is round 1.
|
|
20
|
+
*/
|
|
21
|
+
function parseBundleDirName(dirName) {
|
|
22
|
+
const match = dirName.match(/^(.+)-r(\d+)$/);
|
|
23
|
+
if (!match)
|
|
24
|
+
return { baseName: dirName, round: 1 };
|
|
25
|
+
return { baseName: match[1], round: Number(match[2]) };
|
|
26
|
+
}
|
|
27
|
+
/** The sibling bundle path a new round for `bundlePath` would scaffold into. */
|
|
28
|
+
export function nextRoundBundlePath(bundlePath) {
|
|
29
|
+
const { baseName, round } = parseBundleDirName(basename(bundlePath));
|
|
30
|
+
return join(dirname(bundlePath), `${baseName}-r${round + 1}`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detects, purely from what's on disk plus a supplied live head, whether a
|
|
34
|
+
* bundle's recorded Comparison has fallen behind its MR — the "new round"
|
|
35
|
+
* case alongside New/Existing/Improve bundle determination.
|
|
36
|
+
*/
|
|
37
|
+
export function checkRound(bundlePath, liveHead) {
|
|
38
|
+
const { comparison } = readReviewDocument(bundlePath);
|
|
39
|
+
return { needsNewRound: comparison.head !== liveHead, recordedHead: comparison.head, liveHead };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Scaffolds a new sibling round bundle for an MR whose live head has moved
|
|
43
|
+
* past what `bundlePath` recorded. Never mutates `bundlePath` (ADR 0002) —
|
|
44
|
+
* only creates the new `<name>-r{N}` directory alongside it, with its own
|
|
45
|
+
* Comparison, Unified Patch, and a `chain.json` linking it back.
|
|
46
|
+
*/
|
|
47
|
+
export function scaffoldNextRound(bundlePath, liveHead, options) {
|
|
48
|
+
const document = readReviewDocument(bundlePath);
|
|
49
|
+
if (document.comparison.head === liveHead) {
|
|
50
|
+
throw new Error(`Live head ${liveHead} matches the bundle's recorded head; no new round is needed`);
|
|
51
|
+
}
|
|
52
|
+
const { round: currentRound } = parseBundleDirName(basename(bundlePath));
|
|
53
|
+
const newBundlePath = nextRoundBundlePath(bundlePath);
|
|
54
|
+
if (existsSync(newBundlePath)) {
|
|
55
|
+
throw new Error(`Round bundle already exists at ${newBundlePath}`);
|
|
56
|
+
}
|
|
57
|
+
const chain = {
|
|
58
|
+
mrKey: mrKeyFor(document.comparison),
|
|
59
|
+
round: currentRound + 1,
|
|
60
|
+
previousBundle: relative(newBundlePath, bundlePath),
|
|
61
|
+
previousHead: document.comparison.head,
|
|
62
|
+
};
|
|
63
|
+
mkdirSync(newBundlePath, { recursive: true });
|
|
64
|
+
writeFileSync(join(newBundlePath, 'changes.diff'), options.patch);
|
|
65
|
+
writeFileSync(join(newBundlePath, 'review.json'), JSON.stringify({
|
|
66
|
+
schemaVersion: document.schemaVersion,
|
|
67
|
+
comparison: { ...document.comparison, head: liveHead },
|
|
68
|
+
}, null, 2));
|
|
69
|
+
writeFileSync(join(newBundlePath, 'chain.json'), JSON.stringify(chain, null, 2));
|
|
70
|
+
return { bundlePath: newBundlePath, chain };
|
|
71
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { validateBundle } from "./bundle/validate-bundle.js";
|
|
3
4
|
import { publishBundle } from "./bundle/publish.js";
|
|
5
|
+
import { checkRound, scaffoldNextRound } from "./bundle/round.js";
|
|
4
6
|
import { startReviewServer } from "./server/create-server.js";
|
|
5
7
|
const [, , command, bundlePath, ...rest] = process.argv;
|
|
6
8
|
function fail(message) {
|
|
@@ -12,7 +14,7 @@ function flagValue(name) {
|
|
|
12
14
|
return index === -1 ? undefined : rest[index + 1];
|
|
13
15
|
}
|
|
14
16
|
if (!command || !bundlePath) {
|
|
15
|
-
fail('Usage: review-workspace <open|publish|serve> <bundlePath> [--port N]');
|
|
17
|
+
fail('Usage: review-workspace <open|publish|serve|round> <bundlePath> [--port N] [--live-head SHA --patch FILE]');
|
|
16
18
|
}
|
|
17
19
|
if (command === 'open') {
|
|
18
20
|
const result = validateBundle(bundlePath);
|
|
@@ -31,6 +33,22 @@ else if (command === 'publish') {
|
|
|
31
33
|
}
|
|
32
34
|
console.log('Published.');
|
|
33
35
|
}
|
|
36
|
+
else if (command === 'round') {
|
|
37
|
+
const liveHead = flagValue('--live-head');
|
|
38
|
+
const patchPath = flagValue('--patch');
|
|
39
|
+
if (!liveHead || !patchPath) {
|
|
40
|
+
fail('Usage: review-workspace round <bundlePath> --live-head <sha> --patch <patchFile>');
|
|
41
|
+
}
|
|
42
|
+
const { needsNewRound } = checkRound(bundlePath, liveHead);
|
|
43
|
+
if (!needsNewRound) {
|
|
44
|
+
console.log(JSON.stringify({ needsNewRound: false }));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const patch = readFileSync(patchPath, 'utf-8');
|
|
48
|
+
const result = scaffoldNextRound(bundlePath, liveHead, { patch });
|
|
49
|
+
console.log(JSON.stringify({ needsNewRound: true, ...result }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
else if (command === 'serve') {
|
|
35
53
|
const port = Number(flagValue('--port') ?? 4317);
|
|
36
54
|
const handle = await startReviewServer(bundlePath, { port });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "review-workspace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A local, offline decision surface for reviewing a code Comparison — evidence-linked map of intent, behavior, risk, and observable effects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Salih Sayer",
|