paperclip/scripts/bootstrap-npm-package.test.mjs

130 lines
4.5 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
PLACEHOLDER_VERSION,
buildPlaceholderFiles,
parseArgs,
promptOtp,
validatePackageName,
waitForPackageVisible,
} from "./bootstrap-npm-package.mjs";
test("parseArgs recognizes the publish flag", () => {
assert.deepEqual(parseArgs(["@paperclipai/adapter-kimi-local", "--publish"]), {
help: false,
packageName: "@paperclipai/adapter-kimi-local",
publish: true,
});
});
test("parseArgs defaults to a dry run", () => {
assert.deepEqual(parseArgs(["@paperclipai/adapter-kimi-local"]), {
help: false,
packageName: "@paperclipai/adapter-kimi-local",
publish: false,
});
});
test("parseArgs rejects a second package name", () => {
assert.throws(() => parseArgs(["@paperclipai/a", "@paperclipai/b"]), /exactly one package name/);
});
test("parseArgs rejects unknown options", () => {
assert.throws(() => parseArgs(["@paperclipai/a", "--skip-build"]), /unknown option/);
assert.throws(() => parseArgs(["@paperclipai/a", "--otp", "123456"]), /unknown option/);
});
test("validatePackageName accepts @paperclipai scoped names", () => {
validatePackageName("@paperclipai/adapter-kimi-local");
validatePackageName("@paperclipai/plugin-workspace-diff");
});
test("validatePackageName rejects names outside the @paperclipai scope", () => {
assert.throws(() => validatePackageName("left-pad"), /@paperclipai scope/);
assert.throws(() => validatePackageName("@evil/adapter-kimi-local"), /@paperclipai scope/);
assert.throws(() => validatePackageName("@paperclipai/UPPER"), /@paperclipai scope/);
});
test("buildPlaceholderFiles produces a publishable manifest at the placeholder version", () => {
const files = buildPlaceholderFiles("@paperclipai/adapter-kimi-local");
const manifest = JSON.parse(files["package.json"]);
assert.equal(manifest.name, "@paperclipai/adapter-kimi-local");
assert.equal(manifest.version, PLACEHOLDER_VERSION);
assert.equal(manifest.publishConfig.access, "public");
assert.deepEqual(manifest.files, ["index.js"]);
assert.match(manifest.description, /[Pp]laceholder/);
});
test("buildPlaceholderFiles entry point throws with a pointer to the repo", () => {
const files = buildPlaceholderFiles("@paperclipai/adapter-kimi-local");
assert.match(files["index.js"], /^throw new Error\(/);
assert.match(files["index.js"], /placeholder/);
assert.match(files["index.js"], /github\.com\/paperclipai\/paperclip/);
// The entry point must be valid JS: evaluating it should throw our message,
// not a SyntaxError.
assert.throws(() => new Function(files["index.js"])(), /placeholder that reserves/);
});
test("buildPlaceholderFiles README explains the placeholder", () => {
const files = buildPlaceholderFiles("@paperclipai/adapter-kimi-local");
assert.match(files["README.md"], /placeholder publish/);
assert.match(files["README.md"], /release-bootstrap CI gate/);
});
test("promptOtp re-prompts until a non-empty code is entered", async () => {
const answers = ["", " ", " 123456 "];
const rl = { question: async () => answers.shift() ?? "" };
assert.equal(await promptOtp(rl, "publish"), "123456");
assert.equal(answers.length, 0);
});
test("waitForPackageVisible requires consecutive sightings before reporting success", async () => {
const states = [{ exists: false }, { exists: true }, { exists: false }, { exists: true }, { exists: true }];
let sleeps = 0;
const visible = await waitForPackageVisible("@paperclipai/x", {
attempts: 10,
consecutive: 2,
inspect: () => states.shift() ?? { exists: true },
sleep: async () => {
sleeps += 1;
},
});
assert.equal(visible, true);
// Five inspections happened; sleeps run between attempts, not before the first.
assert.equal(sleeps, 4);
});
test("waitForPackageVisible times out when the package never appears", async () => {
const visible = await waitForPackageVisible("@paperclipai/x", {
attempts: 3,
consecutive: 2,
inspect: () => ({ exists: false }),
sleep: async () => {},
});
assert.equal(visible, false);
});
test("waitForPackageVisible treats registry errors as misses and keeps polling", async () => {
let calls = 0;
const visible = await waitForPackageVisible("@paperclipai/x", {
attempts: 6,
consecutive: 2,
inspect: () => {
calls += 1;
if (calls <= 2) throw new Error("transient registry error");
return { exists: true };
},
sleep: async () => {},
});
assert.equal(visible, true);
assert.equal(calls, 4);
});