import { spawnSync } from "node:child_process"; import { chmodSync, existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; const root = existsSync(join(process.cwd(), "turbo.json")) ? process.cwd() : join(process.cwd(), "..", ".."); describe("workspace boundaries", () => { test("declares the root as a turbo-managed bun workspace", () => { const packageJson = JSON.parse( readFileSync(join(root, "package.json"), "utf8"), ) as { private?: boolean; scripts?: Record; workspaces?: string[]; }; expect(packageJson.private).toBe(true); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.scripts?.build).toBe("turbo run build"); expect(existsSync(join(root, "turbo.json"))).toBe(true); }); test("keeps release orchestration in the root turbo task graph", () => { const rootPackage = JSON.parse( readFileSync(join(root, "package.json"), "utf8"), ) as { scripts?: Record; }; const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), ) as { scripts?: Record }; const turboConfig = JSON.parse( readFileSync(join(root, "turbo.json"), "utf8"), ) as { globalDependencies?: string[]; tasks?: Record; }; expect(rootPackage.scripts?.["test:qa"]).toBe( "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e", ); expect(webPackage.scripts).not.toHaveProperty("test:qa"); expect(webPackage.scripts?.test).toBe("bun --bun vitest run"); expect(webPackage.scripts?.["test:unit"]).toBe("bun --bun vitest run"); expect(turboConfig.tasks).not.toHaveProperty("test:qa"); expect(turboConfig.globalDependencies).toEqual( expect.arrayContaining(["bun.lock", "tsconfig.base.json"]), ); expect(turboConfig.tasks?.["test:e2e"]?.env).toEqual( expect.arrayContaining([ "CI", "PLAYWRIGHT_DATABASE_URL", "PLAYWRIGHT_PORT", "PLAYWRIGHT_STORYBOOK_PORT", ]), ); }); test("lets turbo build app and Storybook artifacts before e2e serves them", () => { const playwrightConfig = readFileSync( join(root, "apps/web/playwright.config.ts"), "utf8", ); const turboConfig = JSON.parse( readFileSync(join(root, "turbo.json"), "utf8"), ) as { tasks?: Record; }; expect(playwrightConfig).not.toContain("bun run build &&"); expect(playwrightConfig).not.toContain("bun run build-storybook"); expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual( expect.arrayContaining(["@dimensionlab/ui#build-storybook"]), ); }); test("accounts for local env files in cacheable Vite and Storybook task hashes", () => { const turboConfig = JSON.parse( readFileSync(join(root, "turbo.json"), "utf8"), ) as { tasks?: Record; }; for (const taskName of ["build", "build-storybook"]) { expect(turboConfig.tasks?.[taskName]?.inputs).toEqual([ "$TURBO_DEFAULT$", ".env*", ]); } }); test("keeps the website app and reusable UI library as separate packages", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), ) as { dependencies?: Record; name?: string }; const uiPackage = JSON.parse( readFileSync(join(root, "packages/ui/package.json"), "utf8"), ) as { exports?: Record; name?: string }; expect(webPackage.name).toBe("@dimensionlab/web"); expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*"); expect(uiPackage.name).toBe("@dimensionlab/ui"); expect(uiPackage.exports).toHaveProperty("."); expect(uiPackage.exports).toHaveProperty("./styles.css"); }); test("keeps the dashboard model in a reusable internal package", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), ) as { dependencies?: Record }; const modelPackage = JSON.parse( readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"), ) as { exports?: Record; name?: string }; const tsconfig = JSON.parse( readFileSync(join(root, "tsconfig.json"), "utf8"), ) as { references?: Array<{ path: string }> }; expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe( "workspace:*", ); expect(modelPackage.name).toBe("@dimensionlab/dashboard-model"); expect(modelPackage.exports).toHaveProperty("."); expect(modelPackage.exports).toHaveProperty("./fixtures"); expect(tsconfig.references).toEqual( expect.arrayContaining([{ path: "./packages/dashboard-model" }]), ); expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe( false, ); }); test("consumes workspace packages through package exports instead of source aliases", () => { const webTsconfig = JSON.parse( readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"), ) as { compilerOptions?: { paths?: Record } }; const modelPackage = JSON.parse( readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"), ) as { exports?: Record }; const uiPackage = JSON.parse( readFileSync(join(root, "packages/ui/package.json"), "utf8"), ) as { exports?: Record }; const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8"); const turboConfig = JSON.parse( readFileSync(join(root, "turbo.json"), "utf8"), ) as { tasks?: Record }; expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( "@dimensionlab/dashboard-model", ); expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( "@dimensionlab/dashboard-model/fixtures", ); expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty("@dimensionlab/ui"); expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty( "@dimensionlab/ui/styles.css", ); expect(webTsconfig.compilerOptions?.paths).toEqual({ "$lib/*": ["src/lib/*"], }); expect(viteConfig).not.toContain("../../packages/dashboard-model/src"); expect(viteConfig).not.toContain("../../packages/ui/src"); expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]); expectPackageExport(modelPackage.exports?.["."], { types: "./dist/index.d.ts", development: "./src/index.ts", default: "./dist/index.js", }); expectPackageExport(modelPackage.exports?.["./fixtures"], { types: "./dist/fixtures/index.d.ts", development: "./src/fixtures/index.ts", default: "./dist/fixtures/index.js", }); expectPackageExport(uiPackage.exports?.["."], { types: "./dist/index.d.ts", development: "./src/index.ts", default: "./dist/index.js", }); expectPackageExport(uiPackage.exports?.["./styles.css"], { development: "./src/styles.css", default: "./dist/styles.css", }); expectPackageExport(uiPackage.exports?.["./tokens.css"], { development: "./src/tokens.css", default: "./dist/tokens.css", }); }); test("builds the internal container from a turbo-pruned web workspace", () => { const containerfile = readFileSync( join(root, "apps/web/Containerfile"), "utf8", ); const pruneCommand = "turbo prune @dimensionlab/web --docker"; const jsonCopy = "COPY --from=pruner /repo/out/json/ ./"; const sourceCopy = "COPY --from=pruner /repo/out/full/ ./"; const tsconfigCopy = "COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./"; const installCommand = "RUN bun install --frozen-lockfile --ignore-scripts"; expect(containerfile).toContain("AS pruner"); expect(containerfile).toContain(pruneCommand); expect(containerfile).toContain(jsonCopy); expect(containerfile).toContain(sourceCopy); expect(containerfile).toContain(tsconfigCopy); expect(containerfile).not.toContain( "COPY packages/dashboard-model/package.json", ); expect(containerfile).not.toContain("COPY packages/ui/package.json"); const jsonCopyIndex = containerfile.indexOf(jsonCopy); const installIndex = containerfile.indexOf(installCommand); const sourceCopyIndex = containerfile.indexOf(sourceCopy); const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy); expect(jsonCopyIndex).toBeGreaterThan(-1); expect(installIndex).toBeGreaterThan(jsonCopyIndex); expect(sourceCopyIndex).toBeGreaterThan(installIndex); expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex); expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan( tsconfigCopyIndex, ); }); test("keeps local turbo prune output out of git and container contexts", () => { const webPackage = JSON.parse( readFileSync(join(root, "apps/web/package.json"), "utf8"), ) as { scripts?: Record }; const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); const containerignore = readFileSync(join(root, ".containerignore"), "utf8"); const containerIgnoreRules = new Set( containerignore .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean), ); expect(gitignore).toContain("out/"); expect(containerignore).toContain("out"); expect([...containerIgnoreRules]).toEqual( expect.arrayContaining([ "apps/*/.turbo", "apps/*/build", "apps/*/data", "apps/*/dist", "apps/*/playwright-report", "apps/*/test-results", "packages/*/.turbo", "packages/*/dist", "packages/*/storybook-static", ]), ); expect(webPackage.scripts?.build).toBe( "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build", ); }); test("defines Forgejo CI and main-branch deploy automation", () => { const workflow = readFileSync( join(root, ".forgejo/workflows/dimensionlab-website.yml"), "utf8", ); expect(workflow).toContain("name: Dimension Lab website"); expect(workflow).toContain("pull_request:"); expect(workflow).toContain("push:"); expect(workflow).toContain("branches:"); expect(workflow).toContain("- main"); expect(workflow).toContain("runs-on: docker"); expect(workflow).toContain("bun install --frozen-lockfile"); expect(workflow).toContain("bun run check"); expect(workflow).toContain("bun run test"); expect(workflow).toContain("bun run build"); expect(workflow).toContain("needs: ci"); expect(workflow).toContain("github.event_name == 'push'"); expect(workflow).toContain("github.ref == 'refs/heads/main'"); expect(workflow).toContain("docker inspect dimensionlab-website"); expect(workflow).toContain("PODMAN_SYSTEMD_UNIT"); expect(workflow).toContain("scripts/deploy-dimensionlab-website.sh"); }); test("keeps production deployment behind a guarded script", () => { const deployScript = readFileSync( join(root, "scripts/deploy-dimensionlab-website.sh"), "utf8", ); expect(deployScript).toContain("refs/heads/main"); expect(deployScript).toContain("dimensionlab-website.service"); expect(deployScript).toContain("localhost/dimensionlab-website"); expect(deployScript).toContain("apps/web/Containerfile"); expect(deployScript).toContain("rollback-"); expect(deployScript).toContain("https://dimensionlab.net"); expect(deployScript).toContain("/api/dashboard/tiles"); expect(deployScript).toContain("--dry-run"); expect(deployScript).toContain("DEPLOY_RESTART_STRATEGY"); }); test("rolls back the latest image when production smoke checks fail", () => { const result = runDeployScriptWithFakes( { curl: failingCurlCommand, git: fakeGitCommand, podman: fakePodmanCommand("dimensionlab-website.service"), }, { DEPLOY_CONTAINER_CLI: "podman", DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/main", DEPLOY_RESTART_STRATEGY: "quadlet-container", DEPLOY_SHA: "1234567890abcdef", DEPLOY_SMOKE_TIMEOUT_SECONDS: "0", }, ); expect(result.status).toBe(1); expect(result.stderr).toContain("smoke checks failed"); expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-"); expect(result.log).toMatch( /tag localhost\/dimensionlab-website:latest localhost\/dimensionlab-website:rollback-\d{14}/, ); expect(result.log).toMatch( /tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/, ); expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2); }); test("rolls back the latest image when the container fails to restart", () => { const result = runDeployScriptWithFakes( { curl: passingCurlCommand, git: fakeGitCommand, podman: fakePodmanCommand("dimensionlab-website.service"), }, { DEPLOY_CONTAINER_CLI: "podman", DEPLOY_CONTAINER_START_TIMEOUT_SECONDS: "1", DEPLOY_TEST_CONTAINER_IMAGE: "wrong", DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/main", DEPLOY_RESTART_STRATEGY: "quadlet-container", DEPLOY_SHA: "1234567890abcdef", }, ); expect(result.status).toBe(1); expect(result.stderr).toContain("did not restart on localhost/dimensionlab-website:latest"); expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-"); expect(result.stderr).toContain("rollback image is running"); expect(result.log).toMatch( /tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/, ); expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2); }); test.each([ { env: { DEPLOY_EVENT_NAME: "pull_request", DEPLOY_REF: "refs/heads/main" }, message: "production deploys only run for push", }, { env: { DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/codex/test" }, message: "expected refs/heads/main", }, ])("refuses guarded deploy contexts before host mutations", ({ env, message }) => { const result = runDeployScriptWithFakes( { curl: passingCurlCommand, git: fakeGitCommand, podman: fakePodmanCommand("dimensionlab-website.service"), }, { DEPLOY_CONTAINER_CLI: "podman", DEPLOY_SHA: "1234567890abcdef", ...env, }, ); expect(result.status).toBe(1); expect(result.stderr).toContain(message); expect(result.log).toBe(""); }); test("refuses stop-based deploys unless the container belongs to the expected unit", () => { const result = runDeployScriptWithFakes( { curl: passingCurlCommand, git: fakeGitCommand, podman: fakePodmanCommand("other.service"), }, { DEPLOY_CONTAINER_CLI: "podman", DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/main", DEPLOY_RESTART_STRATEGY: "quadlet-container", DEPLOY_SHA: "1234567890abcdef", }, ); expect(result.status).toBe(1); expect(result.stderr).toContain( "refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service", ); expect(result.log).not.toContain("build "); expect(result.log).not.toContain("stop dimensionlab-website"); }); test("checks the expected unit before auto falls back to stopping the container", () => { const result = runDeployScriptWithFakes( { curl: passingCurlCommand, git: fakeGitCommand, podman: fakePodmanCommand("other.service"), systemctl: fakeSystemctlCommand({ active: false, show: true }), }, { DEPLOY_CONTAINER_CLI: "podman", DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/main", DEPLOY_RESTART_STRATEGY: "auto", DEPLOY_SHA: "1234567890abcdef", }, ); expect(result.status).toBe(1); expect(result.stderr).toContain( "refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service", ); expect(result.log).not.toContain("stop dimensionlab-website"); }); }); type WorkspacePackageExport = | string | { types?: string; development?: string; default?: string; }; function expectPackageExport( actual: WorkspacePackageExport | undefined, expected: Exclude, ): void { expect(actual).toMatchObject(expected); } function runDeployScriptWithFakes( commands: Record, env: Record, ): { log: string; status: number | null; stderr: string; stdout: string } { const tempDir = mkdtempSync(join(tmpdir(), "dimensionlab-deploy-test-")); const logPath = join(tempDir, "commands.log"); for (const [name, source] of Object.entries(commands)) { const commandPath = join(tempDir, name); writeFileSync(commandPath, source); chmodSync(commandPath, 0o755); } const result = spawnSync("bash", [join(root, "scripts/deploy-dimensionlab-website.sh")], { cwd: root, encoding: "utf8", env: { ...process.env, ...env, DEPLOY_TEST_LOG: logPath, PATH: `${tempDir}:${process.env.PATH ?? ""}`, }, }); return { log: existsSync(logPath) ? readFileSync(logPath, "utf8") : "", status: result.status, stderr: result.stderr, stdout: result.stdout, }; } const fakeGitCommand = `#!/usr/bin/env bash case "$1" in branch) echo main ;; rev-parse) echo 1234567890abcdef ;; config|submodule) exit 0 ;; esac `; function fakePodmanCommand(systemdUnit: string): string { return `#!/usr/bin/env bash state_file="$DEPLOY_TEST_LOG.state" [ -f "$state_file" ] || printf 'initial' > "$state_file" printf '%s\\n' "$*" >> "$DEPLOY_TEST_LOG" if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then if [ "$4" = "--format" ]; then case "$3" in *:rollback-*) echo sha256:old ;; *) if [ "$(cat "$state_file")" = "rollback" ]; then echo sha256:old else echo sha256:new fi ;; esac fi exit 0 fi if [ "$1" = "tag" ] && [ "$2" != "localhost/dimensionlab-website:latest" ]; then printf 'rollback' > "$state_file" fi if [ "$1" = "inspect" ]; then case "$*" in *PODMAN_SYSTEMD_UNIT*) echo ${systemdUnit} ;; *State.Running*) echo true ;; *'.Image'*|*'{{.Image}}'*) if [ "$(cat "$state_file")" = "rollback" ]; then echo sha256:old elif [ "\${DEPLOY_TEST_CONTAINER_IMAGE:-new}" = "wrong" ]; then echo sha256:wrong else echo sha256:new fi ;; esac fi `; } const failingCurlCommand = `#!/usr/bin/env bash printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG" exit 22 `; const passingCurlCommand = `#!/usr/bin/env bash printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG" if [ "$*" = *'/api/dashboard/tiles'* ]; then printf '{"state":"ready","tiles":[]}' fi `; function fakeSystemctlCommand(options: { active: boolean; show: boolean }): string { const activeStatus = options.active ? 0 : 3; const showStatus = options.show ? 0 : 1; return `#!/usr/bin/env bash printf 'systemctl %s\\n' "$*" >> "$DEPLOY_TEST_LOG" if [ "$1" = "--user" ] && [ "$2" = "is-active" ]; then exit ${activeStatus} fi if [ "$1" = "--user" ] && [ "$2" = "show" ]; then exit ${showStatus} fi if [ "$1" = "--user" ] && [ "$2" = "restart" ]; then exit 0 fi `; }