Compare commits

...

3 commits

Author SHA1 Message Date
22f50d64f9 Merge pull request #33 from codex/turbo-idiomatic-workspace
build(turbo): normalize workspace task graph
2026-06-20 06:19:39 +02:00
vince
c8183bcc47 fix(turbo): rely on graph build before e2e 2026-06-20 06:16:41 +02:00
vince
ae6eabed6f build(turbo): normalize workspace task graph 2026-06-20 06:10:16 +02:00
6 changed files with 138 additions and 32 deletions

View file

@ -32,7 +32,8 @@ This MVP uses Drizzle with Bun SQLite for local file-backed persistence.
- `bun run test`: run the unit test stage in all workspaces. - `bun run test`: run the unit test stage in all workspaces.
- `bun run test:unit`: run Vitest explicitly as the unit test stage. - `bun run test:unit`: run Vitest explicitly as the unit test stage.
- `bun run test:e2e`: build and run Playwright browser smoke and QA checks. - `bun run test:e2e`: build and run Playwright browser smoke and QA checks.
- `bun run test:qa`: run the release gate through Turbo. - `bun run test:qa`: run the release gate through Turbo across check, unit,
build, Storybook, and e2e tasks.
- `bun run build`: build the UI package, production website, and Bun server. - `bun run build`: build the UI package, production website, and Bun server.
- `bun run preview`: preview the production web build. - `bun run preview`: preview the production web build.
- `bun run storybook`: start the UI package component explorer on port 6006. - `bun run storybook`: start the UI package component explorer on port 6006.
@ -102,7 +103,9 @@ bun run test:qa
The gate runs TypeScript checks, Vitest coverage for model, The gate runs TypeScript checks, Vitest coverage for model,
persistence, renderer, datasource mocks, and presentation boundaries, the persistence, renderer, datasource mocks, and presentation boundaries, the
production build, the static Storybook build, and Playwright desktop/mobile production build, the static Storybook build, and Playwright desktop/mobile
smoke checks against the built adapter output. Playwright also performs smoke checks against the built adapter output. Turbo owns the release task
graph; app package scripts stay as leaf commands and do not re-run the QA
pipeline internally. Playwright also performs
baseline screenshot checks, keyboard navigation checks, reduced-motion checks, baseline screenshot checks, keyboard navigation checks, reduced-motion checks,
landmark checks, and axe accessibility checks against the real model-driven landmark checks, and axe accessibility checks against the real model-driven
route. route.

View file

@ -11,7 +11,6 @@
"test": "vitest run", "test": "vitest run",
"test:unit": "vitest run", "test:unit": "vitest run",
"test:e2e": "env -u NO_COLOR playwright test", "test:e2e": "env -u NO_COLOR playwright test",
"test:qa": "bun run check && bun run test:unit && bun run build && bun run test:e2e",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:check": "drizzle-kit check" "db:check": "drizzle-kit check"
}, },

View file

@ -21,13 +21,13 @@ export default defineConfig({
}, },
webServer: [ webServer: [
{ {
command: `DISABLE_LIVE_DATASOURCES=1 bun run build && DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`, command: `DISABLE_LIVE_DATASOURCES=1 DATABASE_URL=${databaseUrl} HOST=127.0.0.1 PORT=${port} bun build/index.js`,
url: baseURL, url: baseURL,
reuseExistingServer: false, reuseExistingServer: false,
timeout: 120_000, timeout: 120_000,
}, },
{ {
command: `cd ../.. && bun run build-storybook && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`, command: `cd ../.. && STORYBOOK_STATIC_PORT=${storybookPort} bun apps/web/tests/e2e/storybook-server.ts`,
url: storybookURL, url: storybookURL,
reuseExistingServer: false, reuseExistingServer: false,
timeout: 120_000, timeout: 120_000,

View file

@ -18,10 +18,62 @@ describe("workspace boundaries", () => {
expect(packageJson.private).toBe(true); expect(packageJson.private).toBe(true);
expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]); expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
expect(packageJson.scripts?.build).toBe("turbo build"); expect(packageJson.scripts?.build).toBe("turbo run build");
expect(existsSync(join(root, "turbo.json"))).toBe(true); 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<string, string>;
};
const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"),
) as { scripts?: Record<string, string> };
const turboConfig = JSON.parse(
readFileSync(join(root, "turbo.json"), "utf8"),
) as {
globalDependencies?: string[];
tasks?: Record<string, { env?: string[] }>;
};
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<string, { dependsOn?: string[] }>;
};
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("keeps the website app and reusable UI library as separate packages", () => { test("keeps the website app and reusable UI library as separate packages", () => {
const webPackage = JSON.parse( const webPackage = JSON.parse(
readFileSync(join(root, "apps/web/package.json"), "utf8"), readFileSync(join(root, "apps/web/package.json"), "utf8"),

View file

@ -9,18 +9,18 @@
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"dev": "turbo dev --filter=@dimensionlab/web", "dev": "turbo run dev --filter=@dimensionlab/web",
"build": "turbo build", "build": "turbo run build",
"preview": "bun run --cwd apps/web preview", "preview": "turbo run preview --filter=@dimensionlab/web",
"storybook": "turbo storybook --filter=@dimensionlab/ui", "storybook": "turbo run storybook --filter=@dimensionlab/ui",
"build-storybook": "turbo build-storybook --filter=@dimensionlab/ui", "build-storybook": "turbo run build-storybook --filter=@dimensionlab/ui",
"check": "turbo check", "check": "turbo run check",
"test": "turbo test:unit", "test": "turbo run test:unit",
"test:unit": "turbo test:unit", "test:unit": "turbo run test:unit",
"test:e2e": "turbo test:e2e --filter=@dimensionlab/web", "test:e2e": "turbo run test:e2e --filter=@dimensionlab/web",
"test:qa": "turbo test:qa", "test:qa": "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
"db:generate": "bun run --cwd apps/web db:generate", "db:generate": "turbo run db:generate --filter=@dimensionlab/web",
"db:check": "bun run --cwd apps/web db:check" "db:check": "turbo run db:check --filter=@dimensionlab/web"
}, },
"devDependencies": { "devDependencies": {
"turbo": "^2.5.0" "turbo": "^2.5.0"

View file

@ -1,9 +1,16 @@
{ {
"$schema": "https://turbo.build/schema.json", "$schema": "https://turbo.build/schema.json",
"globalDependencies": [
"bun.lock",
"package.json",
"tsconfig.base.json",
"tsconfig.json"
],
"tasks": { "tasks": {
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"outputs": ["dist/**", "build/**"] "outputs": ["dist/**", "build/**"],
"env": ["NODE_ENV", "VITE_*"]
}, },
"check": { "check": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
@ -11,33 +18,78 @@
}, },
"test:unit": { "test:unit": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"outputs": [] "outputs": [],
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"DISABLE_LIVE_DATASOURCES",
"PROMETHEUS_BASE_URL"
]
}, },
"build-storybook": { "build-storybook": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"outputs": ["storybook-static/**"] "outputs": ["storybook-static/**"],
"env": ["NODE_ENV", "STORYBOOK_*", "VITE_*"]
}, },
"test:e2e": { "test:e2e": {
"dependsOn": ["build", "^build"],
"outputs": ["test-results/**", "playwright-report/**"]
},
"test:qa": {
"dependsOn": [ "dependsOn": [
"check",
"test:unit",
"build", "build",
"build-storybook", "^build",
"test:e2e" "@dimensionlab/ui#build-storybook"
], ],
"outputs": [] "outputs": ["test-results/**", "playwright-report/**"],
"env": [
"AGENT_CONFIG_TOKEN",
"CI",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"DISABLE_LIVE_DATASOURCES",
"PLAYWRIGHT_DATABASE_URL",
"PLAYWRIGHT_PORT",
"PLAYWRIGHT_STORYBOOK_PORT",
"PROMETHEUS_BASE_URL",
"STORYBOOK_STATIC_PORT"
]
},
"db:generate": {
"cache": false
},
"db:check": {
"outputs": [],
"env": ["DATABASE_URL"]
}, },
"dev": { "dev": {
"cache": false, "cache": false,
"persistent": true "persistent": true,
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_DEV_API_HOST",
"DASHBOARD_DEV_API_PORT",
"DASHBOARD_DEV_API_TARGET",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"HOST",
"PORT",
"PROMETHEUS_BASE_URL"
]
},
"preview": {
"cache": false,
"persistent": true,
"env": [
"AGENT_CONFIG_TOKEN",
"DASHBOARD_MIGRATIONS_DIR",
"DATABASE_URL",
"HOST",
"PORT",
"PROMETHEUS_BASE_URL"
]
}, },
"storybook": { "storybook": {
"cache": false, "cache": false,
"persistent": true "persistent": true,
"env": ["HOST", "PORT", "STORYBOOK_*", "VITE_*"]
} }
} }
} }