// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The image the three processes run from (spec §13). These are the properties a deployment // depends on, and each one has cost a deployment somewhere: a manifest the install stage does not // copy, a root process, a base image nobody pinned to the Node the app is written for. import { readdirSync, readFileSync } from "node:fs" import { describe, expect, it } from "vitest" import { parse } from "yaml" const dockerfile = readFileSync(new URL("../Dockerfile", import.meta.url), "utf8") const workspace = parse(readFileSync(new URL("../pnpm-workspace.yaml", import.meta.url), "utf8")) as { minimumReleaseAgeExclude: string[] trustPolicy: string trustPolicyExclude: string[] } describe("the Dockerfile", () => { it("is multi-stage, with the runtime stage compose.yaml names", () => { expect(dockerfile).toMatch(/^FROM node:24-slim AS deps$/m) expect(dockerfile).toMatch(/^FROM deps AS build$/m) expect(dockerfile).toMatch(/^FROM node:24-slim AS runtime$/m) }) it("copies the manifest of every workspace package before installing", () => { // The install stage resolves the whole workspace, so a package whose manifest is missing // fails the install with "no package named @app/ is present in the workspace". const packages = readdirSync(new URL("..", import.meta.url), { withFileTypes: true }) expect(packages.some((entry) => entry.name === "pnpm-workspace.yaml")).toBe(true) for (const dir of ["config", "modules", "apps/web", "apps/worker"]) { expect(dockerfile, `${dir}/package.json is not copied`).toContain(`COPY ${dir}/package.json ./${dir}/`) } }) it("keeps code root-owned but lets the runtime user write generated code", () => { expect(dockerfile).toMatch(/^USER node$/m) expect(dockerfile).toContain("--chown=root:root") expect(dockerfile).toContain( "chown -R node:node /app/convex/_generated /app/better-auth/functions/_generated /app/better-auth/functions/betterAuth/_generated", ) }) it("takes the registry credential as a build secret, never as a layer or a build argument", () => { // Two build paths deliver the same credential under two ids: a preset single-image build // mounts it as `npmrc`, a compose build as `conusai_npmrc` (the platform prefixes its compose // secrets so a tenant's own can never collide). The install must accept either — a // deployment that built through the preset path got no credential at all and pnpm fell back // to the public registry, where the vendor scopes 404 (stage, deployment 4, 2026-10-20). expect(dockerfile).toContain("--mount=type=secret,id=conusai_npmrc,target=/run/secrets/conusai_npmrc") expect(dockerfile).toContain("--mount=type=secret,id=npmrc,target=/run/secrets/npmrc") expect(dockerfile).not.toMatch(/ARG .*(TOKEN|PASSWORD|SECRET)/) expect(dockerfile).not.toMatch(/_authToken/) }) it("excludes the vendor scopes from the release-age cooldown", () => { // minimumReleaseAge blocks versions published in the last seven days — but every dev // bundle publishes the vendor packages minutes before a deployment installs them, from // the ConusAI vendor registry, so the cooldown must never apply to either scope // (stage, deployment 5, 2026-10-20: ERR_PNPM_NO_MATURE_MATCHING_VERSION). expect(workspace.minimumReleaseAgeExclude).toContain("@conusai-sdk/*") expect(workspace.minimumReleaseAgeExclude).toContain("@conusai-kit/*") }) it("keeps the trust policy on, excepting one exact version", () => { // A range or a whole package here would switch the policy off for releases nobody has looked at: an entry names // the version, its chain and its reason (pnpm-workspace.yaml), and its integrity is checked against the registry. expect(workspace.trustPolicy).toBe("no-downgrade") expect(workspace.trustPolicyExclude).toEqual(["semver@6.3.1"]) }) it("starts no process by default: compose.yaml names the one each service runs", () => { const cmd = dockerfile.match(/^CMD (.*)$/m)?.[1] ?? "" expect(cmd).toContain("compose.yaml") expect(cmd).not.toMatch(/server\.mjs|vite|main\.js|migrate\.js/) }) it("carries the Start build, and compose.yaml starts the web process on it with no package manager", () => { // The web service's argv is the server itself: a package manager as PID 1 does not forward SIGTERM. const compose = parse(readFileSync(new URL("../compose.yaml", import.meta.url), "utf8")) as { services: Record } expect(compose.services.web?.working_dir).toBe("/app/apps/web") expect(compose.services.web?.command).toEqual(["node", "server.mjs"]) expect(dockerfile).not.toContain(".next") expect(dockerfile).not.toMatch(/\bnext (?:build|start)\b/) }) })