Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 36 additions & 27 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,36 +110,21 @@ jobs:

- uses: ./.github/actions/setup-toolchain

- run: bun run test:coverage

# Workspace package tests are not picked up by the root vitest config,
# so run their dedicated configs explicitly. Keep this list in sync with
# the `test:run` script in root package.json. The standalone server
# projects have their own jobs: server/api (`api-test`), server/mcp
# (`mcp-test`), server/hocuspocus (`hocuspocus-test`); they are not
# duplicated here.
# ルートの vitest 設定はワークスペース配下のテストを含まないため、
# 各パッケージの vitest.config を明示的に実行する。server/api・server/mcp・
# server/hocuspocus はそれぞれ別 job(`api-test` / `mcp-test` /
# `hocuspocus-test`)で動かすためここには含めない。
- name: Run admin tests
run: bunx vitest run --config admin/vitest.config.ts

- name: Run @zedi/shared tests
run: bunx vitest run --config packages/shared/vitest.config.ts

- name: Run @zedi/ui tests
run: bunx vitest run --config packages/ui/vitest.config.ts

- name: Run @zedi/claude-sidecar tests
run: bunx vitest run --config packages/claude-sidecar/vitest.config.ts
# Root workspaces only; standalone server/* jobs run their own coverage below.
# ルート配下のワークスペースのみ。server/* は各専用 job で coverage を計測する。
- run: node scripts/run-workspace-coverage.mjs

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-report
path: coverage/
path: |
coverage/
admin/coverage/
packages/shared/coverage/
packages/ui/coverage/
packages/claude-sidecar/coverage/
retention-days: 7

build:
Expand Down Expand Up @@ -329,7 +314,15 @@ jobs:

- name: Run tests
working-directory: server/api
run: bunx vitest run
run: bunx vitest run --coverage

- name: Upload API coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-report-api
path: server/api/coverage/
retention-days: 7

drizzle-migration-check:
name: Drizzle Migration Check
Expand Down Expand Up @@ -434,7 +427,15 @@ jobs:

- name: Run tests
working-directory: server/mcp
run: bun run test
run: bun run test:coverage

- name: Upload MCP coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-report-mcp
path: server/mcp/coverage/
retention-days: 7

hocuspocus-test:
name: Hocuspocus Server Tests
Expand Down Expand Up @@ -464,7 +465,15 @@ jobs:

- name: Run tests
working-directory: server/hocuspocus
run: bun run test
run: bun run test:coverage

- name: Upload Hocuspocus coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: coverage-report-hocuspocus
path: server/hocuspocus/coverage/
retention-days: 7

mutation-light:
name: Mutation (light)
Expand Down
5 changes: 5 additions & 0 deletions admin/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from "path";
import { defineConfig } from "vitest/config";
import { createCoverageConfig } from "../vitest.coverage.shared";

export default defineConfig({
// Anchor `include` / `setupFiles` to admin/ so the config also works when
Expand All @@ -21,5 +22,9 @@ export default defineConfig({
environment: "jsdom",
setupFiles: [path.resolve(__dirname, "src/test/setup.ts")],
include: ["src/**/*.{test,spec}.{ts,tsx}"],
coverage: createCoverageConfig({
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/main.tsx", "src/App.tsx", "src/vite-env.d.ts", "src/i18n/locales/**"],
}),
},
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"preview": "vite preview",
"test": "vitest",
"test:run": "vitest run && vitest run --config packages/shared/vitest.config.ts && vitest run --config packages/ui/vitest.config.ts && vitest run --config packages/claude-sidecar/vitest.config.ts && vitest run --config server/hocuspocus/vitest.config.ts && vitest run --config server/mcp/vitest.config.ts && vitest run --config admin/vitest.config.ts",
"test:coverage": "vitest run --coverage",
"test:coverage": "node scripts/run-workspace-coverage.mjs --with-servers",
"test:ui": "vitest --ui",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
Expand Down
5 changes: 5 additions & 0 deletions packages/claude-sidecar/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { defineConfig } from "vitest/config";
import { createCoverageConfig } from "../../vitest.coverage.shared";

export default defineConfig({
root: import.meta.dirname,
test: {
environment: "node",
include: ["src/**/*.test.ts"],
coverage: createCoverageConfig({
include: ["src/**/*.ts"],
exclude: ["src/index.ts"],
}),
},
});
4 changes: 4 additions & 0 deletions packages/shared/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { defineConfig } from "vitest/config";
import { createCoverageConfig } from "../../vitest.coverage.shared";

export default defineConfig({
root: import.meta.dirname,
test: {
environment: "node",
include: ["src/**/*.test.ts"],
coverage: createCoverageConfig({
include: ["src/**/*.ts"],
}),
},
});
11 changes: 11 additions & 0 deletions packages/ui/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from "path";
import { defineConfig } from "vitest/config";
import { createCoverageConfig } from "../../vitest.coverage.shared";

/**
* Vitest config for `@zedi/ui` package.
Expand All @@ -24,5 +25,15 @@ export default defineConfig({
environment: "jsdom",
setupFiles: [path.resolve(import.meta.dirname, "src/test/setup.ts")],
include: ["src/**/*.{test,spec}.{ts,tsx}"],
coverage: createCoverageConfig({
// In-house hooks/lib/sidebar only; flat `components/*.tsx` are shadcn vendored (AGENTS.md).
// 自前実装のみ。直下の `components/*.tsx` は shadcn 由来のため母数から除外する。
include: [
"src/hooks/**/*.{ts,tsx}",
"src/lib/**/*.{ts,tsx}",
"src/components/sidebar/**/*.{ts,tsx}",
"src/components/resizable.tsx",
],
}),
},
});
104 changes: 104 additions & 0 deletions scripts/run-workspace-coverage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env node
/**
* Run Vitest with coverage for every workspace that participates in the
* monorepo test matrix. Exits non-zero if any workspace fails.
*
* モノレポのテスト対象ワークスペースすべてで Vitest coverage を実行する。
* いずれかが失敗したら非ゼロで終了する。
*/
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

/** @type {Array<{ name: string; cwd?: string; args: string[]; preInstall?: boolean }>} */
const workspaces = [
{ name: "src/ (frontend)", args: ["vitest", "run", "--coverage"] },
{
name: "admin",
args: ["vitest", "run", "--coverage", "--config", "admin/vitest.config.ts"],
},
{
name: "@zedi/shared",
args: ["vitest", "run", "--coverage", "--config", "packages/shared/vitest.config.ts"],
},
{
name: "@zedi/ui",
args: ["vitest", "run", "--coverage", "--config", "packages/ui/vitest.config.ts"],
},
{
name: "@zedi/claude-sidecar",
args: ["vitest", "run", "--coverage", "--config", "packages/claude-sidecar/vitest.config.ts"],
},
];

const serverWorkspaces = [
{
name: "server/hocuspocus",
cwd: "server/hocuspocus",
preInstall: true,
args: ["vitest", "run", "--coverage", "--config", "vitest.config.ts"],
},
{
name: "server/mcp",
cwd: "server/mcp",
preInstall: true,
args: ["vitest", "run", "--coverage", "--config", "vitest.config.ts"],
},
{
name: "server/api",
cwd: "server/api",
preInstall: true,
args: ["vitest", "run", "--coverage"],
},
];

const includeServers = process.argv.includes("--with-servers");
const targets = includeServers ? [...workspaces, ...serverWorkspaces] : workspaces;

const spawnShell = process.platform === "win32";

/**
* @param {import("node:child_process").SpawnSyncReturns<Buffer | string>} result
*/
function exitOnSpawnFailure(result) {
if (result.error) {
console.error(result.error);
process.exit(1);
}
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}

/**
* @param {string} label
* @param {string[]} command
* @param {string} cwd
*/
function run(label, command, cwd) {
console.log(`\n=== Coverage: ${label} ===\n`);
const result = spawnSync("bunx", command, {
cwd,
stdio: "inherit",
env: process.env,
shell: spawnShell,
});
exitOnSpawnFailure(result);
}
Comment on lines +80 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Windows環境において、bunbunx などのコマンドを spawnSync で直接実行すると、シェル経由でないために ENOENT エラーが発生して実行に失敗することがあります。また、コマンドの起動自体に失敗した場合(result.error が存在する場合)、エラー内容がコンソールに出力されず、終了コード 1 でサイレントに終了してしまいます。\n\nこれを防ぐために、Windows環境では shell: true を有効にし、result.error が存在する場合はエラー内容を出力するように改善することを推奨します。

function run(label, command, cwd) {
  console.log("\\n=== Coverage: \${label} ===\\n");
  const result = spawnSync("bunx", command, {
    cwd,
    stdio: "inherit",
    env: process.env,
    shell: process.platform === "win32",
  });
  if (result.error) {
    console.error(result.error);
    process.exit(1);
  }
  if (result.status !== 0) {
    process.exit(result.status ?? 1);
  }
}


for (const workspace of targets) {
const cwd = path.join(repoRoot, workspace.cwd ?? ".");
if (workspace.preInstall) {
console.log(`\n--- Installing dependencies for ${workspace.name} ---\n`);
const install = spawnSync("bun", ["install", "--frozen-lockfile"], {
cwd,
stdio: "inherit",
env: process.env,
shell: spawnShell,
});
exitOnSpawnFailure(install);
}
Comment on lines +93 to +102

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

依存関係のインストール処理(bun install)においても、Windows環境での実行失敗を防ぐために shell: true を有効にし、起動失敗時のエラーハンドリング(install.error の出力)を追加することを推奨します。

  if (workspace.preInstall) {
    console.log("\\n--- Installing dependencies for \${workspace.name} ---\\n");
    const install = spawnSync("bun", ["install", "--frozen-lockfile"], {
      cwd,
      stdio: "inherit",
      env: process.env,
      shell: process.platform === "win32",
    });
    if (install.error) {
      console.error(install.error);
      process.exit(1);
    }
    if (install.status !== 0) {
      process.exit(install.status ?? 1);
    }
  }

run(workspace.name, workspace.args, cwd);
}
3 changes: 2 additions & 1 deletion server/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"sync:ai-models": "tsx scripts/sync-ai-models.ts",
"inspect:ai-models-cost": "tsx scripts/inspect-ai-models-cost.ts",
"test": "vitest",
"test:run": "vitest run"
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.1002.0",
Expand Down
5 changes: 5 additions & 0 deletions server/api/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { defineConfig } from "vitest/config";
import { createCoverageConfig } from "../../vitest.coverage.shared";

export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.{test,spec}.{ts,tsx}", "scripts/**/*.{test,spec}.{ts,tsx}"],
coverage: createCoverageConfig({
include: ["src/**/*.ts", "scripts/**/*.ts"],
exclude: ["src/types/**", "src/index.ts"],
}),
},
});
Loading
Loading