g1t/apps/web/app/lib/starter-workflow.test.ts

78 lines3,812 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { detectStacks, freeBranch, starterDescription, starterWorkflow } from "./starter-workflow.ts";
5
6test("each stack is found by what is at the root", () => {
7 const cases: [string[], string[]][] = [
8 [["package.json", "package-lock.json"], ["npm"]],
9 [["package.json", "pnpm-lock.yaml"], ["pnpm"]],
10 [["package.json", "yarn.lock"], ["yarn"]],
11 [["package.json", "bun.lock"], ["bun"]],
12 [["Cargo.toml", "Cargo.lock", "src"], ["rust"]],
13 [["go.mod", "main.go"], ["go"]],
14 [["pyproject.toml", "uv.lock"], ["uv"]],
15 [["requirements.txt"], ["python"]],
16 [["Gemfile", "Rakefile"], ["ruby"]],
17 [["pom.xml"], ["maven"]],
18 [["build.gradle.kts", "gradlew"], ["gradle"]],
19 [["App.sln"], ["dotnet"]],
20 [["Makefile", "main.c"], ["make"]],
21 [["README.md"], []],
22 // A monorepo: Node and Rust, each with a job.
23 [["package.json", "package-lock.json", "Cargo.toml", "Makefile"], ["npm", "rust"]],
24 ];
25 for (const [names, stacks] of cases) assert.deepEqual(detectStacks({ names }), stacks, names.join(" "));
26});
27
28test("a Node project's workflow runs the scripts it has", () => {
29 const { yaml, stacks } = starterWorkflow(
30 {
31 names: ["package.json", "package-lock.json"],
32 packageJson: JSON.stringify({ scripts: { test: "node --test", build: "tsc", start: "node ." } }),
33 },
34 "main",
35 );
36 assert.deepEqual(stacks, ["npm"]);
37 assert.match(yaml, /^name: CI$/m);
38 assert.match(yaml, /^ {2}pull_request:$/m);
39 assert.match(yaml, /^ {4}branches: \["main"\]$/m);
40 assert.match(yaml, /^ {2}merge_group:$/m);
41 assert.match(yaml, /- run: npm ci/);
42 assert.match(yaml, /- run: npm run build/);
43 assert.match(yaml, /- run: npm run test/);
44 assert.doesNotMatch(yaml, /npm run lint/);
45 assert.match(yaml, / {8}with:\n {10}node-version: 24\n {10}cache: npm/);
46});
47
48test("an unreadable package.json runs the usual scripts only if present", () => {
49 const { yaml } = starterWorkflow({ names: ["package.json"], packageJson: "{ not json" }, "trunk");
50 assert.match(yaml, /- run: npm install/);
51 assert.match(yaml, /- run: npm run test --if-present/);
52 assert.match(yaml, /branches: \["trunk"\]/);
53});
54
55test("Rust, Go and an unknown project each get a workflow", () => {
56 assert.match(starterWorkflow({ names: ["Cargo.toml"] }, "main").yaml, /cargo test --all-targets/);
57 assert.match(starterWorkflow({ names: ["go.mod"] }, "main").yaml, /go-version-file: go.mod[\s\S]*go test \.\/\.\.\./);
58 const unknown = starterWorkflow({ names: ["README.md"] }, "main");
59 assert.deepEqual(unknown.stacks, []);
60 assert.match(unknown.yaml, /Add the commands that build and test this project/);
61 assert.match(starterDescription([], "main"), /placeholder/);
62 assert.match(starterDescription(["rust"], "main"), /found Rust/);
63});
64
65test("the workflow is well-formed: one job per stack, steps under each", () => {
66 const { yaml } = starterWorkflow({ names: ["package.json", "pnpm-lock.yaml", "Cargo.toml"], packageJson: '{"scripts":{"lint":"x"}}' }, "main");
67 const jobs = yaml.split("\n").filter((line) => /^ {2}[a-z]+:$/.test(line) && !line.includes("pull_request") && !line.includes("push") && !line.includes("merge_group"));
68 assert.deepEqual(jobs, [" node:", " rust:"]);
69 // Every line is indented by an even number of spaces, as YAML here wants.
70 for (const line of yaml.split("\n")) assert.equal((line.match(/^ */)?.[0].length ?? 0) % 2, 0, line);
71 assert.match(yaml, /pnpm\/action-setup@v4[\s\S]*pnpm install --frozen-lockfile[\s\S]*pnpm run lint/);
72});
73
74test("a branch name is found that is free", () => {
75 assert.equal(freeBranch([]), "add-ci");
76 assert.equal(freeBranch(["main", "add-ci"]), "add-ci-2");
77 assert.equal(freeBranch(["add-ci", "add-ci-2"]), "add-ci-3");
78});