Просмотр исходного кода

ci: make validation reproducible

Erman HAVUÇ 3 недель назад
Родитель
Сommit
e8a90ef81d

+ 53 - 8
.github/workflows/ci.yml

@@ -20,11 +20,9 @@ concurrency:
 
 jobs:
   test:
+    name: Required / Bun from package.json
     runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        bun-version: [latest]
+    timeout-minutes: 10
 
     steps:
       - name: Checkout code
@@ -32,14 +30,61 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
+
+      - name: Install dependencies
+        run: bun ci
+
+      - name: Run formatting and lint checks
+        run: bun run check:ci
+
+      - name: Run typecheck
+        run: bun run typecheck
+
+      - name: Run tests
+        run: |
+          mkdir -p reports
+          bun test \
+            --coverage \
+            --coverage-reporter=text \
+            --coverage-reporter=lcov \
+            --coverage-dir=coverage \
+            --reporter=junit \
+            --reporter-outfile=reports/junit.xml
+
+      - name: Upload test and coverage reports
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: test-results-bun-pinned
+          path: |
+            reports/junit.xml
+            coverage/lcov.info
+          if-no-files-found: ignore
+          retention-days: 14
+
+      - name: Build
+        run: bun run build
+
+  bun-latest-canary:
+    name: Canary / Bun latest
+    continue-on-error: true
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup latest Bun
+        uses: oven-sh/setup-bun@v2
         with:
-          bun-version: ${{ matrix.bun-version }}
+          bun-version: latest
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
-      - name: Run lint
-        run: bun run lint
+      - name: Run formatting and lint checks
+        run: bun run check:ci
 
       - name: Run typecheck
         run: bun run typecheck

+ 1 - 3
.github/workflows/contributors.yml

@@ -37,11 +37,9 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
-        with:
-          bun-version: latest
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
       - name: Add contributor
         env:

+ 49 - 0
.github/workflows/opencode-compatibility.yml

@@ -0,0 +1,49 @@
+name: OpenCode Compatibility
+
+on:
+  workflow_dispatch:
+  schedule:
+    - cron: '17 6 * * 1'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  host-smoke:
+    name: Host ${{ matrix.channel }} / OpenCode ${{ matrix.opencode-version }}
+    continue-on-error: ${{ matrix.experimental }}
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - channel: supported
+            opencode-version: '1.18.13'
+            experimental: false
+          - channel: latest-canary
+            opencode-version: latest
+            experimental: true
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Setup Bun
+        uses: oven-sh/setup-bun@v2
+
+      - name: Install dependencies
+        run: bun ci
+
+      - name: Build
+        run: bun run build
+
+      - name: Verify OpenCode host compatibility
+        env:
+          OPENCODE_SMOKE_VERSION: ${{ matrix.opencode-version }}
+        run: bun run verify:host-smoke

+ 16 - 4
.github/workflows/package-smoke.yml

@@ -2,6 +2,20 @@ name: Package Smoke
 
 on:
   workflow_dispatch:
+  pull_request:
+    branches: [main, master]
+    paths:
+      - '.github/workflows/package-smoke.yml'
+      - 'package.json'
+      - 'bun.lock'
+      - 'tsconfig.json'
+      - 'oh-my-opencode-slim.schema.json'
+      - 'LICENSE'
+      - 'README*.md'
+      - 'scripts/generate-schema.ts'
+      - 'scripts/verify-opencode-host-smoke.ts'
+      - 'scripts/verify-release-artifact.ts'
+      - 'src/**'
   push:
     tags: ['v*']
 
@@ -12,11 +26,11 @@ concurrency:
 jobs:
   package-smoke:
     runs-on: ${{ matrix.os }}
+    timeout-minutes: 15
 
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        bun-version: [latest]
 
     steps:
       - name: Checkout code
@@ -24,11 +38,9 @@ jobs:
 
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
-        with:
-          bun-version: ${{ matrix.bun-version }}
 
       - name: Install dependencies
-        run: bun install --frozen-lockfile
+        run: bun ci
 
       - name: Build
         run: bun run build

+ 1 - 0
.gitignore

@@ -32,6 +32,7 @@ yarn-error.log*
 
 # Test coverage
 coverage/
+reports/
 
 # Temporary files
 tmp/

+ 7 - 0
docs/release.md

@@ -203,6 +203,13 @@ bun test
 bun run build
 ```
 
+CI installs the Bun version pinned by `packageManager` in `package.json` and
+uses `bun ci` so lockfile drift fails instead of rewriting `bun.lock`. Required
+CI uploads JUnit and LCOV reports. A non-blocking latest-Bun canary reports
+upcoming runtime incompatibilities, package smoke runs for package-affecting
+pull requests, and the scheduled OpenCode compatibility workflow checks both
+the pinned supported host and latest host canary.
+
 ### Skill Synchronization Hashes Gate
 
 If this release changes any bundled skill content (under `src/skills/`), you must populate the `LEGACY_MANAGED_SKILL_HASHES` table in `src/hooks/auto-update-checker/skill-sync.ts` with the hashes of the previously published versions of those skills (obtained from published npm package tarballs). This ensures existing users' installations are adopted and upgraded safely. If this is a migration-only release without skill changes, confirm the table is kept as-is.

+ 1 - 0
package.json

@@ -1,6 +1,7 @@
 {
   "name": "oh-my-opencode-slim",
   "version": "2.2.17",
+  "packageManager": "bun@1.3.14",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",

+ 2 - 0
scripts/codemap.md

@@ -21,6 +21,8 @@
 - `verify-opencode-host-smoke.ts`
   - Builds temporary OpenCode environment (bin from `bun add opencode-ai`), mounts the plugin tarball,
     launches `opencode serve`, and probes `http://127.0.0.1:<port>/global/health`.
+  - Uses `OPENCODE_SMOKE_VERSION` when set so scheduled CI can test the pinned supported host and latest-host canary;
+    defaults to `latest` for local and release smoke runs.
   - Captures logs and fails on `failed to load plugin` and `cannot find module` patterns.
 - All scripts are executable boundary files (`#!/usr/bin/env bun` / Node), with explicit temp-dir lifecycle management
   and defensive cleanup via `rmSync(..., { force: true, recursive: true })`.

+ 5 - 2
scripts/verify-opencode-host-smoke.ts

@@ -15,6 +15,7 @@ import { fileURLToPath } from 'node:url';
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
 const repoRoot = path.resolve(__dirname, '..');
 const distEntry = path.join(repoRoot, 'dist', 'index.js');
+const opencodeVersion = process.env.OPENCODE_SMOKE_VERSION?.trim() || 'latest';
 
 function fail(message: string): never {
   throw new Error(message);
@@ -197,8 +198,10 @@ async function verifyHostSmoke(tarballPath: string) {
       ),
     );
 
-    console.log('Installing opencode-ai into isolated test root...');
-    run('bun', ['add', 'opencode-ai@latest'], { cwd: hostDir });
+    console.log(
+      `Installing opencode-ai@${opencodeVersion} into isolated test root...`,
+    );
+    run('bun', ['add', `opencode-ai@${opencodeVersion}`], { cwd: hostDir });
 
     const opencodeBin = path.join(hostDir, 'node_modules', '.bin', 'opencode');
     if (!existsSync(opencodeBin)) {

+ 5 - 5
src/hooks/task-session-manager/board-injection.ts

@@ -120,10 +120,7 @@ type SyntheticTerminalOccurrenceLookup =
       reason: string;
     };
 
-type SyntheticTerminalProvenanceKind =
-  | 'explicit'
-  | 'host-message'
-  | 'legacy';
+type SyntheticTerminalProvenanceKind = 'explicit' | 'host-message' | 'legacy';
 
 const HOST_MESSAGE_OCCURRENCE_PREFIX = 'host-message:';
 
@@ -428,7 +425,10 @@ function findSyntheticTerminalOccurrence(
           origin.generationAtObservation === currentGeneration,
       )
       .map(([, origin]) => origin);
-    if (candidates.length === 1 && candidates[0].occurrenceID === occurrenceID) {
+    if (
+      candidates.length === 1 &&
+      candidates[0].occurrenceID === occurrenceID
+    ) {
       return { kind: 'matched', origin: candidates[0] };
     }
     if (candidates.length > 1) {

+ 3 - 1
src/hooks/task-session-manager/index.test.ts

@@ -5909,7 +5909,9 @@ describe('task-session-manager hook', () => {
       },
     });
     const hostOrigin = [
-      ...getBackgroundJobLifecycleLedger(board).syntheticTerminalOccurrences.values(),
+      ...getBackgroundJobLifecycleLedger(
+        board,
+      ).syntheticTerminalOccurrences.values(),
     ][0];
     if (!hostOrigin) throw new Error('host origin was not recorded');