1import { expect, test } from "bun:test";
2import {
3 mkdtempSync,
4 mkdirSync,
5 writeFileSync,
6 readFileSync,
7 existsSync,
8 rmSync,
9} from "node:fs";
10import { tmpdir } from "node:os";
11import { join, resolve } from "node:path";
12import type { Manifest } from "../src/Manifest.ts";
13
14const cli = resolve(import.meta.dir, "../src/cli/bin.ts");
15
16test("CLI selects PR packages, honors overrides, and clears empty output", async () => {
17 const cwd = mkdtempSync(join(tmpdir(), "pkg-selection-"));
18 const env = { ...process.env };
19 delete env.GITHUB_EVENT_NAME;
20 delete env.GITHUB_OUTPUT;
21 const run = async (args: string[]) => {
22 const child = Bun.spawn(args, { cwd, env, stdout: "pipe", stderr: "pipe" });
23 const [stdout, stderr, code] = await Promise.all([
24 new Response(child.stdout).text(),
25 new Response(child.stderr).text(),
26 child.exited,
27 ]);
28 if (code !== 0) throw new Error(`${args.join(" ")}: ${stdout}\n${stderr}`);
29 return stdout.trim();
30 };
31 try {
32 await run(["git", "init", "-q"]);
33 await run(["git", "config", "user.email", "test@example.com"]);
34 await run(["git", "config", "user.name", "Test"]);
35 for (const name of ["core", "aws", "cloudflare"]) {
36 const dir = join(cwd, "packages", name);
37 mkdirSync(dir, { recursive: true });
38 writeFileSync(
39 join(dir, "package.json"),
40 JSON.stringify({
41 name,
42 version: "1.0.0",
43 dependencies: name === "core" ? {} : { core: "1.0.0" },
44 }),
45 );
46 writeFileSync(join(dir, "index.js"), "export const value = 1;");
47 }
48 await run(["git", "add", "."]);
49 await run(["git", "commit", "-qm", "base"]);
50 const base = await run(["git", "rev-parse", "HEAD"]);
51 writeFileSync(
52 join(cwd, "packages/cloudflare/index.js"),
53 "export const value = 2;",
54 );
55 await run(["git", "add", "."]);
56 await run(["git", "commit", "-qm", "change"]);
57 const head = await run(["git", "rev-parse", "HEAD"]);
58 const pack = async (...flags: string[]) => {
59 await run([
60 process.execPath,
61 cli,
62 "pack",
63 "--group",
64 "SDK=./packages/*",
65 ...flags,
66 ]);
67 const manifest: Manifest = JSON.parse(
68 readFileSync(join(cwd, ".pkg/pkg-manifest.json"), "utf8"),
69 );
70 return manifest.packages.map((pkg) => pkg.name);
71 };
72 expect(await pack("--since", base)).toEqual(["cloudflare", "core"]);
73 expect(await pack("--since", "HEAD")).toEqual([]);
74 expect(existsSync(join(cwd, ".pkg/cloudflare.tgz"))).toBe(false);
75
76 env.GITHUB_EVENT_NAME = "pull_request";
77 env.GITHUB_EVENT_PATH = join(cwd, "event.json");
78 env.GITHUB_OUTPUT = join(cwd, "output");
79 const event = {
80 pull_request: {
81 base: { sha: base },
82 head: { sha: head },
83 labels: [] as { name: string }[],
84 },
85 };
86 writeFileSync(env.GITHUB_EVENT_PATH, JSON.stringify(event));
87 expect(await pack()).toEqual(["cloudflare", "core"]);
88 expect(readFileSync(env.GITHUB_OUTPUT, "utf8")).toContain(
89 "package-count=2",
90 );
91 expect(await pack("--all")).toEqual(["aws", "cloudflare", "core"]);
92 event.pull_request.labels.push({ name: "force-ci" });
93 writeFileSync(env.GITHUB_EVENT_PATH, JSON.stringify(event));
94 // Repository-specific labels have no meaning to the CLI.
95 expect(await pack()).toEqual(["cloudflare", "core"]);
96 } finally {
97 rmSync(cwd, { recursive: true, force: true });
98 }
99}, 120_000);