* fix(#623): make release Verify-publish tolerant of npm propagation lag The rc and latest Verify-publish steps used a single `sleep 10` + one `npm view`, which false-failed the whole release job when npm's registry read lagged the publish write — observed on the v1.3.0-rc.1 RC run, where publish/tag/GitHub-release all succeeded but verification reported NOT_FOUND. Extract the check into scripts/verify-npm-publish.cjs: a testable module with a bounded retry/poll loop, a frozen REASON enum, and a --json mode (mirrors verify-reapply-patches.cjs). Both workflow steps now call it. dist-tag reporting stays informational and never fails the step, matching prior behavior. Adds tests/verify-npm-publish.test.cjs covering retry, exhaustion, and dist-tag reporting via injected lookups (no network). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#623): allowlist verify-npm-publish.test.cjs in the verify cluster The test-file-count linter groups test files by production-module prefix. scripts/verify-npm-publish.cjs lives under scripts/ (not a scanned prod dir), so its test collapses into the existing `verify` module via the startsWith(prefix + '-') rule — same as scripts/verify-reapply-patches.cjs, whose tests are already allowlisted under `verify`. Add the new test to that cluster's allowlisted set to satisfy the identity ratchet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
149 lines
4.1 KiB
JavaScript
149 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
|
|
const SCRIPT = path.join(__dirname, '..', 'scripts', 'verify-npm-publish.cjs');
|
|
const { verifyPublish, REASON } = require(SCRIPT);
|
|
|
|
// ---- Helpers -----------------------------------------------------------------
|
|
|
|
function makeFetchVersion(sequence) {
|
|
let i = 0;
|
|
return () => sequence[Math.min(i++, sequence.length - 1)];
|
|
}
|
|
|
|
function makeSleepSpy() {
|
|
let sleeps = 0;
|
|
const sleep = async () => { sleeps++; };
|
|
const count = () => sleeps;
|
|
return { sleep, count };
|
|
}
|
|
|
|
// ---- Tests -------------------------------------------------------------------
|
|
|
|
describe('verifyPublish', () => {
|
|
test('returns OK on first attempt when version is already live', async () => {
|
|
const { sleep, count } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion(['1.3.0-rc.1']);
|
|
const fetchDistTag = makeFetchVersion([null]);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.reason, REASON.OK_VERSION_LIVE);
|
|
assert.equal(result.attempts, 1);
|
|
assert.equal(count(), 0);
|
|
});
|
|
|
|
test('retries through propagation lag and succeeds once the version appears', async () => {
|
|
const { sleep, count } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion([null, null, '1.3.0-rc.1']);
|
|
const fetchDistTag = makeFetchVersion([null]);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.attempts, 3);
|
|
assert.equal(count(), 2);
|
|
});
|
|
|
|
test('fails after exhausting maxAttempts when version never appears', async () => {
|
|
const { sleep, count } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion([null]);
|
|
const fetchDistTag = makeFetchVersion([null]);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
maxAttempts: 4,
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.equal(result.reason, REASON.FAIL_VERSION_NOT_FOUND);
|
|
assert.equal(result.attempts, 4);
|
|
assert.equal(count(), 3);
|
|
});
|
|
|
|
test('reports dist-tag pointer informationally without affecting ok', async () => {
|
|
const { sleep, count } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion(['1.3.0-rc.1']);
|
|
const fetchDistTag = makeFetchVersion(['1.3.0-rc.1']);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
distTag: 'next',
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.deepEqual(result.distTag, {
|
|
name: 'next',
|
|
points_to: '1.3.0-rc.1',
|
|
matches: true,
|
|
});
|
|
void count;
|
|
});
|
|
|
|
test('dist-tag mismatch is a warning, not a failure', async () => {
|
|
const { sleep } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion(['1.3.0-rc.1']);
|
|
const fetchDistTag = makeFetchVersion(['1.2.0']);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
distTag: 'latest',
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.distTag.matches, false);
|
|
assert.equal(result.distTag.points_to, '1.2.0');
|
|
});
|
|
|
|
test('no dist-tag requested yields null distTag', async () => {
|
|
const { sleep } = makeSleepSpy();
|
|
const fetchVersion = makeFetchVersion(['1.3.0-rc.1']);
|
|
const fetchDistTag = makeFetchVersion([null]);
|
|
|
|
const result = await verifyPublish({
|
|
pkg: '@opengsd/gsd-core',
|
|
version: '1.3.0-rc.1',
|
|
fetchVersion,
|
|
fetchDistTag,
|
|
sleep,
|
|
intervalMs: 0,
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.distTag, null);
|
|
});
|
|
});
|