import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { test } from "node:test"; const root = resolve("."); const sha = "a".repeat(40); const fixture = "synthetic-workflow-credential"; function workflow(name: string): string { return readFileSync(join(root, `.gitea/workflows/${name}.yml`), "utf8"); } function step(name: string, title: string): string { const value = workflow(name).split(" - name: ").find(block => block.startsWith(title + "\n")); assert.ok(value, `Missing workflow step: ${title}`); return value; } function script(name: string, title: string): string { const block = step(name, title); const run = block.split(" run: ")[1]; assert.ok(run, `Missing run body: ${title}`); return run.startsWith("|\n") ? run.slice(2).split("\n").map(line => line.replace(/^ /, "")).join("\n") : run.trim(); } function sandbox(body: (dir: string) => void): void { const dir = mkdtempSync(join(tmpdir(), "potato-workflow-")); try { body(dir); } finally { rmSync(dir, { recursive: true, force: true }); } } function fake(dir: string, command: string, source: string): void { mkdirSync(join(dir, "bin"), { recursive: true }); writeFileSync(join(dir, "bin", command), `#!${process.execPath}\n${source}`, { mode: 0o700 }); } function shell(dir: string, source: string, env: NodeJS.ProcessEnv = {}) { return spawnSync("/bin/sh", ["-eu", "-c", source], { cwd: dir, encoding: "utf8", env: { PATH: `${join(dir, "bin")}:${process.env.PATH}`, HOME: dir, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GITHUB_WORKSPACE: root, GITHUB_OUTPUT: join(dir, "output"), ...env, } }); } test("CI runs the full check and commitlint with inert SHA arguments and SHA-named artifacts", () => { sandbox(dir => { writeFileSync(join(dir, "build-args"), ""); writeFileSync(join(dir, "gradlew"), '#!/bin/sh\nprintf "%s\\n" "$@" > build-args\n', { mode: 0o700 }); assert.equal(shell(dir, script("ci", "Build and test")).status, 0); assert.equal(readFileSync(join(dir, "build-args"), "utf8"), "clean\ncheck\njar\n"); fake(dir, "npx", `require('node:fs').writeFileSync('lint-args', JSON.stringify(process.argv.slice(2)));`); const child = shell(dir, script("ci", "Validate conventional commits"), { BASE_SHA: "b".repeat(40), HEAD_SHA: sha }); assert.equal(child.status, 0, child.stderr); const args = JSON.parse(readFileSync(join(dir, "lint-args"), "utf8")); assert.deepEqual(args.slice(-6), ["--from", "b".repeat(40), "--to", sha, "--extends", "@commitlint/config-conventional"]); assert.match(step("ci", "Validate conventional commits"), /if: github.event_name == 'pull_request'/); assert.match(step("ci", "Upload development artifact"), /name: purpur-potato-\$\{\{ github.sha \}\}/); assert.match(step("ci", "Upload development artifact"), /path: build\/libs\/purpur-potato-\*\.jar/); assert.match(step("ci", "Upload development artifact"), /if-no-files-found: error/); assert.match(workflow("ci"), /java-version: 25/); assert.match(workflow("ci"), /node-version: 20/); }); }); test("release performs full snapshot and versioned checks and gates versioned artifacts on a new release", () => sandbox(dir => { writeFileSync(join(dir, "build-args"), ""); writeFileSync(join(dir, "gradlew"), '#!/bin/sh\nprintf "%s\\n" "$@" > build-args\n', { mode: 0o700 }); assert.equal(shell(dir, script("release", "Build and test before release")).status, 0); assert.equal(readFileSync(join(dir, "build-args"), "utf8"), "clean\ncheck\njar\n"); assert.equal(shell(dir, script("release", "Verify versioned plugin and release tooling"), { VERSION: "1.2.3" }).status, 0); assert.equal(readFileSync(join(dir, "build-args"), "utf8"), "clean\ncheck\njar\n-PreleaseVersion=1.2.3\n"); for (const title of ["Verify versioned plugin and release tooling", "Upload release workflow artifact"]) { assert.match(step("release", title), /if: steps.release.outputs.created == 'true'/); } const release = workflow("release"); assert.ok(release.indexOf("Build and test before release") < release.indexOf("Compute and push")); assert.ok(release.indexOf("Capture previous tag") < release.indexOf("Compute and push")); assert.ok(release.indexOf("Verify versioned") < release.indexOf("Upload release workflow artifact")); assert.match(release, /branches: \[main\]/); assert.match(release, /group: potato-release\n cancel-in-progress: false/); assert.match(step("release", "Upload release workflow artifact"), /path: build\/libs\/purpur-potato-\$\{\{ steps.release.outputs.version \}\}\.jar/); })); for (const scenario of ["new", "none", "unchanged", "wrong-source", "invalid-tag"] as const) { test(`release resolution proves exact source and does not claim no-release publication: ${scenario}`, () => sandbox(dir => { const init = shell(dir, "git init -q; git -c user.name=Fixture -c user.email=fixture@example.invalid commit --allow-empty -qm 'feat: fixture'"); assert.equal(init.status, 0, init.stderr); if (scenario === "unchanged") assert.equal(shell(dir, "git tag v1.2.3").status, 0); writeFileSync(join(dir, "output"), ""); const previous = shell(dir, script("release", "Capture previous tag")); assert.equal(previous.status, 0, previous.stderr); const previousOutput = readFileSync(join(dir, "output"), "utf8"); assert.equal(previousOutput, scenario === "unchanged" ? "tag=v1.2.3\n" : "tag=\n"); writeFileSync(join(dir, "output"), ""); if (scenario === "new" || scenario === "wrong-source") assert.equal(shell(dir, "git tag v1.2.3").status, 0); if (scenario === "invalid-tag") assert.equal(shell(dir, "git tag v01.2.3-unsupported").status, 0); const head = shell(dir, "git rev-parse HEAD").stdout.trim(); const child = shell(dir, script("release", "Resolve newly created exact-source release"), { PREVIOUS_TAG: scenario === "unchanged" ? "v1.2.3" : "", EXPECTED_COMMIT: scenario === "wrong-source" ? sha : head, }); const output = readFileSync(join(dir, "output"), "utf8"); if (scenario === "wrong-source" || scenario === "invalid-tag") { assert.notEqual(child.status, 0); assert.ok(!output.includes("created=true")); } else { assert.equal(child.status, 0, child.stderr); assert.equal(output, scenario === "new" ? "created=true\nversion=1.2.3\n" : "created=false\n"); } })); } const forbiddenEnv = [ "GIT_CREDENTIALS", "GH_TOKEN", "GITHUB_TOKEN", "GL_TOKEN", "GITLAB_TOKEN", "BB_TOKEN", "BITBUCKET_TOKEN", "BB_TOKEN_BASIC_AUTH", "BITBUCKET_TOKEN_BASIC_AUTH", "DEBUG", "NODE_DEBUG", "GIT_TRACE", "GIT_TRACE_CURL", "GIT_CURL_VERBOSE", "GIT_TRACE_PACKET", "GIT_TRACE_SETUP", "GIT_TRACE2", "GIT_TRACE2_EVENT", "GIT_TRACE2_PERF", ]; test("publication invokes only the in-process publisher with no secret argv or tracing", () => sandbox(dir => { assert.equal(shell(dir, "git init -q; git -c user.name=Fixture -c user.email=fixture@example.invalid commit --allow-empty -qm 'feat: fixture'").status, 0); writeFileSync(join(dir, "publish-command"), "null"); fake(dir, "node", ` const assert = require('node:assert/strict'); for (const key of ${JSON.stringify(forbiddenEnv)}) assert.equal(process.env[key], undefined, key); assert.equal(process.env.RELEASE_CREDENTIAL, ${JSON.stringify(fixture)}); assert.match(process.env.RELEASE_NOTES, /feat: fixture/); require('node:fs').writeFileSync('publish-command', JSON.stringify(process.argv.slice(2))); `); const child = shell(dir, script("release", "Publish verified JAR through a draft release"), { RELEASE_CREDENTIAL: fixture, PREVIOUS_TAG: "", ...Object.fromEntries(forbiddenEnv.map(key => [key, key.includes("TOKEN") ? fixture : "1"])), }); assert.equal(child.status, 0, child.stderr); assert.deepEqual(JSON.parse(readFileSync(join(dir, "publish-command"), "utf8")), ["build/release-tools/publish-release.js"]); assert.ok(![child.stdout, child.stderr].join("").includes(fixture)); assert.match(step("release", "Publish verified JAR through a draft release"), /if: steps.release.outputs.created == 'true'/); assert.ok(workflow("release").indexOf("Verify versioned") < workflow("release").indexOf("Publish verified")); assert.doesNotMatch(workflow("release"), /curl|secrets\.RELEASE_TOKEN[^\n]*(?:curl|git|node)/); })); test("semantic tagging keeps the dummy credential only in helper environment, never argv, URLs or diagnostics", () => { sandbox(dir => { assert.equal(shell(dir, "git init -q").status, 0); writeFileSync(join(dir, "tag-command"), "null"); fake(dir, "npx", ` const assert = require('node:assert/strict'); for (const key of ${JSON.stringify(forbiddenEnv)}) assert.equal(process.env[key], undefined, key); assert.equal(process.env.RELEASE_CREDENTIAL, ${JSON.stringify(fixture)}); assert.equal(process.env.GIT_TERMINAL_PROMPT, '0'); require('node:fs').writeFileSync('tag-command', JSON.stringify(process.argv.slice(2))); `); const child = shell(dir, script("release", "Compute and push semantic release tag"), { RELEASE_CREDENTIAL: fixture, REPOSITORY: "dmg/purpur-potato", ...Object.fromEntries(forbiddenEnv.map(key => [key, key.includes("TOKEN") ? fixture : "1"])), }); assert.equal(child.status, 0, child.stderr); const args = JSON.parse(readFileSync(join(dir, "tag-command"), "utf8")); assert.ok(Array.isArray(args), "Must actually invoke semantic-release"); assert.ok(args.includes("semantic-release@24.2.9")); assert.ok(args.includes("@semantic-release/commit-analyzer@13.0.1")); assert.ok(args.includes("@semantic-release/release-notes-generator@14.1.0")); assert.equal(args[args.indexOf("--repository-url") + 1], "https://git.garvis.dev/dmg/purpur-potato.git"); const config = shell(dir, "git config --local --list"); assert.match(config.stdout, /credential.usehttppath=true/); assert.match(config.stdout, /http.followredirects=false/); assert.ok(config.stdout.includes(`credential.helper=${root}/scripts/git-credential.sh`)); assert.ok(![JSON.stringify(args), config.stdout, child.stdout, child.stderr].join("").includes(fixture)); }); }); for (const env of [{ RELEASE_CREDENTIAL: "", REPOSITORY: "dmg/purpur-potato" }, { RELEASE_CREDENTIAL: fixture, REPOSITORY: "evil/purpur-potato" }]) { test(`tagging refuses missing credential or wrong repository: ${env.REPOSITORY}`, () => sandbox(dir => { fake(dir, "npx", "process.exit(90);"); const child = shell(dir, script("release", "Compute and push semantic release tag"), env); assert.notEqual(child.status, 0); assert.notEqual(child.status, 90); assert.ok(!child.stderr.includes(fixture)); })); } for (const name of ["ci", "release"]) { test(`${name} rejects multiline source SHAs before invoking Git`, () => sandbox(dir => { writeFileSync(join(dir, "calls"), ""); fake(dir, "git", `require('node:fs').appendFileSync('calls', 'called');`); const child = shell(dir, script(name, "Checkout public source without a credential"), { SOURCE_SHA: `${sha}\nmalformed` }); assert.notEqual(child.status, 0); assert.equal(readFileSync(join(dir, "calls"), "utf8"), ""); })); } for (const name of ["ci", "release"]) test(`${name} anonymously fetches full history and exact source without credentials or redirects`, () => { sandbox(dir => { writeFileSync(join(dir, "calls"), ""); fake(dir, "git", `require('node:fs').appendFileSync('calls', JSON.stringify(process.argv.slice(2))+'\\n');`); const child = shell(dir, script(name, "Checkout public source without a credential"), { SOURCE_SHA: sha }); assert.equal(child.status, 0, child.stderr); const calls = readFileSync(join(dir, "calls"), "utf8").trim().split("\n").filter(Boolean).map(line => JSON.parse(line)); assert.ok(calls.some(args => JSON.stringify(args) === JSON.stringify(["remote", "add", "origin", "https://git.garvis.dev/dmg/purpur-potato.git"]))); for (const [key, value] of [["credential.helper", ""], ["http.extraHeader", ""], ["http.followRedirects", "false"]]) { assert.ok(calls.some(args => JSON.stringify(args) === JSON.stringify(["config", "--local", key, value]))); } assert.ok(calls.some(args => JSON.stringify(args) === JSON.stringify(["fetch", "--force", "--tags", "origin", "+refs/heads/*:refs/remotes/origin/*"]))); assert.deepEqual(calls.at(-1), name === "ci" ? ["checkout", "--detach", sha] : ["checkout", "-B", "main", sha]); assert.doesNotMatch(step(name, "Checkout public source without a credential"), /secrets\.|token:/); }); });