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
50 changes: 50 additions & 0 deletions scripts/openapi-mcp-release-startup.fixture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Replace only the release operation. Keep the entrypoint and installed plugin
// normalization/loading real, including dynamic imports of configured plugins.
import { registerHooks } from "node:module";

const pluginsUrl = new URL(
"./lib/plugins/index.js",
import.meta.resolve("semantic-release"),
).href;
const boundaryUrl = "test:semantic-release-operation";

registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === "semantic-release") {
return { url: boundaryUrl, shortCircuit: true };
}
return nextResolve(specifier, context);
},
load(url, context, nextLoad) {
if (url !== boundaryUrl) return nextLoad(url, context);
return {
format: "module",
shortCircuit: true,
source: `
import assert from "node:assert/strict";
import normalizePlugins from ${JSON.stringify(pluginsUrl)};

export default async function release(options, context) {
// Select the configured npm adapter without executing unrelated release hooks.
const adapterEntries = options.plugins.filter(entry => Array.isArray(entry) && entry[1]?.npmPublish === true);
assert.equal(adapterEntries.length, 1);
const logger = { log() {}, success() {}, warn() {}, error() {}, scope() { return this; } };
const input = { ...context, env: {}, options: { ...options, plugins: adapterEntries, dryRun: false }, logger };
const plugins = await normalizePlugins(input, {});
const expected = {
verifyConditions: "Protected npmrelease GitHub OIDC context and observed publisher readiness are required",
prepare: "Protected npmrelease GitHub OIDC context and observed publisher readiness are required",
publish: "No tested tarball is available for publication",
};
for (const [hook, message] of Object.entries(expected)) {
await assert.rejects(() => plugins[hook](input), error => {
const errors = error.errors ?? [error];
return errors.length === 1 && errors[0].message === message;
});
}
console.log("adapter guards reached: verifyConditions, prepare, publish");
}
`,
};
},
});
2 changes: 1 addition & 1 deletion scripts/openapi-mcp-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ if (
);
const plugins = manifest.release.plugins.map((entry) =>
Array.isArray(entry) && entry[0] === "@semantic-release/npm"
? [fileURLToPath(import.meta.url), entry[1]]
? [adapter, entry[1]]
: entry,
);
const { default: semanticRelease } = await import("semantic-release");
Expand Down
22 changes: 22 additions & 0 deletions scripts/openapi-mcp-release.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { expect, spyOn, test } from "bun:test";
import { spawnSync } from "node:child_process";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import * as release from "./openapi-mcp-release.mjs";
import {
createReleaseAdapter,
verifyAuditBinding,
} from "./openapi-mcp-release.mjs";

test("release CLI initializes its configured adapter through the installed plugin loader", () => {
const result = spawnSync(
"node",
[
"--import",
fileURLToPath(
new URL("./openapi-mcp-release-startup.fixture.mjs", import.meta.url),
),
fileURLToPath(new URL("./openapi-mcp-release.mjs", import.meta.url)),
],
{ encoding: "utf8", timeout: 15_000, env: { PATH: process.env.PATH } },
);
expect(result.error).toBeUndefined();
expect(result.stderr).toBe("");
expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe(
"adapter guards reached: verifyConditions, prepare, publish",
);
});

test("registry readiness retries packument visibility and accepts only the exact release", async () => {
expect(typeof release.waitForRegistryVersion).toBe("function");
const responses = [
Expand Down
Loading