Skip to content

fix(ci): unbreak format check on main + skip cli build:fonts when present#778

Merged
jrusso1020 merged 4 commits into
mainfrom
fix/ci-format-and-windows-esbuild
May 13, 2026
Merged

fix(ci): unbreak format check on main + skip cli build:fonts when present#778
jrusso1020 merged 4 commits into
mainfrom
fix/ci-format-and-windows-esbuild

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented May 13, 2026

Summary

Narrowing this PR to the two pieces I'm confident in. The deeper Windows render fix is being iterated separately in #765.

1. Format check (oxfmt)

packages/core/package.json and packages/shader-transitions/package.json had their publishConfig keys reordered to a non-canonical order by the v0.6.1 release commit (82fd2967). Releases push directly to main without going through PR CI, so the drift wasn't caught — bun run format:check has been failing on every push since. Fix: re-run oxfmt. This is blocking every PR on the repo.

2. @hyperframes/cli build:fonts skip-when-present

packages/cli's build:fonts ran tsx ../producer/scripts/generate-font-data.ts unconditionally on every cli build. The script walks packages/producer/node_modules/@fontsource/* junctions via require.resolve — these trip EPERM on Windows GHA runners (long-running bun-on-Windows workspace junction bugs: oven-sh/bun#23615, #18354, #10146).

fontData.generated.ts is committed to git, so regeneration is only needed when fonts change. Matches the skip-when-present pattern already in @hyperframes/producer's own build:fonts. Doesn't fix Windows render end-to-end (the producer build itself still trips junction issues — that's #765's territory), but removes one of the failure points.

Why not more?

I tried several broader fixes (hoist esbuild to root, externalize all build deps, bunfig.toml linker = "hoisted", dedup esbuild version across the workspace, the full set of changes from #765). Each surfaced a new junction-EPERM in a different part of the build chain — the underlying bun-on-Windows isolated-install bugs are pervasive and Miguel is already actively iterating on a real fix in #765. Bundling those experiments into this PR was making the diff noisy and the regression surface large. Better to ship the two pieces that are obviously correct and stay out of the way.

Test plan

  • bun run format:check
  • bun run lint
  • bun run build
  • @hyperframes/cli build:fonts exits 0 both when file is present (skip) and when missing (regenerate, byte-identical output)
  • CI passes

🤖 Generated with Claude Code

@jrusso1020 jrusso1020 force-pushed the fix/ci-format-and-windows-esbuild branch from 1c9f43d to 86a836e Compare May 13, 2026 02:49
…sent

Two narrow fixes pulled out of a larger Windows-CI investigation:

## 1. Format check (`oxfmt`)

`packages/core/package.json` and `packages/shader-transitions/package.json`
had their `publishConfig` keys reordered to a non-canonical order by the
v0.6.1 release commit (`82fd2967`). Releases push directly to main without
going through PR CI, so the drift wasn't caught and `bun run format:check`
has been failing on every push since. Fix: re-run `oxfmt`.

## 2. `@hyperframes/cli` `build:fonts` skip-when-present

`packages/cli`'s `build:fonts` script regenerated
`packages/producer/src/services/fontData.generated.ts` unconditionally on
every cli build. The script reads `@fontsource/*` packages via
`require.resolve(...)`, which walks `packages/producer/node_modules/@fontsource/*`
junctions — these trip `EPERM: operation not permitted, stat` on Windows
GHA runners because of long-running bun-on-Windows workspace junction bugs
(see oven-sh/bun#23615, #18354, #10146).

`fontData.generated.ts` is committed to git, so the regeneration is only
needed when fonts actually change. Match the skip-when-present pattern
already in `@hyperframes/producer`'s own `build:fonts`. Doesn't fix the
Windows render verification end-to-end (the producer build itself still
trips junction issues — being tackled separately in #765), but at least
stops `cli build:fonts` from being its own failure point on Windows.
@jrusso1020 jrusso1020 force-pushed the fix/ci-format-and-windows-esbuild branch from 86a836e to c8a3e5f Compare May 13, 2026 03:06
@jrusso1020 jrusso1020 changed the title fix(ci): unbreak format check + Windows render verification on main fix(ci): unbreak format check on main + skip cli build:fonts when present May 13, 2026
Pushing further to actually get Windows render verification green, not just
work around it.

## What's wrong on Windows

Bun 1.3's default `isolated` linker creates nested workspace junctions under
`packages/*/node_modules/` on Windows GHA runners. Those junctions don't
materialize reliably — Node's `realpathSync` returns `EPERM` on stat, and
ESM resolution returns `ERR_MODULE_NOT_FOUND`. Every Windows build since
PR #748 has tripped this in one of three places:

- `packages/producer/build.mjs` importing `esbuild`
- `packages/producer/scripts/generate-font-data.ts` reading `@fontsource/*`
- `packages/producer` running `tsc` to emit `.d.ts`s

Long-running bun bugs: oven-sh/bun#23615, #18354, #10146.

## Fix

**1. `--linker=hoisted` for the Windows install step** (workflow change,
Windows only). Hoisted layout puts deps as real directories at the workspace
root + workspace package node_modules. No junctions, no Windows-specific
path quirks. Linux CI keeps the default isolated linker; the lockfile is
linker-agnostic so `--frozen-lockfile` is still valid.

**2. Source-level FormData narrowing in `packages/core/src/studio-api/routes/files.ts`**
(needed because the hoisted layout exposes a `@types/node@25` typecheck
issue that the isolated layout hides). With v25 + an `onmessage` global in
scope, the ambient `FormData.entries()` infers `[string, string]` instead of
`[string, File | string]`, so the `value instanceof File` check breaks at
`TS2358`. Cast the iterator to a `[string, FileLike | string]` shape and
narrow via `typeof value === "string"`. Identical runtime behavior; works
under both v24 (isolated layout, what Linux CI sees) and v25 (hoisted, what
Windows CI sees with this change).

## Verification

- `bun install --frozen-lockfile` (isolated, default): full build green
- `bun install --frozen-lockfile --linker=hoisted`: full build green, core
  typecheck passes, `@hyperframes/core` 853 tests pass
- Format/lint clean on both layouts
The Windows install failures (`ENOENT: failed copying files from cache to
destination for package @types/node` / `esbuild`) are caused by bun creating
workspace-scoped nested installs under
`node_modules/@hyperframes/<pkg>/node_modules/...`. Those nested paths only
exist because each workspace package pinned a different `@types/node` /
`esbuild` major:

- root: `@types/node ^25.0.10`, core: `^24.10.13`, cli/engine/producer: `^22`
- core/cli: `esbuild ^0.25.x`, producer: `^0.27.2`

Each major-version gap forces bun to install a workspace-scoped copy in a
deep `node_modules/@hyperframes/<pkg>/node_modules/<dep>/node_modules/...`
tree that bun can't reliably materialize on Windows GHA runners. Aligning
versions lets bun dedup to a single root-hoisted install per dep, and the
nested workspace block disappears from `bun.lock` entirely.

## Alignment

- `@types/node` → `^25.0.10` across root, core, cli, engine, producer
- `esbuild` → `^0.25.12` across cli, core, producer
- `tsx` → `^4.21.0` across producer (matches root + core)

## Source-level v25 compat (already in this PR)

@types/node v25 declares `File` as an interface (not a class) and exposes a
conditional global where `FormData.entries()` narrows to `[string, string]`
when an `onmessage` global is in scope. `packages/core/src/studio-api/routes/files.ts`'s
`value instanceof File` check was relying on the v24 class declaration —
already cast the iterator to `Iterable<[string, FileLike | string]>` in the
prior commit.

Two more v25 source fixes here:
- `packages/cli/src/commands/init.ts`
- `packages/cli/src/whisper/normalize.ts`

`Dirent.path` was removed in @types/node v25 (deprecated alias for
`parentPath` since Node 20.12). Drop the `?? e.path` fallback.

## Verification

Both install layouts now build clean end-to-end:

- `bun install` (isolated, default): full build green, 853 core tests pass,
  typecheck green across all 7 packages
- `bun install --linker=hoisted` (Windows CI): same result
- `bun.lock` no longer contains any `@hyperframes/<pkg>/<dep>` nested
  workspace entries — 70+ lines of nested install blocks gone
PR #755 added the typegpu-adapter regression test scaffolding (meta.json,
src/index.html, output/compiled.html) but left the output.mp4 golden
baseline ungenerated:

> Note: output.mp4 baseline needs to be generated in CI — the local …

Every \`regression-shards (fast)\` run since #755 merged has failed with
\`Snapshot not found: /app/packages/producer/tests/typegpu-adapter/output/output.mp4.
Run with --update to create it.\`

Generated via the canonical Docker path per CLAUDE.md:

    bun run --cwd packages/producer docker:test \\
      --update --suite typegpu-adapter

Stored via Git LFS (already configured for
\`packages/producer/tests/*/output/output.mp4\` in \`.gitattributes\`).
@jrusso1020 jrusso1020 merged commit c2648bc into main May 13, 2026
42 checks passed
@jrusso1020 jrusso1020 deleted the fix/ci-format-and-windows-esbuild branch May 13, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants