import { existsSync, readFileSync } from "node:fs"; 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(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"; 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", ); }); }); type WorkspacePackageExport = | string | { types?: string; development?: string; default?: string; }; function expectPackageExport( actual: WorkspacePackageExport | undefined, expected: Exclude, ): void { expect(actual).toMatchObject(expected); }