Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bindings/go/sysprims/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Go bindings for the `sysprims` Rust process primitives.
## Install

```bash
go get github.com/3leaps/sysprims/bindings/go/sysprims@v0.2.2
go get github.com/3leaps/sysprims/bindings/go/sysprims@v0.2.3
```

The Go module resolves `v0.2.2` through the repository's path-prefixed
`bindings/go/sysprims/v0.2.2` tag. That tag and the canonical `v0.2.2` tag
The Go module resolves `v0.2.3` through the repository's path-prefixed
`bindings/go/sysprims/v0.2.3` tag. That tag and the canonical `v0.2.3` tag
identify the same reviewed commit.

### Windows toolchain
Expand Down
113 changes: 110 additions & 3 deletions scripts/version-pack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ const baseOwnedPaths = [
"VERSION",
"Cargo.toml",
"Cargo.lock",
"bindings/go/sysprims/README.md",
"bindings/typescript/sysprims/package.json",
"bindings/typescript/sysprims/package-lock.json",
...nativeDirectories.map(
(directory) =>
`bindings/typescript/sysprims/npm/${directory}/package.json`,
),
];
const semverPattern =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
const semverSource =
"(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-(?:(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\\+(?:[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?";
const semverPattern = new RegExp(`^${semverSource}$`);

function fail(message) {
throw new Error(message);
Expand Down Expand Up @@ -320,6 +322,64 @@ function checkJson(root, expected, errors) {
}
}

function extractUniqueRequiredVersion(text, pattern, label, expected, errors) {
const matches = [...text.matchAll(pattern)];
if (matches.length === 0) {
errors.push(`${label} is missing`);
return;
}
if (matches.length !== 1) {
const versions = matches.map((match) => match[1]).join(", ");
errors.push(`${label} appears ${matches.length} times: ${versions}`);
return;
}
const match = matches[0];
if (match[1] !== expected) {
errors.push(`${label} is ${match[1]}, expected ${expected}`);
}
}

function checkGoReadme(root, expected, errors) {
const readme = readFileSync(
join(root, "bindings/go/sysprims/README.md"),
"utf8",
);
extractUniqueRequiredVersion(
readme,
new RegExp(
`go get github\\.com\\/3leaps\\/sysprims\\/bindings\\/go\\/sysprims@v(${semverSource})`,
"g",
),
"Go README install version",
expected,
errors,
);
extractUniqueRequiredVersion(
readme,
new RegExp(`The Go module resolves \`v(${semverSource})\``, "g"),
"Go README module version",
expected,
errors,
);
extractUniqueRequiredVersion(
readme,
new RegExp(
`\`bindings\\/go\\/sysprims\\/v(${semverSource})\` tag`,
"g",
),
"Go README path-prefixed tag version",
expected,
errors,
);
extractUniqueRequiredVersion(
readme,
new RegExp(`canonical \`v(${semverSource})\` tag`, "g"),
"Go README canonical tag version",
expected,
errors,
);
}

function collectErrors(root) {
const errors = [];
let expected;
Expand All @@ -339,6 +399,11 @@ function collectErrors(root) {
} catch (error) {
errors.push(error.message);
}
try {
checkGoReadme(root, expected, errors);
} catch (error) {
errors.push(error.message);
}
return errors;
}

Expand Down Expand Up @@ -424,6 +489,47 @@ function updateCargoWorkspaceDependencyPins(root, version) {
writeTextAtomic(cargoPath, cargoToml);
}

function replaceExactlyOnce(text, pattern, replacement, label) {
const matches = [...text.matchAll(pattern)];
if (matches.length !== 1) {
fail(`expected exactly one ${label} replacement, found ${matches.length}`);
}
return text.replace(pattern, replacement);
}

function updateGoReadme(root, version) {
const readmePath = join(root, "bindings/go/sysprims/README.md");
let readme = readFileSync(readmePath, "utf8");
readme = replaceExactlyOnce(
readme,
new RegExp(
`(go get github\\.com\\/3leaps\\/sysprims\\/bindings\\/go\\/sysprims@)v${semverSource}`,
"g",
),
(_match, prefix) => `${prefix}v${version}`,
"Go README install version",
);
readme = replaceExactlyOnce(
readme,
new RegExp(`(The Go module resolves \`)v${semverSource}(\`)`, "g"),
(_match, prefix, suffix) => `${prefix}v${version}${suffix}`,
"Go README module version",
);
readme = replaceExactlyOnce(
readme,
new RegExp(`(\`bindings\\/go\\/sysprims\\/)v${semverSource}(\` tag)`, "g"),
(_match, prefix, suffix) => `${prefix}v${version}${suffix}`,
"Go README path-prefixed tag version",
);
readme = replaceExactlyOnce(
readme,
new RegExp(`(canonical \`)v${semverSource}(\` tag)`, "g"),
(_match, prefix, suffix) => `${prefix}v${version}${suffix}`,
"Go README canonical tag version",
);
writeTextAtomic(readmePath, readme);
}

function withRollback(root, paths, operation) {
const backupRoot = mkdtempSync(join(tmpdir(), "sysprims-version-pack-"));
try {
Expand Down Expand Up @@ -464,6 +570,7 @@ export function synchronize(
updateCargoWorkspaceDependencyPins(root, version);
afterCargoSetVersion?.();
updateJsonSurfaces(root, version);
updateGoReadme(root, version);
check(root, true);
});

Expand All @@ -473,7 +580,7 @@ export function synchronize(
function bump(root, component) {
const current = readCanonicalVersion(root);
const match = current.match(semverPattern);
if (match[4] || match[5]) {
if (current.includes("-") || current.includes("+")) {
fail(`cannot ${component}-bump prerelease/build version ${current}`);
}
let [major, minor, patch] = current.split(".").map(Number);
Expand Down
80 changes: 80 additions & 0 deletions scripts/version-pack.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ path = "src/lib.rs"
);
writeFileSync(join(root, "crate", "src", "lib.rs"), "pub fn fixture() {}\n");
mustRun(root, "cargo", ["generate-lockfile"]);
mkdirSync(join(root, "bindings/go/sysprims"), { recursive: true });
writeFileSync(
join(root, "bindings/go/sysprims/README.md"),
`# sysprims Go bindings

\`\`\`bash
go get github.com/3leaps/sysprims/bindings/go/sysprims@v0.2.1
\`\`\`

The Go module resolves \`v0.2.1\` through the repository's path-prefixed
\`bindings/go/sysprims/v0.2.1\` tag. That tag and the canonical \`v0.2.1\` tag
identify the same reviewed commit.

v0.1.14 remains part of the historical API notes.
`,
);

const optionalDependencies = Object.fromEntries(
platformNames.map((name) => [name, "0.2.1"]),
Expand Down Expand Up @@ -252,9 +268,37 @@ test("sync updates owned fields and removes only stale resolution evidence", ()
readFileSync(join(root, "Cargo.toml"), "utf8"),
/sysprims-fixture = \{ version = "0\.2\.2", path = "crate" \}/,
);
const goReadme = readFileSync(
join(root, "bindings/go/sysprims/README.md"),
"utf8",
);
assert.match(
goReadme,
/go get github\.com\/3leaps\/sysprims\/bindings\/go\/sysprims@v0\.2\.2/,
);
assert.match(goReadme, /The Go module resolves `v0\.2\.2`/);
assert.match(goReadme, /`bindings\/go\/sysprims\/v0\.2\.2` tag/);
assert.match(goReadme, /canonical `v0\.2\.2` tag/);
assert.match(goReadme, /v0\.1\.14 remains/);
assert.equal(version(root, "check").status, 0);
});

test("check fails precisely when the Go README install version is stale", () => {
const root = createFixture();
const readmePath = join(root, "bindings/go/sysprims/README.md");
writeFileSync(
readmePath,
readFileSync(readmePath, "utf8").replace(
/sysprims@v0\.2\.1/,
"sysprims@v0.2.0",
),
);

const result = version(root, "check");
assert.notEqual(result.status, 0);
assert.match(result.stderr, /Go README install version is 0\.2\.0/);
});

test("check calls out stale npm resolution evidence without rewriting it", () => {
const root = createFixture();
const lockPath = join(
Expand Down Expand Up @@ -283,6 +327,42 @@ test("set canonicalizes VERSION and synchronizes in one command", () => {
assert.equal(version(root, "check").status, 0);
});

test("set synchronizes prerelease versions through Go README coordinates", () => {
const root = createFixture();
const result = version(root, "set", "0.2.2-rc.1");
assert.equal(result.status, 0, `${result.stderr}${result.stdout}`);

const goReadme = readFileSync(
join(root, "bindings/go/sysprims/README.md"),
"utf8",
);
assert.match(
goReadme,
/go get github\.com\/3leaps\/sysprims\/bindings\/go\/sysprims@v0\.2\.2-rc\.1/,
);
assert.match(goReadme, /The Go module resolves `v0\.2\.2-rc\.1`/);
assert.match(goReadme, /`bindings\/go\/sysprims\/v0\.2\.2-rc\.1` tag/);
assert.match(goReadme, /canonical `v0\.2\.2-rc\.1` tag/);
assert.equal(version(root, "check").status, 0);
});

test("check rejects duplicate Go README release coordinates", () => {
const root = createFixture();
const readmePath = join(root, "bindings/go/sysprims/README.md");
writeFileSync(
readmePath,
`${readFileSync(readmePath, "utf8")}\n` +
"go get github.com/3leaps/sysprims/bindings/go/sysprims@v0.2.0\n",
);

const result = version(root, "check");
assert.notEqual(result.status, 0);
assert.match(
result.stderr,
/Go README install version appears 2 times: 0\.2\.1, 0\.2\.0/,
);
});

test("patch, minor, and major bumps each synchronize the full pack", () => {
const root = createFixture();
for (const [component, expected] of [
Expand Down