import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { CLOUDFLARE_AUTH_REFUSED, CLOUDFLARE_NO_CREDENTIALS, MAX_PREVIEW_BRANCH, PRODUCTION_BRANCH, WRANGLER_MAJOR, cloudflareCredentialProblem, deploymentUrlIn, pagesDeployArgs, previewAliasUrl, previewBranchProblem, wranglerAuthFailureIn, wranglerBin, wranglerOAuthConfigFiles, } from "./pagesDeploy"; test("previewBranchProblem accepts ordinary preview names", () => { for (const ok of [ "preview", "p", "7", "tags-exclude", "rc-2026-09-23", "a-b-c-d", ]) { assert.equal(previewBranchProblem(ok), null, `expected "${ok}" to be valid`); } }); test("previewBranchProblem trims before judging", () => { assert.equal(previewBranchProblem(" preview \n"), null); // ...and the trimmed value, not the raw one, is what the complaint names. assert.match(previewBranchProblem(" main ") ?? "", /"main" is the production branch/); }); test("previewBranchProblem refuses the production branch names", () => { for (const branch of ["main", "master", "production"]) { const problem = previewBranchProblem(branch); assert.ok(problem, `expected "${branch}" to be refused`); assert.match(problem, /production branch/); assert.ok(problem.includes(branch)); } }); test("previewBranchProblem refuses uppercase rather than lowercasing it", () => { const problem = previewBranchProblem("Main"); assert.ok(problem); // Not the production-branch sentence: "Main" is refused for its shape, and // the point is that we never silently rewrite it into "main" (production). assert.match(problem, /not a valid preview branch name/); assert.match(problem, /lowercase/); }); test("previewBranchProblem refuses shapes Cloudflare's alias sanitizer would rewrite", () => { for (const bad of ["bad name", "-lead", "trail-", "under_score", "dot.dot", "sl/ash"]) { assert.ok(previewBranchProblem(bad), `expected "${bad}" to be refused`); } }); test("previewBranchProblem is length-bounded at 28, not 29", () => { assert.equal(MAX_PREVIEW_BRANCH, 28); const twentyEight = "a".repeat(28); const twentyNine = "a".repeat(29); assert.equal(twentyEight.length, 28); assert.equal(previewBranchProblem(twentyEight), null); const problem = previewBranchProblem(twentyNine); assert.ok(problem); assert.match(problem, /at most 28 characters/); assert.match(problem, /is 29/); }); test("previewBranchProblem refuses empty and non-strings", () => { assert.ok(previewBranchProblem("")); assert.ok(previewBranchProblem(" ")); assert.ok(previewBranchProblem(undefined)); assert.ok(previewBranchProblem(null)); assert.ok(previewBranchProblem(42)); assert.match(previewBranchProblem(42) ?? "", /must be a string/); }); test("pagesDeployArgs: production names branch main, never left to the checkout", () => { assert.deepEqual( pagesDeployArgs({ outDir: "/repo/export/out", project: "anilyzer" }), ["pages", "deploy", "/repo/export/out", "--project-name", "anilyzer", "--branch", "main"], ); // An explicitly-empty / whitespace preview is the same thing as none. for (const blank of ["", " "]) { assert.deepEqual( pagesDeployArgs({ outDir: "/o", project: "p", previewBranch: blank }), ["pages", "deploy", "/o", "--project-name", "p", "--branch", "main"], ); } assert.equal(PRODUCTION_BRANCH, "main"); }); test("pagesDeployArgs names the preview branch, and only it", () => { assert.deepEqual( pagesDeployArgs({ outDir: "/repo/export/out", project: "anilyzer", previewBranch: "tags-exclude", }), ["pages", "deploy", "/repo/export/out", "--project-name", "anilyzer", "--branch", "tags-exclude"], ); const args = pagesDeployArgs({ outDir: "/o", project: "p", previewBranch: " x " }); assert.deepEqual(args.slice(-2), ["--branch", "x"]); assert.equal(args.filter((a) => a === "--branch").length, 1); }); test("wranglerBin: WRANGLER_BIN when set, else common's pinned binary", () => { const paths = { monorepoRoot: "/repo" }; assert.equal(wranglerBin(paths, {}), "/repo/common/node_modules/.bin/wrangler"); assert.equal(wranglerBin(paths, { WRANGLER_BIN: " " }), "/repo/common/node_modules/.bin/wrangler"); assert.equal(wranglerBin(paths, { WRANGLER_BIN: "/fake/wrangler" }), "/fake/wrangler"); }); test("wrangler is pinned EXACTLY in common's devDependencies, at WRANGLER_MAJOR", () => { const pkg = JSON.parse( readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8"), ) as { devDependencies?: Record; dependencies?: Record }; const pin = pkg.devDependencies?.wrangler; assert.ok(pin, "common/package.json has no wrangler devDependency"); assert.match(pin, /^\d+\.\d+\.\d+$/, `"${pin}" is a range, not an exact pin`); assert.equal(Number(pin.split(".")[0]), WRANGLER_MAJOR); assert.equal(pkg.dependencies?.wrangler, undefined); }); test("wranglerAuthFailureIn: Cloudflare's refusals and wrangler's missing-login sentences", () => { for (const line of [ " Authentication error [code: 10000]", "✘ [ERROR] A request to the Cloudflare API (/accounts/x/pages/projects/y) failed. Authentication error [code: 10000]", "Invalid access token [code: 9109]", "Unable to authenticate request [code: 10001]", // A malformed token (CLOUDFLARE_API_TOKEN=bogus, the container smoke): " Invalid request headers [code: 6003]", " Invalid format for Authorization header [code: 6111]", "✘ [ERROR] A request to the Cloudflare API (/accounts/x/pages/projects/y) failed. Invalid request headers [code: 6003]", "In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work.", "You are not authenticated. Please run `wrangler login`.", "Failed to refresh OAuth token", ]) { assert.equal(wranglerAuthFailureIn(line), true, line); } for (const line of [ "✨ Success! Uploaded 12 files (40 already uploaded)", "Project not found. The specified project name does not match any of your existing projects. [code: 8000007]", "Take a peek over at https://abc123.anilyzer.pages.dev", "Invalid request headers", "[code: 6003]", "", ]) { assert.equal(wranglerAuthFailureIn(line), false, line); } assert.equal(CLOUDFLARE_AUTH_REFUSED, "[deploy] REFUSED by Cloudflare — the API token was not accepted"); }); test("cloudflareCredentialProblem: a token or an OAuth login; else the .env sentence", () => { assert.equal(cloudflareCredentialProblem({ CLOUDFLARE_API_TOKEN: "t" }, false), null); assert.equal(cloudflareCredentialProblem({}, true), null); const none = cloudflareCredentialProblem({ CLOUDFLARE_API_TOKEN: " " }, false); assert.equal(none, CLOUDFLARE_NO_CREDENTIALS); assert.match(none ?? "", /set CLOUDFLARE_API_TOKEN in \.env/); }); test("wranglerOAuthConfigFiles: the legacy dir, XDG, and macOS Preferences", () => { assert.deepEqual(wranglerOAuthConfigFiles("/home/u", {}), [ "/home/u/.wrangler/config/default.toml", "/home/u/.config/.wrangler/config/default.toml", "/home/u/Library/Preferences/.wrangler/config/default.toml", ]); assert.equal( wranglerOAuthConfigFiles("/home/u", { XDG_CONFIG_HOME: "/x" })[1], "/x/.wrangler/config/default.toml", ); }); test("previewAliasUrl is the branch alias hostname", () => { assert.equal( previewAliasUrl("anilyzer", "tags-exclude"), "https://tags-exclude.anilyzer.pages.dev", ); assert.equal(previewAliasUrl(" anilyzer ", " preview "), "https://preview.anilyzer.pages.dev"); }); test("deploymentUrlIn pulls the URL out of wrangler's 'Take a peek' line", () => { const line = "✨ Deployment complete! Take a peek over at https://a1b2c3d4.anilyzer.pages.dev"; assert.equal(deploymentUrlIn(line, "anilyzer"), "https://a1b2c3d4.anilyzer.pages.dev"); }); test("deploymentUrlIn matches the branch alias line too", () => { assert.equal( deploymentUrlIn("Branch alias: https://tags-exclude.anilyzer.pages.dev", "anilyzer"), "https://tags-exclude.anilyzer.pages.dev", ); }); test("deploymentUrlIn returns null for lines with no URL for THIS project", () => { assert.equal(deploymentUrlIn("Uploading... (12/12)", "anilyzer"), null); assert.equal(deploymentUrlIn("", "anilyzer"), null); // Another project's pages.dev URL is not this deployment. assert.equal( deploymentUrlIn("see https://deadbeef.jeralyzer.pages.dev", "anilyzer"), null, ); // The bare apex is not a deployment URL either — it needs a label in front. assert.equal(deploymentUrlIn("https://anilyzer.pages.dev", "anilyzer"), null); assert.equal(deploymentUrlIn("https://x.anilyzer.pages.dev", ""), null); }); test("deploymentUrlIn cannot latch onto an earlier unrelated pages.dev", () => { // A dot-swallowing prefix could span from the FIRST https:// all the way to // this project's suffix and hand back a host nobody deployed. assert.equal( deploymentUrlIn( "old https://docs.pages.dev new https://c0ffee.anilyzer.pages.dev", "anilyzer", ), "https://c0ffee.anilyzer.pages.dev", ); // Same line, no space to stop a greedy class: the prefix is one label or it // is not one of our aliases. assert.equal( deploymentUrlIn("https://docs.pages.dev.anilyzer.pages.dev", "anilyzer"), null, ); }); test("deploymentUrlIn stops at the TLD, not inside a longer hostname", () => { // The nastiest one: it reads exactly like the real thing and is not it. assert.equal( deploymentUrlIn("https://c0ffee.anilyzer.pages.devil.com/x", "anilyzer"), null, ); // A trailing dot-path or punctuation still ends the URL cleanly. assert.equal( deploymentUrlIn("peek at https://c0ffee.anilyzer.pages.dev.", "anilyzer"), null, ); assert.equal( deploymentUrlIn("peek at https://c0ffee.anilyzer.pages.dev!", "anilyzer"), "https://c0ffee.anilyzer.pages.dev", ); }); test("deploymentUrlIn treats a dotted project name literally", () => { // A regex-special character in the project name must not become a wildcard. assert.equal(deploymentUrlIn("https://h.aXb.pages.dev", "a.b"), null); assert.equal(deploymentUrlIn("https://h.a.b.pages.dev", "a.b"), "https://h.a.b.pages.dev"); });