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
18 changes: 14 additions & 4 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
version:
description: 'Version already committed in js/package.json (e.g. 2.8.0)'
required: true
verify_only:
description: 'Verify an already-published version without publishing again'
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -55,18 +59,23 @@ jobs:
# npm 11.21 supports private dist-tag reads through trusted publishing.
- run: npm install --global npm@11.21.0
- name: Check npm publishing access
run: npm dist-tag ls @dev.smartpricing/traceflow-sdk --json
run: npm dist-tag ls @dev.smartpricing/traceflow-sdk
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN || secrets.NO_TWOFA_NPM_TOKEN }}

# Kafka is needed only for its declarations; do not build its native addon.
- run: npm ci --ignore-scripts
if: inputs.verify_only != true
- run: npm run typecheck:errors && npm run test:release && npm test -- --run
if: inputs.verify_only != true
- run: npm run build
if: inputs.verify_only != true
- name: Verify the npm archive
if: inputs.verify_only != true
run: npm run test:package -- "$RUNNER_TEMP/traceflow-sdk.tgz"

- name: Publish to npm
if: inputs.verify_only != true
run: npm publish "$RUNNER_TEMP/traceflow-sdk.tgz" --tag latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN || secrets.NO_TWOFA_NPM_TOKEN }}
Expand All @@ -80,10 +89,11 @@ jobs:
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { appendFileSync, readFileSync } from 'node:fs';
import { readLatestVersion } from './scripts/release-version.mjs';
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
const tags = JSON.parse(execFileSync('npm', ['dist-tag', 'ls', pkg.name, '--json'], { encoding: 'utf8' }));
assert.equal(tags.latest, pkg.version);
const message = `Published ${pkg.name}@${pkg.version}; npm latest verified. The published archive passed export, CJS/ESM import, and frontend bundle checks.`;
const tags = execFileSync('npm', ['dist-tag', 'ls', pkg.name], { encoding: 'utf8' });
assert.equal(readLatestVersion(tags), pkg.version);
const message = `Verified npm latest: ${pkg.name}@${pkg.version}.`;
console.log(message);
appendFileSync(process.env.GITHUB_STEP_SUMMARY, message + '\n');
NODE
7 changes: 7 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ For recovery after a failed publish, run the workflow with the committed version
gh workflow run workflow.yaml --ref main -f version=2.8.0
```

If publishing succeeded and only the final check failed, verify the existing
release without attempting to publish the same version again:

```sh
gh workflow run workflow.yaml --ref main -f version=2.8.0 -f verify_only=true
```

The workflow tests and builds every entry point, installs and verifies the npm
archive, then publishes that same archive. It verifies npm's `latest` tag after
publishing. The existing `release/X.Y.Z` flow is also supported: commit version
Expand Down
16 changes: 15 additions & 1 deletion js/scripts/release-version.check.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { shouldRelease } from "./release-version.mjs";
import { readLatestVersion, shouldRelease } from "./release-version.mjs";

test("read the plain-text output returned by npm dist-tag ls", () => {
assert.equal(readLatestVersion("latest: 2.8.0\n"), "2.8.0");
});

test("select latest when the registry also contains other tags", () => {
assert.equal(readLatestVersion("beta: 3.0.0-beta.1\nlatest: 2.8.0\nnext: 2.9.0\n"), "2.8.0");
});

test("reject missing or invalid latest tags", () => {
for (const output of ["", "next: 2.8.0\n", "latest: \nnext: 2.8.0\n", "latest: invalid\n"]) {
assert.throws(() => readLatestVersion(output));
}
});

const main = { event: "push", branch: "main", version: "2.8.0", previous: "2.8.0" };

Expand Down
8 changes: 8 additions & 0 deletions js/scripts/release-version.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import { fileURLToPath } from "node:url";

const stableVersion = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;

export function readLatestVersion(output) {
// npm dist-tag ls emits text even when passed --json.
const match = output.match(/^latest:[ \t]+(\S+)[ \t]*$/m);
assert(match, "npm did not return a latest tag");
assert.match(match[1], stableVersion, "npm latest must be a stable version");
return match[1];
}

export function shouldRelease({ event, branch, requested, version, previous }) {
assert.match(version, stableVersion, "Releases must use a stable X.Y.Z version");
if (event === "workflow_dispatch") {
Expand Down
Loading