diff --git a/.github/workflows/windows-render.yml b/.github/workflows/windows-render.yml index 0a829147c..1490a97f2 100644 --- a/.github/workflows/windows-render.yml +++ b/.github/workflows/windows-render.yml @@ -150,7 +150,16 @@ jobs: - name: Install dependencies shell: pwsh - run: bun install --frozen-lockfile + # Force the pre-1.3 "hoisted" linker on Windows. Bun 1.3.x's default + # "isolated" linker creates nested workspace junctions that don't + # materialize reliably on Windows GHA runners — manifests as + # `Cannot find package 'esbuild'` walking up from + # `packages/producer/build.mjs` and `EPERM stat` on + # `packages/producer/node_modules/@fontsource/*` from generators. + # See oven-sh/bun#23615, #18354, #10146. Linux CI keeps isolated; + # the lockfile is linker-agnostic so this flag is safe with + # --frozen-lockfile. + run: bun install --frozen-lockfile --linker=hoisted - name: Build all packages shell: pwsh @@ -371,7 +380,16 @@ jobs: - name: Install dependencies shell: pwsh - run: bun install --frozen-lockfile + # Force the pre-1.3 "hoisted" linker on Windows. Bun 1.3.x's default + # "isolated" linker creates nested workspace junctions that don't + # materialize reliably on Windows GHA runners — manifests as + # `Cannot find package 'esbuild'` walking up from + # `packages/producer/build.mjs` and `EPERM stat` on + # `packages/producer/node_modules/@fontsource/*` from generators. + # See oven-sh/bun#23615, #18354, #10146. Linux CI keeps isolated; + # the lockfile is linker-agnostic so this flag is safe with + # --frozen-lockfile. + run: bun install --frozen-lockfile --linker=hoisted - name: Build shell: pwsh diff --git a/packages/core/src/studio-api/routes/files.ts b/packages/core/src/studio-api/routes/files.ts index 33474d253..e801d5b59 100644 --- a/packages/core/src/studio-api/routes/files.ts +++ b/packages/core/src/studio-api/routes/files.ts @@ -306,8 +306,21 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void { const skipped: string[] = []; const invalid: Array<{ name: string; reason: string }> = []; - for (const [, value] of formData.entries()) { - if (!(value instanceof File)) continue; + // @types/node v25 narrows the ambient `FormData.entries()` to + // `[string, string]` in workspaces where another dep declares an + // `onmessage` global (it trips the worker branch of v25's conditional + // File type). At runtime the value is still `File | string` — cast the + // iterator so the rest of this block keeps type-checking on every + // bun-install layout (hoisted on Windows surfaces this; isolated on + // Linux happens to keep v24 in scope). + type FileLike = { + readonly name: string; + readonly size: number; + arrayBuffer(): Promise; + }; + const entries = formData.entries() as unknown as Iterable<[string, FileLike | string]>; + for (const [, value] of entries) { + if (typeof value === "string") continue; // Strip path separators — browsers may include directory components const name = value.name.split("/").pop()?.split("\\").pop() ?? "";