pi-pr-review 1.6.4 → 1.6.5
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/.github/workflows/pull-request.yml +3 -0
- package/.github/workflows/release-please.yml +6 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/RELEASING.md +3 -1
- package/package.json +3 -2
- package/scripts/verify-release-version.mjs +66 -0
- package/tests/release-version.test.ts +63 -0
|
@@ -19,6 +19,7 @@ jobs:
|
|
|
19
19
|
contents: read
|
|
20
20
|
outputs:
|
|
21
21
|
release_created: ${{ steps.release.outputs.release_created }}
|
|
22
|
+
release_version: ${{ steps.release.outputs.version }}
|
|
22
23
|
steps:
|
|
23
24
|
- name: Create nerv-ops installation token
|
|
24
25
|
id: app-token
|
|
@@ -65,5 +66,10 @@ jobs:
|
|
|
65
66
|
- name: Test
|
|
66
67
|
run: bun test
|
|
67
68
|
|
|
69
|
+
- name: Verify release version metadata
|
|
70
|
+
env:
|
|
71
|
+
EXPECTED_VERSION: ${{ needs.release.outputs.release_version }}
|
|
72
|
+
run: npm run verify:release-version -- --expected "$EXPECTED_VERSION"
|
|
73
|
+
|
|
68
74
|
- name: Publish to npm
|
|
69
75
|
run: npm publish --access public --provenance
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.5](https://github.com/10ego/pi-pr-review/compare/v1.6.4...v1.6.5) (2026-07-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **release:** verify root version metadata ([#9](https://github.com/10ego/pi-pr-review/issues/9)) ([ce4e36f](https://github.com/10ego/pi-pr-review/commit/ce4e36f1d2c3aa11b60f5d37564d48a8783e192c))
|
|
9
|
+
|
|
3
10
|
## [1.6.4](https://github.com/10ego/pi-pr-review/compare/v1.6.3...v1.6.4) (2026-07-13)
|
|
4
11
|
|
|
5
12
|
|
package/RELEASING.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Releasing
|
|
2
2
|
|
|
3
|
-
[Release Please](https://github.com/googleapis/release-please) watches conventional commits merged into `main`. It opens or updates a release PR containing the calculated version change, `CHANGELOG.md`, and release manifest. The generated PR title—and therefore its squash commit—uses `release(main): release <version>`. Merging that release PR creates a GitHub release and, after the test suite passes, publishes `pi-pr-review` to npm with signed provenance.
|
|
3
|
+
[Release Please](https://github.com/googleapis/release-please) watches conventional commits merged into `main`. It opens or updates a release PR containing the calculated version change, `CHANGELOG.md`, and release manifest. Its root Node strategy is the sole version writer: the generated PR keeps `package.json` and `.release-please-manifest.json` in sync. The generated PR title—and therefore its squash commit—uses `release(main): release <version>`. Merging that release PR creates a GitHub release and, after the test suite passes, publishes `pi-pr-review` to npm with signed provenance.
|
|
4
|
+
|
|
5
|
+
Pull-request CI verifies both root version fields remain equal. The publish job repeats that read-only check against the version emitted by Release Please, so an inconsistent release commit cannot publish; no feature PR should manually set a release version.
|
|
4
6
|
|
|
5
7
|
## Semver
|
|
6
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pr-review",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.5",
|
|
4
4
|
"description": "Parallel AI code review for GitHub pull requests in the Pi coding agent, with model-agnostic tiered subagents, structured findings, optional verification, and safe COMMENT-only publishing.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"url": "git+https://github.com/10ego/pi-pr-review.git"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "bun test"
|
|
34
|
+
"test": "bun test",
|
|
35
|
+
"verify:release-version": "node scripts/verify-release-version.mjs"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@earendil-works/pi-ai": "*",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
// SemVer numeric prerelease identifiers cannot contain leading zeroes.
|
|
6
|
+
const NUMERIC_IDENTIFIER = "(?:0|[1-9]\\d*)";
|
|
7
|
+
const PRERELEASE_IDENTIFIER = `(?:${NUMERIC_IDENTIFIER}|\\d*[A-Za-z-][0-9A-Za-z-]*)`;
|
|
8
|
+
const SEMVER = new RegExp(`^${NUMERIC_IDENTIFIER}\\.${NUMERIC_IDENTIFIER}\\.${NUMERIC_IDENTIFIER}(?:-${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$`);
|
|
9
|
+
|
|
10
|
+
function readJson(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
+
throw new Error(`Could not read ${filePath}: ${message}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function assertVersion(location, value) {
|
|
20
|
+
if (typeof value !== "string" || !SEMVER.test(value)) {
|
|
21
|
+
throw new Error(`${location} must contain a valid semantic version; received ${JSON.stringify(value)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Verify the root package metadata that Release Please updates in one release PR.
|
|
27
|
+
* This deliberately reads only: Release Please remains the sole version writer.
|
|
28
|
+
*/
|
|
29
|
+
export function verifyRootReleaseVersion(rootDir = process.cwd(), expectedVersion) {
|
|
30
|
+
const manifest = readJson(path.join(rootDir, ".release-please-manifest.json"));
|
|
31
|
+
const packageJson = readJson(path.join(rootDir, "package.json"));
|
|
32
|
+
const versions = {
|
|
33
|
+
'.release-please-manifest.json["."]': manifest["."],
|
|
34
|
+
"package.json.version": packageJson.version,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
for (const [location, version] of Object.entries(versions)) assertVersion(location, version);
|
|
38
|
+
if (expectedVersion !== undefined) assertVersion("expected release version", expectedVersion);
|
|
39
|
+
|
|
40
|
+
const [firstLocation, firstVersion] = Object.entries(versions)[0];
|
|
41
|
+
for (const [location, version] of Object.entries(versions).slice(1)) {
|
|
42
|
+
if (version !== firstVersion) {
|
|
43
|
+
throw new Error(`Root release version mismatch: ${firstLocation} is ${firstVersion}, but ${location} is ${version}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (expectedVersion !== undefined && firstVersion !== expectedVersion) {
|
|
47
|
+
throw new Error(`Root release version mismatch: ${firstLocation} is ${firstVersion}, but expected release version is ${expectedVersion}`);
|
|
48
|
+
}
|
|
49
|
+
return firstVersion;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function main(args) {
|
|
53
|
+
if (args.length === 0) return verifyRootReleaseVersion();
|
|
54
|
+
if (args.length === 2 && args[0] === "--expected") return verifyRootReleaseVersion(process.cwd(), args[1]);
|
|
55
|
+
throw new Error("Usage: node scripts/verify-release-version.mjs [--expected <version>]");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
59
|
+
try {
|
|
60
|
+
const version = main(process.argv.slice(2));
|
|
61
|
+
console.log(`Verified root release version ${version}.`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { verifyRootReleaseVersion } from "../scripts/verify-release-version.mjs";
|
|
6
|
+
|
|
7
|
+
type VersionFiles = {
|
|
8
|
+
manifest?: unknown;
|
|
9
|
+
packageJson?: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const tempDirs: string[] = [];
|
|
13
|
+
|
|
14
|
+
function writeVersionFiles(versions: VersionFiles = {}): string {
|
|
15
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-pr-review-release-version-test-"));
|
|
16
|
+
tempDirs.push(dir);
|
|
17
|
+
fs.writeFileSync(path.join(dir, ".release-please-manifest.json"), JSON.stringify({ ".": versions.manifest ?? "1.2.3" }));
|
|
18
|
+
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ version: versions.packageJson ?? "1.2.3" }));
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("root release-version invariant", () => {
|
|
27
|
+
test("accepts matching Release Please and npm metadata", () => {
|
|
28
|
+
expect(verifyRootReleaseVersion(writeVersionFiles())).toBe("1.2.3");
|
|
29
|
+
expect(verifyRootReleaseVersion(process.cwd())).toMatch(/^\d+\.\d+\.\d+$/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for (const [location, versions] of [
|
|
33
|
+
["Release Please manifest", { manifest: "1.2.2" }],
|
|
34
|
+
["package manifest", { packageJson: "1.2.2" }],
|
|
35
|
+
] satisfies [string, VersionFiles][]) {
|
|
36
|
+
test(`rejects a stale ${location}`, () => {
|
|
37
|
+
expect(() => verifyRootReleaseVersion(writeVersionFiles(versions))).toThrow(/Root release version mismatch/);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("rejects missing or malformed root versions", () => {
|
|
42
|
+
const missing = fs.mkdtempSync(path.join(os.tmpdir(), "pi-pr-review-release-version-test-"));
|
|
43
|
+
tempDirs.push(missing);
|
|
44
|
+
fs.writeFileSync(path.join(missing, ".release-please-manifest.json"), JSON.stringify({ ".": "1.2.3" }));
|
|
45
|
+
fs.writeFileSync(path.join(missing, "package.json"), JSON.stringify({}));
|
|
46
|
+
expect(() => verifyRootReleaseVersion(missing)).toThrow(/valid semantic version/);
|
|
47
|
+
expect(() => verifyRootReleaseVersion(writeVersionFiles({ packageJson: "not-a-version" }))).toThrow(/valid semantic version/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("rejects leading zeroes in numeric prerelease identifiers even when every field agrees", () => {
|
|
51
|
+
const invalidVersion = "1.2.3-01";
|
|
52
|
+
expect(() => verifyRootReleaseVersion(writeVersionFiles({
|
|
53
|
+
manifest: invalidVersion,
|
|
54
|
+
packageJson: invalidVersion,
|
|
55
|
+
}))).toThrow(/valid semantic version/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("binds publish verification to Release Please's emitted version", () => {
|
|
59
|
+
const dir = writeVersionFiles();
|
|
60
|
+
expect(verifyRootReleaseVersion(dir, "1.2.3")).toBe("1.2.3");
|
|
61
|
+
expect(() => verifyRootReleaseVersion(dir, "1.2.4")).toThrow(/expected release version/);
|
|
62
|
+
});
|
|
63
|
+
});
|