@objectstack/metadata-core's CJS entry point is unloadable since #12843 — import.meta is emitted verbatim into dist/index.cjs, which is a PARSE-time error, so the guarding try/catch never runs.
Measured
At fc8a33935 (current main tip), from a clean pnpm turbo run build --force:
$ node -e "require('./dist/index.cjs')" # in packages/metadata-core
Warning: Failed to load the ES module: .../packages/metadata-core/dist/index.cjs.
REQUIRE FAILED: SyntaxError - Cannot use 'import.meta' outside a module
dist/index.cjs:810:
const req = _module.createRequire.call(void 0, import.meta.url);
Source: packages/metadata-core/src/artifact-forward-conversion.ts:229, in resolveInstalledSpecVersion().
Why the existing guard does not hold
The function is written to be safe in both module systems, and its comment states the intended posture:
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.
That assumes the bundler rewrites import.meta.url in the CJS output. It does not — the identifier is emitted verbatim. And import.meta outside an ES module is a syntax error at parse time, not a runtime undefined: the module never begins executing, so neither the typeof require === 'function' fast path nor the try/catch around createRequire is ever reached. The whole CJS entry point of the package is unloadable, for every consumer and every code path — not just callers of resolveInstalledSpecVersion().
Bisect
packages/metadata-core/package.json declares "type": "module" with a dual exports map (require to ./dist/index.cjs), so the CJS condition is a published, supported entry point.
Downstream blast radius (measured in cloud)
Filed from cloud#1712, the consumer bump that cannot land because of this. On a cloud worktree pinned at fc8a33935, the enterprise runtime refuses to boot: @objectstack/organizations is resolved through the CJS condition, fails to load, and the ADR-0093 D5 fail-closed tenancy wall then correctly refuses to serve:
✖ FATAL: tenancy posture 'isolated' was requested but @objectstack/organizations could not be loaded,
so the organization wall is INACTIVE. Refusing to boot ...
cause: Cannot use 'import.meta' outside a module
Consequences in objectstack-ai/cloud's only required check (build-and-test):
| suite |
result at fc8a33935 |
apps/objectos-ee test/signup-membership-policy.e2e.test.ts |
3 failed / 2 passed (5) — every case that boots the walled posture |
apps/objectos-ee test:hotcrm-acceptance |
2 files failed, 12 of 13 tests skipped — collection-level, same cause |
apps/cloud |
284/284 passed (does not boot the walled EE runtime) |
packages/service-cloud |
1579/1579 passed (likewise) |
The break is not dodgeable by picking an earlier consumer pin: 54e2d3692 is an ancestor of fc8a33935, and cloud#1712 needs a SHA at or past fc8a33935 (the os migrate host-composition fix, #12938 / #12952).
Suggested direction, not a prescription
The contract question is how resolveInstalledSpecVersion() should anchor its resolution in the CJS build at all. Two shapes worth weighing:
- Keep the dual-build and stop emitting
import.meta into CJS — e.g. resolve the ESM anchor behind the bundler's own define/shim so the CJS output carries a real undefined, which is what the current comment already documents as the intended behaviour. Smallest change; keeps the documented posture true instead of aspirational.
- Drop the ESM branch and anchor only on the ambient
require plus an explicit caller-supplied version, so there is one resolution path rather than two that must agree.
Worth a gate either way: nothing currently asserts that each dual-built package's require entry point actually loads. A require() smoke over the CJS conditions in the build would have caught this at the commit that introduced it, and would hold the class rather than this one line.
@objectstack/metadata-core's CJS entry point is unloadable since #12843 —import.metais emitted verbatim intodist/index.cjs, which is a PARSE-time error, so the guarding try/catch never runs.Measured
At
fc8a33935(currentmaintip), from a cleanpnpm turbo run build --force:dist/index.cjs:810:Source:
packages/metadata-core/src/artifact-forward-conversion.ts:229, inresolveInstalledSpecVersion().Why the existing guard does not hold
The function is written to be safe in both module systems, and its comment states the intended posture:
That assumes the bundler rewrites
import.meta.urlin the CJS output. It does not — the identifier is emitted verbatim. Andimport.metaoutside an ES module is a syntax error at parse time, not a runtimeundefined: the module never begins executing, so neither thetypeof require === 'function'fast path nor thetry/catcharoundcreateRequireis ever reached. The whole CJS entry point of the package is unloadable, for every consumer and every code path — not just callers ofresolveInstalledSpecVersion().Bisect
b489d3c725e8(the SHA cloud pinned before this):git grep import.meta -- packages/metadata-core/src/→ 0 hits.fc8a33935: 1 live hit, at the line above. Introduced by54e2d3692— feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door (feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door #12843).packages/metadata-core/package.jsondeclares"type": "module"with a dualexportsmap (requireto./dist/index.cjs), so the CJS condition is a published, supported entry point.Downstream blast radius (measured in cloud)
Filed from cloud#1712, the consumer bump that cannot land because of this. On a cloud worktree pinned at
fc8a33935, the enterprise runtime refuses to boot:@objectstack/organizationsis resolved through the CJS condition, fails to load, and the ADR-0093 D5 fail-closed tenancy wall then correctly refuses to serve:Consequences in
objectstack-ai/cloud's only required check (build-and-test):fc8a33935apps/objectos-eetest/signup-membership-policy.e2e.test.tsapps/objectos-eetest:hotcrm-acceptanceapps/cloudpackages/service-cloudThe break is not dodgeable by picking an earlier consumer pin:
54e2d3692is an ancestor offc8a33935, and cloud#1712 needs a SHA at or pastfc8a33935(theos migratehost-composition fix, #12938 / #12952).Suggested direction, not a prescription
The contract question is how
resolveInstalledSpecVersion()should anchor its resolution in the CJS build at all. Two shapes worth weighing:import.metainto CJS — e.g. resolve the ESM anchor behind the bundler's own define/shim so the CJS output carries a realundefined, which is what the current comment already documents as the intended behaviour. Smallest change; keeps the documented posture true instead of aspirational.requireplus an explicit caller-supplied version, so there is one resolution path rather than two that must agree.Worth a gate either way: nothing currently asserts that each dual-built package's
requireentry point actually loads. Arequire()smoke over the CJS conditions in the build would have caught this at the commit that introduced it, and would hold the class rather than this one line.