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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .changeset/metadata-core-cjs-entry-point-loads.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
---
"@objectstack/metadata-core": patch
---

fix(metadata-core): the CJS entry point loads again — `import.meta` is no longer emitted into `dist/index.cjs` (#12971)

`@objectstack/metadata-core` declares `"type": "module"` with a dual `exports`
map, so `require('@objectstack/metadata-core')` is a published, supported entry
point. Since 17.2.0 it was **unloadable**: `resolveInstalledSpecVersion()`
anchors its `@objectstack/spec` lookup with `createRequire(import.meta.url)` —
correct for the ESM output — and tsup emitted that identifier **verbatim** into
`dist/index.cjs`. `import.meta` outside an ES module is a **parse-time** error,
so the module never began executing:

```
node -e "require('@objectstack/metadata-core')"
SyntaxError: Cannot use 'import.meta' outside a module
```

The failure was total, not partial. Neither the `typeof require === 'function'`
fast path above the line nor the `try`/`catch` around it ever ran, so **every**
CJS consumer and **every** code path in the package was affected, not just
callers of `resolveInstalledSpecVersion()`. Measured downstream: a walled
enterprise runtime refused to boot because `@objectstack/organizations`
resolves through this condition and the fail-closed tenancy wall correctly
refuses to serve when the organization wall cannot load.

**The fix** is `shims: true` in this package's `tsup.config.ts` — the same one
line, for the same measured reason, already carried by `@objectstack/runtime`
and `@objectstack/metadata-protocol`. tsup rewrites `import.meta.url` in the
CJS output to a real `__filename`-derived URL, so both formats anchor on the
module's own file and resolve the same `@objectstack/spec/package.json`. Both
conditions now load and `resolveInstalledSpecVersion()` returns the identical
value in each.

No API, type or behaviour change, and the ESM half is untouched — measured
rather than assumed: rebuilding with and without the shim, the emitted ESM code
bytes are identical in every file, and the whole difference is the shared
chunk's content-hashed **filename** (`chunk-DDDKWTSW.js` → `chunk-46MG4YHS.js`)
and the `sourceMappingURL` line naming it. Chunk names are internal to the
package; no `exports` target moves. Nothing an author writes moves either.

**The class is now gated.** `pnpm check:dual-build-cjs-loads` (a step in the
required **Build Core** job) parses every emitted CommonJS file and `require()`s
every published `require` entry point in the workspace — 105 entries across 67
packages — so the next package to leak ESM-only syntax into its CJS output
fails at the commit that introduces it rather than in a consumer's release.
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -1528,6 +1528,22 @@ jobs:
if [ "$fail" -ne 0 ]; then exit 1; fi
echo "✓ all buildable capability packages ship a runtime JS entry"

# The sibling of the step above, one condition over: that step asks
# whether the declared entry EXISTS, this one whether it LOADS. #12971:
# one source line added `createRequire(import.meta.url)` to
# metadata-core, tsup emitted `import.meta` verbatim into
# `dist/index.cjs`, and outside an ES module that is a PARSE-time error
# — so the package's entire `require` condition was unloadable while
# every suite here stayed green (vitest resolves workspace packages
# through the `import` condition) and `pnpm build` stayed green too (the
# bytes emit fine; they just cannot be parsed by the runtime they are
# declared for). Downstream it refused to boot cloud's walled EE runtime.
# This lives in Build Core rather than the lint job because it reads a
# real dist/; with none it exits 3 (PREREQUISITE NOT MET), never a
# silent green. Its header is the authority on detail.
- name: Every published require entry point actually loads
run: pnpm check:dual-build-cjs-loads

- name: Analyze bundle size
run: pnpm --filter @objectstack/spec analyze

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,6 +127,7 @@
"check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs",
"check:published-readme-exports": "node scripts/check-published-readme-exports.mjs --self-test && node scripts/check-published-readme-exports.mjs",
"check:published-readme-links": "node scripts/check-published-readme-links.mjs --self-test && node scripts/check-published-readme-links.mjs",
"check:dual-build-cjs-loads": "node scripts/check-dual-build-cjs-loads.mjs --self-test && node scripts/check-dual-build-cjs-loads.mjs",
"check:type-check-coverage": "node scripts/check-type-check-coverage.mjs --self-test && node scripts/check-type-check-coverage.mjs",
"check:type-check-debt": "node scripts/check-type-check-coverage.mjs --self-test && node scripts/check-type-check-coverage.mjs --re-measure",
"check:driver-conformance": "node scripts/check-driver-conformance.mjs --self-test && node scripts/check-driver-conformance.mjs",
Expand Down
16 changes: 12 additions & 4 deletions packages/metadata-core/src/artifact-forward-conversion.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -221,10 +221,18 @@ export function resolveInstalledSpecVersion(): string | null {
} catch {
// fall through to the ESM anchor
}
// ESM build: anchor a require at this module's own URL. In the CJS build
// this branch is only reachable when the branch above already failed, and
// its transformed `import.meta.url` is `undefined` there — `createRequire`
// then throws and the catch below answers `null`, the documented posture.
// Anchor a require at this module's own URL. `import.meta.url` reads as
// written in the ESM build; in the CJS build tsup's `shims: true` (declared
// and argued in this package's `tsup.config.ts` — #12971) rewrites it to a
// real `__filename`-derived URL, so BOTH formats anchor on this module's own
// file and resolve the SAME `@objectstack/spec/package.json`.
//
// ⛔ That shim is what makes this line legal in the CJS output at all: at
// this build `target` esbuild emits `import.meta` verbatim, and outside an
// ES module that is a PARSE-time error — the whole `require` entry point
// becomes unloadable and the `catch` below never runs. Never assume the
// catch covers a missing shim; it cannot, and this file has already paid
// for the assumption once.
try {
const req = createRequire(import.meta.url);
const pkg = req('@objectstack/spec/package.json') as { version?: string };
Expand Down
24 changes: 24 additions & 0 deletions packages/metadata-core/tsup.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,5 +10,29 @@ export default defineConfig({
dts: !process.env.OS_SKIP_DTS,
format: ['esm', 'cjs'],
target: 'es2020',
// [#12971] LOAD-BEARING. `artifact-forward-conversion.ts` anchors its
// `@objectstack/spec` version lookup with `createRequire(import.meta.url)`
// — correct as written for the ESM output. At this `target` esbuild does
// NOT empty `import.meta` in the CJS output: it emits
// `createRequire(import.meta.url)` verbatim, and `import.meta` outside an
// ES module is a PARSE-time error — so without this line `dist/index.cjs`
// throws `SyntaxError: Cannot use 'import.meta' outside a module` at LOAD
// time and the package's whole `require` condition is unloadable, for every
// consumer and every code path (the guarding try/catch never runs; the
// module never begins executing). Measured downstream: cloud's walled EE
// runtime refused to boot because `@objectstack/organizations` resolves
// through this condition. `shims: true` makes tsup rewrite
// `import.meta.url` in the CJS build to a real `__filename`-derived value
// (its `assets/cjs_shims.js`), so BOTH formats anchor on this module's own
// file and resolve the SAME `@objectstack/spec/package.json`.
//
// Same line, same reason, same measurement as
// `packages/metadata-protocol/tsup.config.ts` (#11235) and
// `packages/runtime/tsup.config.ts` (#10993) — read either for the sibling
// history. `pnpm check:dual-build-cjs-loads` holds the class: it
// `require()`s every dual-built package's CJS entry point and reds on this
// exact SyntaxError. Need-based injection — nothing here references
// `__dirname`/`__filename`, so the ESM build's shim path is a no-op.
shims: true,
external: ['vitest'],
});
Loading
Loading