check / check (push) Failing after 46s
A plain `docker build .` now stamps the version from git rather than `dev`/`0.0.0`. After `tsc`, `script/build` writes into `dist/package.json` the version `script/version` decides: `VERSION` when given, otherwise `git describe --tags --always` (the tag; or tag, commits since and short commit; or the short commit), otherwise `package.json`'s. A checkout with `.git` that yields an empty, `dev` or `unknown` version fails the build. `.dockerignore` sends `.git` but not `.git/config`, so no remote URL or credential reaches the image. `ARG VERSION` has no default, and the host scripts' version still wins. Not changed: `REPO_POLICIES.md` still says `ARG VERSION=dev` until the shared policy changes. Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
// The Docker build context is load-bearing in two directions, and both
|
|
// failures are silent.
|
|
//
|
|
// Excluding too little: a worktree left under `.claude/` is copied into the
|
|
// image, vitest globs its `test/` tree as well as the real one, and the test
|
|
// phase runs the whole suite twice over while reporting success. A compiled
|
|
// `bin/quak` is ~100 MB of context nobody needs.
|
|
//
|
|
// Excluding too much: Prettier 3 reads `.gitignore` as a default ignore file,
|
|
// so dropping it from the context silently changes which files the lint
|
|
// phase's prettier check looks at compared to `make fmt-check` on the host.
|
|
// And without `.git`, a `docker build .` given no `VERSION` build arg cannot
|
|
// derive the version (`script/version`) and stamps `package.json`'s instead.
|
|
//
|
|
// None of these shows up as a build failure, so they are asserted here.
|
|
import { describe, expect, it } from "vitest";
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { join } from "node:path";
|
|
|
|
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
|
|
|
|
const patterns = (name: string): string[] =>
|
|
readFileSync(join(repoRoot, name), "utf-8")
|
|
.split("\n")
|
|
.map((line) => line.trim())
|
|
.filter((line) => line !== "" && !line.startsWith("#"));
|
|
|
|
const dockerignore = patterns(".dockerignore");
|
|
|
|
describe(".dockerignore", () => {
|
|
// Everything here is either generated, enormous, or secret. `.claude/` is
|
|
// the correctness one: see the header comment and issue #25.
|
|
it.each([
|
|
".claude/",
|
|
".quak/",
|
|
"bin/quak",
|
|
"node_modules",
|
|
"coverage",
|
|
"dist",
|
|
".vitest-cache/",
|
|
".nyc_output/",
|
|
"*.tsbuildinfo",
|
|
])("keeps %s out of the build context", (pattern) => {
|
|
expect(dockerignore).toContain(pattern);
|
|
});
|
|
|
|
it("leaves .gitignore in the build context for prettier", () => {
|
|
expect(dockerignore).not.toContain(".gitignore");
|
|
});
|
|
|
|
it("leaves .git in the build context for the version", () => {
|
|
expect(dockerignore).not.toContain(".git");
|
|
expect(dockerignore).not.toContain(".git/");
|
|
});
|
|
|
|
// The build stage is the final image, so a .git/config sent in would
|
|
// ship the clone's remote URL and any credential in it.
|
|
it("sends .git without its config", () => {
|
|
expect(dockerignore).toContain(".git/config");
|
|
});
|
|
|
|
// BuildKit lets a `Dockerfile.dockerignore` shadow the root one; such a
|
|
// file would silently give the build a different, unreviewed context —
|
|
// and eslint's flat config does not ignore dot-directories, so a stray
|
|
// `.claude/` worktree would be linted.
|
|
it("is not shadowed by a Dockerfile.dockerignore", () => {
|
|
expect(existsSync(join(repoRoot, "Dockerfile.dockerignore"))).toBe(
|
|
false,
|
|
);
|
|
});
|
|
});
|