You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
rstest's testEnvironment: 'node' externalizes node_modules dependencies, so an ESM import { X } from '<cjs-dep>' in code under test goes through Node's native ESM-CJS interop. Node detects named exports from CJS via cjs-module-lexer static analysis -- but google-protobuf's generated *_pb.js modules (e.g. google-protobuf/google/protobuf/timestamp_pb) export via goog.object.extend(exports, proto.google.protobuf), which is invisible to that static analysis. The imported binding is silently undefined at runtime (the namespace object contains only default).
Production is unaffected: the server compiles with tsc to CommonJS require, which works fine. This makes it a test-environment-only landmine that surfaces as a confusing TypeError: ...Timestamp is not a constructor deep inside otherwise-correct code. In src/server/authn.ts's case the throw was additionally swallowed by a broad catch, so it manifested only as a wrong HTTP status.
Why it matters
Silent failure mode: the import succeeds, the binding is undefined, and the failure surfaces far from the cause -- potentially masked entirely by error handling (as happened in authn.ts).
Test/production divergence: tests exercise a different module-loading path than production, so a passing or failing test says nothing reliable about the CJS-interop-affected code path.
src/server (mitigated -- see below): authn.ts, api.ts, and project-creation.ts all import Timestamp from google-protobuf/google/protobuf/timestamp_pb.
Residual risk: any other workspace package that runs rstest with testEnvironment: 'node' and imports a google-protobuf subpath -- or any other CJS dep whose exports cjs-module-lexer cannot see -- will hit the same silent-undefined failure cold.
Mitigation applied (server only)
On branch production-risk-burndown, src/server/rstest.config.mts gained output.bundleDependencies: ['google-protobuf/google/protobuf/timestamp_pb']. Bundling routes the module through rspack's dynamic CJS interop (the same path the local bundled src/server/schemas/*_pb.js files already take), while the jspb runtime stays external so only one copy exists.
Possible approaches
A shared rstest base config (or documented convention in docs/dev/typescript.md) that bundles known goog-style CJS deps for every package using testEnvironment: 'node'.
A lint/CI check flagging named ESM imports from google-protobuf/** subpaths in packages tested under rstest.
Longer term: migrate off goog-style generated protobuf JS (e.g. protobuf-es), which removes the lexer-invisible export pattern entirely.
Context
Identified while adding tests for #927 on branch production-risk-burndown: the temp-user-creation path in src/server/authn.ts threw under rstest because Timestamp was undefined.
Problem
rstest's
testEnvironment: 'node'externalizesnode_modulesdependencies, so an ESMimport { X } from '<cjs-dep>'in code under test goes through Node's native ESM-CJS interop. Node detects named exports from CJS via cjs-module-lexer static analysis -- but google-protobuf's generated*_pb.jsmodules (e.g.google-protobuf/google/protobuf/timestamp_pb) export viagoog.object.extend(exports, proto.google.protobuf), which is invisible to that static analysis. The imported binding is silentlyundefinedat runtime (the namespace object contains onlydefault).Production is unaffected: the server compiles with tsc to CommonJS
require, which works fine. This makes it a test-environment-only landmine that surfaces as a confusingTypeError: ...Timestamp is not a constructordeep inside otherwise-correct code. Insrc/server/authn.ts's case the throw was additionally swallowed by a broad catch, so it manifested only as a wrong HTTP status.Why it matters
undefined, and the failure surfaces far from the cause -- potentially masked entirely by error handling (as happened in authn.ts).Components affected
src/server(mitigated -- see below):authn.ts,api.ts, andproject-creation.tsall importTimestampfromgoogle-protobuf/google/protobuf/timestamp_pb.testEnvironment: 'node'and imports a google-protobuf subpath -- or any other CJS dep whose exports cjs-module-lexer cannot see -- will hit the same silent-undefined failure cold.Mitigation applied (server only)
On branch
production-risk-burndown,src/server/rstest.config.mtsgainedoutput.bundleDependencies: ['google-protobuf/google/protobuf/timestamp_pb']. Bundling routes the module through rspack's dynamic CJS interop (the same path the local bundledsrc/server/schemas/*_pb.jsfiles already take), while the jspb runtime stays external so only one copy exists.Possible approaches
docs/dev/typescript.md) that bundles known goog-style CJS deps for every package usingtestEnvironment: 'node'.google-protobuf/**subpaths in packages tested under rstest.Context
Identified while adding tests for #927 on branch
production-risk-burndown: the temp-user-creation path insrc/server/authn.tsthrew under rstest becauseTimestampwasundefined.