From f10885347913bfe009ccf14a245e3f06f066d806 Mon Sep 17 00:00:00 2001 From: joshunrau Date: Wed, 5 Aug 2026 13:31:11 -0400 Subject: [PATCH 1/3] fix(instrument-bundler): keep the vendored esbuild module out of tree shaking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sideEffects: ["**/cli.ts"]` marks every other module in the package as pure, but the whole body of `vendor/esbuild.ts` is a side effect: a conditional top-level `await import()` assigning to hoisted `var`s. esbuild therefore drops it from any consumer bundle while still inlining the namespace access down to the bare `build` identifier, so every call throws `ReferenceError: build is not defined`. This is why `serve-instrument@2.2.0` cannot compile any instrument at all — its published `dist/cli.js` contains no reference to esbuild whatsoever. That failure was invisible because the catch branches in `build` were inverted: a `ReferenceError` does not satisfy `$BuildFailure`, so the parse failed and the ZodError describing the schema mismatch was thrown as the cause of `Failed to Compile`, discarding the real error. Meanwhile a genuine esbuild failure, which does parse, was reported as `Unknown Error` with no `kind`, so `InstrumentErrorFallback` never rendered a code frame for it. Report an esbuild failure as `Failed to Compile` / `ESBUILD_FAILURE`, passing the original error rather than the parsed copy so `cause instanceof Error` still holds downstream, and name any other error in the message while keeping it as the cause. Co-Authored-By: Claude Opus 5 (1M context) --- packages/instrument-bundler/package.json | 7 ++++--- packages/instrument-bundler/src/build.ts | 13 ++++++++++--- packages/instrument-bundler/src/vendor/esbuild.ts | 5 +++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/instrument-bundler/package.json b/packages/instrument-bundler/package.json index 5ca205a00..149034df6 100644 --- a/packages/instrument-bundler/package.json +++ b/packages/instrument-bundler/package.json @@ -2,9 +2,6 @@ "name": "@opendatacapture/instrument-bundler", "type": "module", "version": "2.2.0", - "sideEffects": [ - "**/cli.ts" - ], "license": "Apache-2.0", "publishConfig": { "access": "public" @@ -14,6 +11,10 @@ "url": "https://github.com/DouglasNeuroInformatics/OpenDataCapture.git", "directory": "packages/instrument-bundler" }, + "sideEffects": [ + "**/cli.ts", + "**/vendor/esbuild.ts" + ], "exports": { ".": "./src/index.ts" }, diff --git a/packages/instrument-bundler/src/build.ts b/packages/instrument-bundler/src/build.ts index 53aeb1a87..3647d1731 100644 --- a/packages/instrument-bundler/src/build.ts +++ b/packages/instrument-bundler/src/build.ts @@ -9,7 +9,7 @@ import * as esbuild from './vendor/esbuild.js'; import type { BundlerInput } from './schemas.js'; import type { BuildOutput } from './types.js'; -import type { BuildResult } from './vendor/esbuild.js'; +import type { BuildFailure, BuildResult } from './vendor/esbuild.js'; const DEFAULT_REACT_PACKAGE = 'react@19.x'; @@ -37,6 +37,10 @@ function resolveJsxImportSource(inputs: BundlerInput[]): string { return `/runtime/v1/${packages.values().next().value ?? DEFAULT_REACT_PACKAGE}`; } +function describeError(err: unknown): string { + return err instanceof Error ? `${err.name}: ${err.message}` : `${typeof err}: ${String(err)}`; +} + function parseBuildResult(result: BuildResult): BuildOutput { const cssOutput = result.outputFiles?.find((output) => output.path.endsWith('bundle.css')); const jsOutput = result.outputFiles?.find((output) => output.path.endsWith('bundle.js')); @@ -95,9 +99,12 @@ export async function build({ } catch (err) { const parseResult = await $BuildFailure.safeParseAsync(err); if (parseResult.success) { - throw new InstrumentBundlerError('Unknown Error', { cause: err }); + // the original error, rather than the parsed copy, so that `cause instanceof Error` holds downstream + throw new InstrumentBundlerError('Failed to Compile', { cause: err as BuildFailure, kind: 'ESBUILD_FAILURE' }); } - throw new InstrumentBundlerError('Failed to Compile', { cause: parseResult.error, kind: 'ESBUILD_FAILURE' }); + // anything esbuild did not report as a compilation failure is a fault in the bundler itself, not in the + // instrument, so name it here instead of discarding it in favor of the schema mismatch that detected it + throw new InstrumentBundlerError(`Unexpected error while invoking esbuild: ${describeError(err)}`, { cause: err }); } return parseBuildResult(result); } diff --git a/packages/instrument-bundler/src/vendor/esbuild.ts b/packages/instrument-bundler/src/vendor/esbuild.ts index f9d65d5ef..10e466a4e 100644 --- a/packages/instrument-bundler/src/vendor/esbuild.ts +++ b/packages/instrument-bundler/src/vendor/esbuild.ts @@ -14,6 +14,11 @@ declare module 'esbuild-wasm' { } } +/** + * The exports below are assigned by a side effect, so this file must stay listed in the `sideEffects` + * field of package.json: a bundler permitted to treat it as pure drops the assignments while inlining + * the imports of it, leaving consumers with a bare `ReferenceError: build is not defined` at runtime. + */ if (typeof window === 'undefined') { var { build, transform } = await import('esbuild'); } else { From 6d15d492dad4a715465cf3c6b9fed2c47402da07 Mon Sep 17 00:00:00 2001 From: joshunrau Date: Wed, 5 Aug 2026 13:38:27 -0400 Subject: [PATCH 2/3] update libnest --- apps/api/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index ed90a00fe..7b413eb36 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,7 +21,7 @@ "@casl/prisma": "^1.5.1", "@douglasneuroinformatics/libcrypto": "catalog:", "@douglasneuroinformatics/libjs": "catalog:", - "@douglasneuroinformatics/libnest": "^8.3.1", + "@douglasneuroinformatics/libnest": "^8.4.1", "@douglasneuroinformatics/libpasswd": "catalog:", "@douglasneuroinformatics/libstats": "catalog:", "@faker-js/faker": "^9.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 515d915bf..6680d110c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -231,8 +231,8 @@ importers: specifier: 'catalog:' version: 3.2.1(neverthrow@8.2.0)(zod@vendor+zod@3.x) '@douglasneuroinformatics/libnest': - specifier: ^8.3.1 - version: 8.3.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-fastify@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24))(@nestjs/testing@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-express@11.1.24))(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3))(@swc/types@0.1.26)(fastify@5.8.5)(neverthrow@8.2.0)(reflect-metadata@0.1.14)(rollup@4.61.1)(rxjs@7.8.2)(typescript@6.0.3)(vite@6.4.3(@types/node@24.13.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.8.2)(yaml@2.9.0))(vitest@4.1.8)(zod@vendor+zod@3.x) + specifier: ^8.4.1 + version: 8.4.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-fastify@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24))(@nestjs/testing@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-express@11.1.24))(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3))(@swc/types@0.1.26)(fastify@5.8.5)(neverthrow@8.2.0)(reflect-metadata@0.1.14)(rollup@4.61.1)(rxjs@7.8.2)(typescript@6.0.3)(vite@6.4.3(@types/node@24.13.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.8.2)(yaml@2.9.0))(vitest@4.1.8)(zod@vendor+zod@3.x) '@douglasneuroinformatics/libpasswd': specifier: 'catalog:' version: 0.0.3(typescript@6.0.3) @@ -2151,8 +2151,8 @@ packages: neverthrow: ^8.2.0 zod: ^3.25.67 || 4.x - '@douglasneuroinformatics/libnest@8.3.1': - resolution: {integrity: sha512-YRO+0YNvru2dZzehGWYHbq/lZB/y39uwuSuW9GNmy9rW4d0lS+PiXSyM3EHYUU5uRIN3I+cB5zxUJTA9t+ZjQg==} + '@douglasneuroinformatics/libnest@8.4.1': + resolution: {integrity: sha512-3DMm1P1NeaK1CsI1T5GwvFpaCxK0qZ8Dy5P7++Cn7exuDnR3Ym/AiclDPsuD94I2bciMBSL+a4NZdhv7wt0Agg==} engines: {node: 22.x || 24.x} hasBin: true peerDependencies: @@ -11903,7 +11903,7 @@ snapshots: type-fest: 4.41.0 zod: link:vendor/zod@3.x - '@douglasneuroinformatics/libnest@8.3.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-fastify@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24))(@nestjs/testing@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-express@11.1.24))(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3))(@swc/types@0.1.26)(fastify@5.8.5)(neverthrow@8.2.0)(reflect-metadata@0.1.14)(rollup@4.61.1)(rxjs@7.8.2)(typescript@6.0.3)(vite@6.4.3(@types/node@24.13.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.8.2)(yaml@2.9.0))(vitest@4.1.8)(zod@vendor+zod@3.x)': + '@douglasneuroinformatics/libnest@8.4.1(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-fastify@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24))(@nestjs/testing@11.1.24(@nestjs/common@11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@11.1.24)(@nestjs/platform-express@11.1.24))(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3))(@swc/types@0.1.26)(fastify@5.8.5)(neverthrow@8.2.0)(reflect-metadata@0.1.14)(rollup@4.61.1)(rxjs@7.8.2)(typescript@6.0.3)(vite@6.4.3(@types/node@24.13.0)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.8.2)(yaml@2.9.0))(vitest@4.1.8)(zod@vendor+zod@3.x)': dependencies: '@douglasneuroinformatics/libjs': 3.2.1(neverthrow@8.2.0)(zod@vendor+zod@3.x) '@nestjs/common': 11.1.24(reflect-metadata@0.1.14)(rxjs@7.8.2) From da892c2ee1468ed872304b42ffe70eea940ec42d Mon Sep 17 00:00:00 2001 From: joshunrau Date: Wed, 5 Aug 2026 13:38:38 -0400 Subject: [PATCH 3/3] increment version --- package.json | 2 +- packages/instrument-bundler/package.json | 2 +- packages/instrument-guidelines/package.json | 2 +- packages/playground-url/package.json | 2 +- packages/serve-instrument/package.json | 2 +- runtime/v1/package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index bcbe6c407..5b128f785 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "opendatacapture", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "private": true, "packageManager": "pnpm@10.34.3", "license": "Apache-2.0", diff --git a/packages/instrument-bundler/package.json b/packages/instrument-bundler/package.json index 149034df6..bc4181c5d 100644 --- a/packages/instrument-bundler/package.json +++ b/packages/instrument-bundler/package.json @@ -1,7 +1,7 @@ { "name": "@opendatacapture/instrument-bundler", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/instrument-guidelines/package.json b/packages/instrument-guidelines/package.json index 9811418ff..a815d9360 100644 --- a/packages/instrument-guidelines/package.json +++ b/packages/instrument-guidelines/package.json @@ -1,7 +1,7 @@ { "name": "@opendatacapture/instrument-guidelines", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "description": "Guidelines for authoring Open Data Capture instruments, intended to be read by an AI agent (e.g. Claude Code).", "license": "Apache-2.0", "publishConfig": { diff --git a/packages/playground-url/package.json b/packages/playground-url/package.json index 377e678a8..cb61b3d6c 100644 --- a/packages/playground-url/package.json +++ b/packages/playground-url/package.json @@ -1,7 +1,7 @@ { "name": "@opendatacapture/playground-url", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "sideEffects": [ "**/cli.ts" ], diff --git a/packages/serve-instrument/package.json b/packages/serve-instrument/package.json index 725da652d..539fa07e3 100644 --- a/packages/serve-instrument/package.json +++ b/packages/serve-instrument/package.json @@ -1,7 +1,7 @@ { "name": "@opendatacapture/serve-instrument", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/runtime/v1/package.json b/runtime/v1/package.json index 87e9047aa..b5f59beec 100644 --- a/runtime/v1/package.json +++ b/runtime/v1/package.json @@ -1,7 +1,7 @@ { "name": "@opendatacapture/runtime-v1", "type": "module", - "version": "2.2.0", + "version": "2.2.1", "author": { "name": "Douglas Neuroinformatics", "email": "support@douglasneuroinformatics.ca"