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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .bun-version

This file was deleted.

32 changes: 32 additions & 0 deletions .changeset/remove-bun-toolchain-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
"@inkeep/open-knowledge": patch
---

Migrate the build, test, and CI toolchain from Bun to pnpm 10 and Vitest 4.

This is an internal toolchain migration with no runtime behavior change for the
published CLI: it still runs on Node (`engines.node >= 24`, shebang
`#!/usr/bin/env node`). The workspace is now a standard pnpm workspace
(`pnpm-workspace.yaml` + `pnpm-lock.yaml`), all five test tiers run under Vitest
via a `bun:test` compatibility shim and a `Bun.*` facade, and every TypeScript
entry point runs through Node + `tsx`. `bun.lock`, `bunfig.toml`, and
`.bun-version` are removed, and the public mirror ships pnpm.

Bun-specific fences retired as part of the cutover:

- `findBunLockMetadataDrift` and the `bun.lock` metadata-drift guard (the pnpm
frozen-lockfile check supersedes it).
- `check:bun-run-fallthrough` (the `bun run` PATH-fallthrough guard) and its
test, along with the `run-bun-if-available.sh` root fan-out wrapper.
- `bun-install-ci.sh`, the retry/idle-timeout install wrapper that existed to
work around a Bun install-hang issue; pnpm's install path replaces it.
- The `run-test-dom.sh` `--isolate` mock-leak mitigation (the per-file
fresh-module-registry workaround for a Bun `mock.module` in-place-patch leak);
the DOM tier is now a dedicated Vitest project with `isolate: true`.

Two small, required behavioral deltas ride along with the swap: `ok diagnose
--redact` bundles now derive doc-name redaction tokens with sha256 instead of
BLAKE2b-256 (Node's OpenSSL rejects BLAKE2 `outputLength`), so the `doc:<hex>`
tokens differ while the in-bundle inverse map still resolves them; and the
file-copy API now returns HTTP 409 for `ERR_FS_CP_DIR_TO_NON_DIR` /
`ERR_FS_CP_NON_DIR_TO_DIR` (previously an unhandled 500).
8 changes: 4 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
#
# All variables are OPTIONAL. A fresh clone runs end-to-end with no .env file:
#
# bun install
# bun run check
# bun run --filter @inkeep/open-knowledge-app dev
# pnpm install
# pnpm run check
# pnpm --filter @inkeep/open-knowledge-app run dev
#
# Copy this file to .env and uncomment any you want to set.

Expand All @@ -19,6 +19,6 @@
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

# --- Dev server ---
# Custom port for the Vite dev server (bun run dev, default: 5173). The CLI
# Custom port for the Vite dev server (pnpm run dev, default: 5173). The CLI
# (ok start / ok ui) has its own port resolution and is not affected by this.
# VITE_PORT=9999
6 changes: 3 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

## How this was verified

<!-- How you tested: `bun run check` output, manual steps, before/after screenshots for UI changes. -->
<!-- How you tested: `pnpm check` output, manual steps, before/after screenshots for UI changes. -->

## Checklist

- [ ] Ran `bun run check` (lint, typecheck, tests) locally
- [ ] Added a changeset (`bun run changeset`) if this changes behavior
- [ ] Ran `pnpm check` (lint, typecheck, tests) locally
- [ ] Added a changeset (`pnpm changeset`) if this changes behavior
- [ ] Updated docs if this changes a user-facing surface
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and agree to license my contribution under the project's terms (CLA)

Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/select-beta-to-promote.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// promotions share one source of version truth.
//
// Extracted here (rather than inline bash) so the release-critical selection is
// unit-tested under `bun test --cwd .github/scripts` (the OK `check` gate),
// unit-tested under `vitest run --config vitest.scripts.config.ts` (the OK `check` gate),
// mirroring the scripts/compute-next-beta.mjs precedent. The pure core
// (parseBetaTags, selectPromotion) takes its git/GitHub boundary as injected
// dependencies so tests need no live repo or API.
Expand Down
60 changes: 32 additions & 28 deletions .github/workflows/desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
# `build/okglass.icon` into `Assets.car` via Apple's `actool`, which only
# ships with Xcode 26+.
runs-on: macos-26
# 45 min budget. Local `bun run build:mac:unsigned` baseline on M3 Max
# 45 min budget. Local `pnpm run build:mac:unsigned` baseline on M3 Max
# is ~3-4 min wall-clock; CI runners are ~2× slower; signing +
# notarization adds 2-5 min of Apple round-trip on top. 45 min leaves
# headroom for cold cache and notary-service queue spikes.
Expand All @@ -85,18 +85,25 @@ jobs:
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
bun-version-file: ".bun-version"
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-ok-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-ok-pnpm-store-

# Turbo cache keeps `build` fast across runs. Keying on bun.lock
# Turbo cache keeps `build` fast across runs. Keying on pnpm-lock.yaml
# warm-caches across commits that don't change dependencies — cold
# cache only when the lockfile actually moves. `github.sha` in the
# restore-keys fallback still picks up same-commit re-runs.
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: .turbo
key: ${{ runner.os }}-turbo-desktop-${{ hashFiles('bun.lock') }}
key: ${{ runner.os }}-turbo-desktop-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-turbo-desktop-

Expand All @@ -106,7 +113,7 @@ jobs:
# modules rebuilt against Electron's Node ABI for a packaged build.
env:
ELECTRON_SKIP_REBUILD: "0"
run: bash scripts/bun-install-ci.sh
run: pnpm install --frozen-lockfile

# electron-builder.yml's `extraResources` rule copies from
# `packages/cli/dist/public/`, which the CLI's `build:assets` populates
Expand All @@ -120,8 +127,8 @@ jobs:
# before app), and run the two filters in sequence to enforce
# app → cli ordering.
#
# `bun run --filter=...` was the previous approach; it does not
# honor turbo's dep graph (only runs the named package's `build`
# A bare per-package `--filter` run was the previous approach; it does
# not honor turbo's dep graph (only runs the named package's `build`
# script in isolation). After the workspace started shipping
# `.mjs` builds for `@inkeep/open-knowledge-server` and
# `@inkeep/open-knowledge-core` (specs/2026-04-24-desktop-packaging-
Expand All @@ -131,8 +138,8 @@ jobs:
# resolve entry for package @inkeep/open-knowledge-server".
- name: Build workspace (produces packages/cli/dist/public for extraResources)
run: |
bunx turbo run build --filter=@inkeep/open-knowledge-app
bunx turbo run build --filter=@inkeep/open-knowledge
pnpm exec turbo run build --filter=@inkeep/open-knowledge-app
pnpm exec turbo run build --filter=@inkeep/open-knowledge

# Decide signed-vs-unsigned path BEFORE invoking electron-builder so
# the artifact can be named to match. We key off CSC_LINK alone: it's
Expand Down Expand Up @@ -197,10 +204,10 @@ jobs:
# for signed and unsigned modes.
- name: Build electron-vite bundles
working-directory: packages/desktop
run: bun run build:desktop
run: pnpm run build:desktop

# Force-install both darwin-arm64 and darwin-x64 prebuilt binaries for
# @napi-rs/keyring before electron-builder's universal merge runs. Bun
# @napi-rs/keyring before electron-builder's universal merge runs. pnpm
# under workspace conditions only installs the host-arch optionalDependency
# (cpu constraint), so the darwin-x64 binary is missing on an arm64
# runner. @electron/universal's lipo merge then either errors or hangs
Expand All @@ -215,20 +222,17 @@ jobs:
working-directory: packages/desktop
run: node scripts/prepare-universal.mjs

# Step B: package via electron-builder. Split from step A across
# separate runner steps (rather than chained via && in a single shell)
# because empirically — verified across runs 24920389870 (combined,
# broken) and 24923907843 (split, correct) — chaining `bun run build:desktop
# && electron-builder ...` in one shell produces an asar containing a
# stale workspace-dep `package.json` (the pre-`exports.development` /
# pre-`files: ["dist", "src"]` snapshot) and omits `dist/`. Same
# commands as separate workflow steps produce a correct asar with the
# current `package.json` + the built `dist/`. Root cause not fully
# characterized — most likely bun.run's workspace-state caching across
# the `&&` boundary, or electron-builder's bun-mode walker reading from
# a snapshot taken when bun's wrapper opened the script. Specs/2026-04-24-
# desktop-packaging-first-boot/ §10 OQ4 captures the open question;
# the empirical fix (separate steps) is sufficient for shipping.
# Step B: package via electron-builder. Kept as a separate runner step
# from step A (rather than chained via && in a single shell). The split
# was originally forced by a packaging bug in the previous script runner:
# chaining the build and electron-builder in one shell produced an asar
# containing a stale workspace-dep `package.json` (the pre-`exports.development`
# / pre-`files: ["dist", "src"]` snapshot) and omitted `dist/`, while the
# same commands as separate workflow steps produced a correct asar with the
# current `package.json` + the built `dist/`. That runner is gone, but the
# separate-steps structure is preserved defensively — it is free and the
# failure mode was expensive to diagnose. Specs/2026-04-24-desktop-packaging-
# first-boot/ §10 OQ4 captured the original open question.
- name: Package DMG (${{ steps.signmode.outputs.mode }})
env:
CSC_LINK: ${{ steps.signmode.outputs.mode == 'signed' && secrets.CSC_LINK || '' }}
Expand All @@ -249,9 +253,9 @@ jobs:
working-directory: packages/desktop
run: |
if [[ "${{ steps.signmode.outputs.mode }}" == "unsigned" ]]; then
bunx electron-builder --mac --publish never -c.mac.identity=null
pnpm exec electron-builder --mac --publish never -c.mac.identity=null
else
bunx electron-builder --mac --publish never
pnpm exec electron-builder --mac --publish never
fi

# Gate on success() — on build failure, there is no DMG, so running
Expand Down
29 changes: 18 additions & 11 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,21 @@ jobs:
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
bun-version-file: ".bun-version"
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-ok-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-ok-pnpm-store-

- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: .turbo
key: ${{ runner.os }}-turbo-desktop-release-${{ hashFiles('bun.lock') }}
key: ${{ runner.os }}-turbo-desktop-release-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-turbo-desktop-release-
${{ runner.os }}-turbo-desktop-
Expand All @@ -149,7 +156,7 @@ jobs:
# Rebuild native modules against Electron's Node ABI — same as
# desktop-build.yml.
ELECTRON_SKIP_REBUILD: "0"
run: bash scripts/bun-install-ci.sh
run: pnpm install --frozen-lockfile

# electron-builder.yml's `extraResources` rule copies from
# `packages/cli/dist/public/`, which the CLI's `build:assets` populates
Expand All @@ -163,8 +170,8 @@ jobs:
# before app), and run the two filters in sequence to enforce
# app → cli ordering.
#
# `bun run --filter=...` was the previous approach; it does not
# honor turbo's dep graph (only runs the named package's `build`
# A bare per-package `--filter` run was the previous approach; it does
# not honor turbo's dep graph (only runs the named package's `build`
# script in isolation). After the workspace started shipping
# `.mjs` builds for `@inkeep/open-knowledge-server` and
# `@inkeep/open-knowledge-core` (specs/2026-04-24-desktop-packaging-
Expand Down Expand Up @@ -238,7 +245,7 @@ jobs:
# lines 7-10), workspace package.json files on disk at the tagged SHA
# always lag the release tag — agents-private mirrors a cycle-base
# version (e.g. `0.5.0-beta.5`) that `release.yml` overrides
# transiently via `jq` before `bun changeset publish`. The Desktop
# transiently via `jq` before `pnpm exec changeset publish`. The Desktop
# pipeline needs the same primitive: `tsdown` inlines
# `import pkgJson from "../package.json"` into
# `packages/cli/dist/constants-*.mjs` at Build time, and
Expand Down Expand Up @@ -272,8 +279,8 @@ jobs:

- name: Build workspace (produces packages/cli/dist/public for extraResources)
run: |
bunx turbo run build --filter=@inkeep/open-knowledge-app
bunx turbo run build --filter=@inkeep/open-knowledge
pnpm exec turbo run build --filter=@inkeep/open-knowledge-app
pnpm exec turbo run build --filter=@inkeep/open-knowledge

- name: Detect signing mode
id: signmode
Expand Down Expand Up @@ -325,7 +332,7 @@ jobs:
echo "::notice::APPLE_API_KEY materialized to $KEY_PATH"

# Force-install both darwin-arm64 and darwin-x64 prebuilt binaries for
# @napi-rs/keyring before electron-builder's universal merge runs. Bun
# @napi-rs/keyring before electron-builder's universal merge runs. pnpm
# under workspace conditions only installs the host-arch optionalDependency
# (cpu constraint), so the darwin-x64 binary is missing on an arm64
# runner. @electron/universal's lipo merge then either errors or hangs
Expand Down Expand Up @@ -422,7 +429,7 @@ jobs:
# `pipefail` (set above) makes the `if` see the electron-builder exit
# code through `tee`; tee mirrors output to the CI log AND captures it
# for the transient-vs-real classification below.
if { bun run build:desktop && bunx electron-builder --mac --publish always \
if { pnpm run build:desktop && pnpm exec electron-builder --mac --publish always \
--config.extraMetadata.version=${{ steps.channel.outputs.version }} \
--config.publish.channel=${{ steps.channel.outputs.channel }} \
--config.publish.releaseType=${{ steps.channel.outputs.release_type }} ; } 2>&1 | tee "${attempt_log}"; then
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/native-config-prebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,16 @@ jobs:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
bun-version-file: .bun-version
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-ok-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-ok-pnpm-store-

- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
Expand Down Expand Up @@ -120,14 +126,14 @@ jobs:
tool: cargo-zigbuild

- name: Install JS dependencies
run: bash scripts/bun-install-ci.sh
run: pnpm install --frozen-lockfile

- name: Build addon
working-directory: packages/native-config
shell: bash
# Appends the per-target flags to the package's own
# `napi build --platform --release` script.
run: bun run build -- --target ${{ matrix.settings.target }} ${{ matrix.settings.napiFlags }}
run: pnpm run build -- --target ${{ matrix.settings.target }} ${{ matrix.settings.napiFlags }}

- name: Upload binding
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/promote-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
# main HEAD.
token: ${{ steps.app-token.outputs.token }}
# We need to operate on the beta tag's SHA for the package.json
# override + bun changeset publish. We resolve and check it out
# override + pnpm exec changeset publish. We resolve and check it out
# explicitly below; default-branch checkout above is just so the
# workflow scripts (this file) live next to a writable working
# tree.
Expand Down Expand Up @@ -313,7 +313,7 @@ jobs:
# Under the zero-commit-bump cadence, beta release bodies are the
# source of truth — packages/*/CHANGELOG.md no longer accumulates
# per-beta entries (only per-stable, written by main-reset's
# `bun changeset version` at promotion time). Each beta body carries
# `pnpm exec changeset version` at promotion time). Each beta body carries
# ONLY the delta introduced in that beta (compute-next-beta.mjs filters
# against the prior beta's embedded consumed-set marker), so the cycle's
# betas concatenated are a clean, duplication-free set of the changes
Expand Down Expand Up @@ -415,7 +415,7 @@ jobs:
# Same-repo repository_dispatch into release.yml. release.yml
# listens for event_type=publish-stable, checks out the stable
# tag, applies the transient package.json#version override, and
# runs `bun changeset publish --tag latest --provenance` via
# runs `pnpm exec changeset publish --tag latest --provenance` via
# npm Trusted Publishing OIDC.
#
# Why we delegate to release.yml instead of publishing here:
Expand Down
Loading
Loading