Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

83 linesCodeBlameRaw
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 [
24 ["package.json", "package-lock.json", "Cargo.toml", "Makefile"],
25 ["npm", "rust"],
26 ],
27 ];
28 for (const [names, stacks] of cases) assert.deepEqual(detectStacks({ names }), stacks, names.join(" "));
29});
30
31test("a Node project's workflow runs the scripts it has", () => {
32 const { yaml, stacks } = starterWorkflow(
33 {
34 names: ["package.json", "package-lock.json"],
35 packageJson: JSON.stringify({ scripts: { test: "node --test", build: "tsc", start: "node ." } }),
36 },
37 "main",
38 );
39 assert.deepEqual(stacks, ["npm"]);
40 assert.match(yaml, /^name: CI$/m);
41 assert.match(yaml, /^ {2}pull_request:$/m);
42 assert.match(yaml, /^ {4}branches: \["main"\]$/m);
43 assert.match(yaml, /^ {2}merge_group:$/m);
44 assert.match(yaml, /- run: npm ci/);
45 assert.match(yaml, /- run: npm run build/);
46 assert.match(yaml, /- run: npm run test/);
47 assert.doesNotMatch(yaml, /npm run lint/);
48 assert.match(yaml, / {8}with:\n {10}node-version: 24\n {10}cache: npm/);
49});
50
51test("an unreadable package.json runs the usual scripts only if present", () => {
52 const { yaml } = starterWorkflow({ names: ["package.json"], packageJson: "{ not json" }, "trunk");
53 assert.match(yaml, /- run: npm install/);
54 assert.match(yaml, /- run: npm run test --if-present/);
55 assert.match(yaml, /branches: \["trunk"\]/);
56});
57
58test("Rust, Go and an unknown project each get a workflow", () => {
59 assert.match(starterWorkflow({ names: ["Cargo.toml"] }, "main").yaml, /cargo test --all-targets/);
60 assert.match(starterWorkflow({ names: ["go.mod"] }, "main").yaml, /go-version-file: go.mod[\s\S]*go test \.\/\.\.\./);
61 const unknown = starterWorkflow({ names: ["README.md"] }, "main");
62 assert.deepEqual(unknown.stacks, []);
63 assert.match(unknown.yaml, /Add the commands that build and test this project/);
64 assert.match(starterDescription([], "main"), /placeholder/);
65 assert.match(starterDescription(["rust"], "main"), /found Rust/);
66});
67
68test("the workflow is well-formed: one job per stack, steps under each", () => {
69 const { yaml } = starterWorkflow({ names: ["package.json", "pnpm-lock.yaml", "Cargo.toml"], packageJson: '{"scripts":{"lint":"x"}}' }, "main");
70 const jobs = yaml
71 .split("\n")
72 .filter((line) => /^ {2}[a-z]+:$/.test(line) && !line.includes("pull_request") && !line.includes("push") && !line.includes("merge_group"));
73 assert.deepEqual(jobs, [" node:", " rust:"]);
74 // Every line is indented by an even number of spaces, as YAML here wants.
75 for (const line of yaml.split("\n")) assert.equal((line.match(/^ */)?.[0].length ?? 0) % 2, 0, line);
76 assert.match(yaml, /pnpm\/action-setup@v4[\s\S]*pnpm install --frozen-lockfile[\s\S]*pnpm run lint/);
77});
78
79test("a branch name is found that is free", () => {
80 assert.equal(freeBranch([]), "add-ci");
81 assert.equal(freeBranch(["main", "add-ci"]), "add-ci-2");
82 assert.equal(freeBranch(["add-ci", "add-ci-2"]), "add-ci-3");
83});