Skip to content

Commit 22ceada

Browse files
authored
fix: retry npm dist-tag verification (#2992)
1 parent 4cce3e2 commit 22ceada

3 files changed

Lines changed: 91 additions & 7 deletions

File tree

scripts/publish-packages.test.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ beforeEach(function setupPublishPackagesTestState() {
8484
});
8585

8686
afterEach(function restoreConsoleSpy() {
87+
jest.useRealTimers();
8788
consoleLogSpy.mockRestore();
8889
});
8990

@@ -207,7 +208,7 @@ describe("publishPackages", function describePublishPackages() {
207208
});
208209

209210
describe("verifyPackageDistTags", function describeVerifyPackageDistTags() {
210-
test("it accepts package dist-tags that point to the current versions", function testMatchingDistTags() {
211+
test("it accepts package dist-tags that point to the current versions", async function testMatchingDistTags() {
211212
publishPackagesExecFileSyncMock.mockImplementation(
212213
function mockExecFile(command, args, options) {
213214
execCalls.push({
@@ -220,10 +221,55 @@ describe("verifyPackageDistTags", function describeVerifyPackageDistTags() {
220221
},
221222
);
222223

223-
expect(() => verifyPackageDistTags("next")).not.toThrow();
224+
await expect(verifyPackageDistTags("next")).resolves.toBeUndefined();
224225
});
225226

226-
test("it explains how to repair mismatched package dist-tags", function testMismatchedDistTag() {
227+
test("it retries stale package dist-tags until npm returns the current version", async function testStaleDistTagRetry() {
228+
jest.useFakeTimers();
229+
230+
publishPackagesExecFileSyncMock.mockImplementation(
231+
function mockExecFile(command, args, options) {
232+
execCalls.push({
233+
command: String(command),
234+
args: Array.isArray(args) ? [...args] : [],
235+
options,
236+
});
237+
238+
if (
239+
command === "npm" &&
240+
Array.isArray(args) &&
241+
args[1] === "@daypicker/react@next"
242+
) {
243+
const packageReads = execCalls.filter(
244+
(call) => call.args[1] === "@daypicker/react@next",
245+
);
246+
return packageReads.length < 3
247+
? "10.0.0-next.0\n"
248+
: "10.0.0-next.1\n";
249+
}
250+
251+
return "10.0.0-next.1\n";
252+
},
253+
);
254+
255+
const verification = verifyPackageDistTags("next");
256+
await jest.advanceTimersByTimeAsync(10_000);
257+
await verification;
258+
259+
expect(
260+
execCalls
261+
.filter((call) => call.args[1] === "@daypicker/react@next")
262+
.map((call) => call.args),
263+
).toEqual([
264+
["view", "@daypicker/react@next", "version"],
265+
["view", "@daypicker/react@next", "version"],
266+
["view", "@daypicker/react@next", "version"],
267+
]);
268+
});
269+
270+
test("it explains how to repair mismatched package dist-tags", async function testMismatchedDistTag() {
271+
jest.useFakeTimers();
272+
227273
publishPackagesExecFileSyncMock.mockImplementation(
228274
function mockExecFile(command, args, options) {
229275
execCalls.push({
@@ -244,8 +290,12 @@ describe("verifyPackageDistTags", function describeVerifyPackageDistTags() {
244290
},
245291
);
246292

247-
expect(() => verifyPackageDistTags("next")).toThrow(
293+
const verification = verifyPackageDistTags("next");
294+
const expectation = expect(verification).rejects.toThrow(
248295
"Expected npm dist-tag next for @daypicker/react to point to 10.0.0-next.1, got 10.0.0-next.0. Trusted publishing only authenticates npm publish, so repair the tag manually with: npm dist-tag add @daypicker/react@10.0.0-next.1 next",
249296
);
297+
298+
await jest.advanceTimersByTimeAsync(60_000);
299+
await expectation;
250300
});
251301
});

scripts/publish-packages.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import process from "node:process";
44
import { pathToFileURL } from "node:url";
55

66
const repoRoot = new URL("../", import.meta.url);
7+
const distTagVerificationAttempts = 12;
8+
const distTagVerificationDelayMs = 5_000;
79

810
export const publishablePackageDirs = [
911
"packages/react-day-picker",
@@ -97,6 +99,35 @@ function readPackageVersionFromDistTag(
9799
}
98100
}
99101

102+
async function waitForDistTagVerificationRetry(): Promise<void> {
103+
await new Promise((resolve) => {
104+
setTimeout(resolve, distTagVerificationDelayMs);
105+
});
106+
}
107+
108+
async function readPackageVersionFromDistTagWithRetry(
109+
packageInfo: {
110+
name: string;
111+
version: string;
112+
},
113+
tag: string,
114+
): Promise<string | undefined> {
115+
let taggedVersion: string | undefined;
116+
117+
for (let attempt = 1; attempt <= distTagVerificationAttempts; attempt++) {
118+
taggedVersion = readPackageVersionFromDistTag(packageInfo, tag);
119+
if (taggedVersion === packageInfo.version) {
120+
return taggedVersion;
121+
}
122+
123+
if (attempt < distTagVerificationAttempts) {
124+
await waitForDistTagVerificationRetry();
125+
}
126+
}
127+
128+
return taggedVersion;
129+
}
130+
100131
export function getUnpublishedPackages(): Array<{
101132
packageDir: string;
102133
packageInfo: {
@@ -112,14 +143,17 @@ export function getUnpublishedPackages(): Array<{
112143
});
113144
}
114145

115-
export function verifyPackageDistTags(tag: string): void {
146+
export async function verifyPackageDistTags(tag: string): Promise<void> {
116147
if (!tag) {
117148
throw new Error("Usage: verifyPackageDistTags <npm-tag>");
118149
}
119150

120151
for (const packageDir of publishablePackageDirs) {
121152
const packageInfo = readPackageInfo(packageDir);
122-
const taggedVersion = readPackageVersionFromDistTag(packageInfo, tag);
153+
const taggedVersion = await readPackageVersionFromDistTagWithRetry(
154+
packageInfo,
155+
tag,
156+
);
123157
if (taggedVersion === packageInfo.version) {
124158
continue;
125159
}

scripts/release-ci.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export async function releaseCi(): Promise<{
9696

9797
console.log(`Publishing ${packageInfo.version} with dist-tag ${npmTag}.`);
9898
publishPackages(npmTag);
99-
verifyPackageDistTags(npmTag);
99+
await verifyPackageDistTags(npmTag);
100100
publishedPackages = true;
101101
} else {
102102
console.log("All publishable package versions are already on npm.");

0 commit comments

Comments
 (0)