1const assert = require("node:assert/strict");
2const { spawnSync } = require("node:child_process");
3const { mkdtempSync, readFileSync, rmSync, existsSync } = require("node:fs");
4const { tmpdir } = require("node:os");
5const { join } = require("node:path");
6const { test } = require("node:test");
7
8const release = readFileSync(join(__dirname, "../workflows/release.yml"), "utf8");
9const preview = readFileSync(join(__dirname, "../workflows/web-preview.yml"), "utf8");
10const stepPattern =
11 / name: (?:Build staged deployment|Deploy preview)\n shell: bash\n run: \|\n((?: .*\n|\n)+)/g;
12const steps = [
13 ...Array.from(release.matchAll(stepPattern), (match, index) => [
14 index === 0 ? "hosted web" : "marketing",
15 match[1],
16 ]),
17 ...Array.from(preview.matchAll(stepPattern), (match) => ["web preview", match[1]]),
18];
19assert.equal(steps.length, 3, "Expected both release steps and the preview step");
20
21const url = "https://t3code-fixture-pinglabs.vercel.app";
22const warning = 'Error while parsing config file: "/runner/t3code/pnpm-lock.yaml"';
23
24function runStep(script, stdout, exitCode = 0) {
25 const directory = mkdtempSync(join(tmpdir(), "t3-vercel-output-"));
26 const outputFile = join(directory, "output");
27 try {
28 const result = spawnSync(
29 "bash",
30 [
31 "-c",
32 'vp() { printf "%s\\n" "$FIXTURE_STDOUT"; return "$FIXTURE_EXIT"; }\n' +
33 'curl() { printf \'{"id":"fixture-project"}\\n\'; }\n' +
34 script.replace(/^ /gm, "").replace(/\$\{\{.*?\}\}/g, "fixture"),
35 ],
36 {
37 encoding: "utf8",
38 env: {
39 PATH: process.env.PATH,
40 VERCEL_TOKEN: "fixture-token",
41 VERCEL_ORG_ID: "fixture-org",
42 VERCEL_PROJECT_ID: "fixture-project",
43 GITHUB_OUTPUT: outputFile,
44 FIXTURE_STDOUT: stdout,
45 FIXTURE_EXIT: String(exitCode),
46 },
47 },
48 );
49 assert.ifError(result.error);
50 return {
51 ...result,
52 output: existsSync(outputFile) ? readFileSync(outputFile, "utf8") : "",
53 };
54 } finally {
55 rmSync(directory, { recursive: true, force: true });
56 }
57}
58
59for (const [name, script] of steps) {
60 for (const [label, stdout] of [
61 ["plain URL", url],
62 ["lockfile warning", `${warning}\n${url}`],
63 ["trailing diagnostic", `${url}\nDone`],
64 ]) {
65 test(`${name}: saves one deployment URL after ${label}`, () => {
66 const result = runStep(script, stdout);
67 assert.equal(result.status, 0, result.stderr);
68 assert.deepEqual(
69 result.output.split("\n").filter((line) => !line.startsWith("project_id=")),
70 [`deployment_url=${url}`, ""],
71 );
72 });
73 }
74
75 for (const [label, stdout, exitCode] of [
76 ["missing URL", warning, 0],
77 ["invalid URL", "https://example.invalid/deployment", 0],
78 ["multiple URLs", `${url}\nhttps://other.vercel.app`, 0],
79 ["failed deployment", url, 1],
80 ]) {
81 test(`${name}: rejects ${label} without saving output`, () => {
82 const result = runStep(script, stdout, exitCode);
83 assert.notEqual(result.status, 0);
84 assert.equal(result.output, "");
85 });
86 }
87}