64 lines
3.4 KiB
TypeScript
64 lines
3.4 KiB
TypeScript
// 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[]
|
|
}
|
|
|
|
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/<id> 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("runs as a user that owns none of the files it runs", () => {
|
|
expect(dockerfile).toMatch(/^USER node$/m)
|
|
expect(dockerfile).toContain("--chown=root:root")
|
|
})
|
|
|
|
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("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.toContain("next")
|
|
})
|
|
})
|