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
2 changes: 1 addition & 1 deletion .github/scripts/prepare-memos-release.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ test("legacy standalone local-plugin publisher requires an extra non-dry-run con
assert.match(workflow, /ALLOW_STAGED_TAG_BEFORE_NPM/);
assert.match(workflow, /audit-local-plugin-package\.mjs/);
assert.match(workflow, /wait-for-local-plugin-npm-release\.test\.mjs/);
assert.match(workflow, /NPM_VISIBILITY_TIMEOUT_SECONDS: "150"/);
assert.match(workflow, /NPM_VISIBILITY_TIMEOUT_SECONDS: "600"/);
assert.match(workflow, /FORCE_PACKAGE_ONLY_RELEASE: \$\{\{ inputs\.tag != 'latest' \|\| contains\(inputs\.version, '-'\) \}\}/);
assert.match(workflow, /if \[ -n "\$\{DOCS_SYNC_MODE\}" \]; then/);
assert.doesNotMatch(workflow, /EVENT_NAME: \$\{\{ github\.event_name \}\}/);
Expand Down
4 changes: 2 additions & 2 deletions .github/scripts/publish-local-plugin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if [ ! -s "${RELEASE_TARBALL}" ]; then
exit 2
fi

npm_visibility_timeout_seconds="${NPM_VISIBILITY_TIMEOUT_SECONDS:-150}"
npm_visibility_timeout_seconds="${NPM_VISIBILITY_TIMEOUT_SECONDS:-600}"
npm_visibility_interval_seconds="${NPM_VISIBILITY_INTERVAL_SECONDS:-10}"
npm_visibility_request_timeout_seconds="${NPM_VISIBILITY_REQUEST_TIMEOUT_SECONDS:-8}"
npm_registry_url="https://registry.npmjs.org"
Expand Down Expand Up @@ -336,7 +336,7 @@ if npm_version_exists; then
published_version_visible=true
published_version_preexisting=true
if [ "${RECOVER_EXISTING_NPM_RELEASE:-false}" != "true" ]; then
echo "::error::${PACKAGE_NAME}@${RELEASE_VERSION} already exists. Normal releases require an unused version; enable recovery only after release-owner verification of a partial failure."
echo "::error::${PACKAGE_NAME}@${RELEASE_VERSION} already exists. Normal releases require an unused version; inspect npm first and use explicit recovery only after release-owner verification of a partial failure."
exit 1
fi
if remote_tag_exists "${RELEASE_TAG}"; then
Expand Down
6 changes: 4 additions & 2 deletions .github/scripts/publish-local-plugin.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ if [[ "\${1:-}" == *"wait-for-local-plugin-npm-release.mjs" ]]; then
printf '%s' "\${count}" > "\${increment_file}"
case "\${NPM_MOCK_SCENARIO}" in
always-missing|publish-fails)
echo "::error::npm release was not fully visible within 150s"
echo "::error::npm release was not fully visible within \${NPM_VISIBILITY_TIMEOUT_SECONDS:-600}s"
exit 1
;;
integrity-mismatch)
Expand All @@ -130,7 +130,7 @@ if [[ "\${1:-}" == *"wait-for-local-plugin-npm-release.mjs" ]]; then
;;
*)
if [ "\${NPM_MOCK_DIST_TAG_VERSION:-\${RELEASE_VERSION}}" != "\${RELEASE_VERSION}" ]; then
echo "::error::npm release was not fully visible within 150s: dist-tag \${NPM_DIST_TAG} did not point to \${RELEASE_VERSION}"
echo "::error::npm release was not fully visible within \${NPM_VISIBILITY_TIMEOUT_SECONDS:-600}s: dist-tag \${NPM_DIST_TAG} did not point to \${RELEASE_VERSION}"
exit 1
fi
echo '{"ok":true,"attempts":3}'
Expand Down Expand Up @@ -330,6 +330,7 @@ test("stops before tag creation when publish succeeds but visibility remains del
assert.equal(result.metadataWaitCount, 1);
assert.equal(result.packCount, 0);
assert.match(result.stdout + result.stderr, /Refusing to issue a second publish request/);
assert.match(result.stdout + result.stderr, /within 600s/);
});

test("allows npm publish after a staged paired Draft Release only in npm-only phase", () => {
Expand Down Expand Up @@ -461,4 +462,5 @@ test("rejects an already-used npm version outside explicit recovery", () => {
assert.equal(result.publishCount, 0);
assert.equal(result.packCount, 0);
assert.match(result.stdout + result.stderr, /Normal releases require an unused version/);
assert.match(result.stdout + result.stderr, /inspect npm first/);
});
10 changes: 8 additions & 2 deletions .github/scripts/wait-for-local-plugin-npm-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";

const INTEGRITY_PATTERN = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
export const DEFAULT_NPM_VISIBILITY_TIMEOUT_SECONDS = 600;

function clean(value) {
return String(value ?? "").trim();
Expand Down Expand Up @@ -103,7 +104,7 @@ export async function waitForNpmReleaseVisibility(
distTag,
expectedIntegrity,
registryUrl = "https://registry.npmjs.org",
timeoutMs = 150_000,
timeoutMs = DEFAULT_NPM_VISIBILITY_TIMEOUT_SECONDS * 1000,
intervalMs = 10_000,
requestTimeoutMs = 8_000,
} = {},
Expand Down Expand Up @@ -208,7 +209,12 @@ export async function run(env = process.env) {
distTag: env.NPM_DIST_TAG,
expectedIntegrity,
registryUrl: env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org",
timeoutMs: positiveInteger(env.NPM_VISIBILITY_TIMEOUT_SECONDS, 150, "timeout") * 1000,
timeoutMs:
positiveInteger(
env.NPM_VISIBILITY_TIMEOUT_SECONDS,
DEFAULT_NPM_VISIBILITY_TIMEOUT_SECONDS,
"timeout",
) * 1000,
intervalMs: positiveInteger(env.NPM_VISIBILITY_INTERVAL_SECONDS, 10, "interval") * 1000,
requestTimeoutMs:
positiveInteger(env.NPM_VISIBILITY_REQUEST_TIMEOUT_SECONDS, 8, "request timeout") * 1000,
Expand Down
33 changes: 33 additions & 0 deletions .github/scripts/wait-for-local-plugin-npm-release.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { join } from "node:path";
import test from "node:test";

import {
DEFAULT_NPM_VISIBILITY_TIMEOUT_SECONDS,
inspectNpmReleaseVisibility,
npmPackumentUrl,
tarballIntegrity,
Expand Down Expand Up @@ -224,3 +225,35 @@ test("uses a hard deadline for an unavailable registry version", async () => {
assert.equal(clock, 30_000);
assert.equal(attempts, 3);
});

test("uses the ten-minute default visibility deadline", async () => {
let clock = 0;
let attempts = 0;
await assert.rejects(
waitForNpmReleaseVisibility(
{
packageName: "@memtensor/memos-local-plugin",
version: "2.0.14",
distTag: "latest",
expectedIntegrity: integrity,
intervalMs: 10_000,
requestTimeoutMs: 1_000,
},
{
fetchImpl: async () => {
attempts += 1;
return response({}, 404);
},
sleep: async (milliseconds) => {
clock += milliseconds;
},
now: () => clock,
log: () => {},
},
),
/not fully visible within 600s/,
);
assert.equal(DEFAULT_NPM_VISIBILITY_TIMEOUT_SECONDS, 600);
assert.equal(clock, 600_000);
assert.equal(attempts, 60);
});
4 changes: 2 additions & 2 deletions .github/workflows/memos-local-plugin-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ on:
type: boolean
default: true
recover_existing_npm_release:
description: "Allow reconstructing a missing tag for an existing npm version. Keep false for normal releases."
description: "Recover a verified partial publish only; keep false for normal releases."
required: true
type: boolean
default: false
Expand Down Expand Up @@ -799,7 +799,7 @@ jobs:
NPM_DIST_TAG: ${{ inputs.tag }}
RECOVER_EXISTING_NPM_RELEASE: ${{ inputs.recover_existing_npm_release }}
RELEASE_METADATA_STATE: ${{ steps.release_state.outputs.state }}
NPM_VISIBILITY_TIMEOUT_SECONDS: "150"
NPM_VISIBILITY_TIMEOUT_SECONDS: "600"
NPM_VISIBILITY_INTERVAL_SECONDS: "10"
NPM_VISIBILITY_REQUEST_TIMEOUT_SECONDS: "8"
ALLOW_STAGED_TAG_BEFORE_NPM: ${{ (inputs.publish_phase || 'full') == 'publish_npm_only' && 'true' || 'false' }}
Expand Down
Loading