fix(tsconfig): stop four library tsconfigs from shadowing the workspace baseUrl - #1071
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
blove
enabled auto-merge (squash)
September 8, 2026 20:30
blove
force-pushed
the
blove/fix-library-baseurl-shadowing
branch
from
September 8, 2026 21:05
5e4d504 to
d27d362
Compare
Contributor
blove
force-pushed
the
blove/fix-library-baseurl-shadowing
branch
from
September 8, 2026 21:39
d27d362 to
3c39667
Compare
Contributor
…ce baseUrl
`libs/cockpit-runtime-bridge`, `libs/growth`, `libs/example-layouts` and
`libs/cockpit-registry` each re-declared `"baseUrl": "."` while extending
`tsconfig.base.json` and declaring no `paths` of their own. `baseUrl`
outranks the implicit `pathsBasePath`, so every inherited substitution
from the base `paths` map — which is workspace-root-relative, e.g.
`libs/design-tokens/src/index.ts` — was probed under the library folder
instead of the repo root.
`npx tsc --traceResolution` on a probe file in each library, before:
'baseUrl' option is set to '<root>/libs/example-layouts', using this
value to resolve non-relative module name '@threadplane/design-tokens'.
Trying substitution 'libs/design-tokens/src/index.ts', candidate module
location: 'libs/design-tokens/src/index.ts'.
File '<root>/libs/example-layouts/libs/design-tokens/src/index.ts' does
not exist.
Loading module '@threadplane/design-tokens' from 'node_modules' folder…
Module name '@threadplane/design-tokens' was successfully resolved to
'<root>/libs/design-tokens/src/index.ts' with Package ID
'@threadplane/design-tokens/src/index.ts@0.0.35'.
and after:
'baseUrl' option is set to '<root>', using this value to resolve
non-relative module name '@threadplane/design-tokens'.
Trying substitution 'libs/design-tokens/src/index.ts', candidate module
location: 'libs/design-tokens/src/index.ts'.
File '<root>/libs/design-tokens/src/index.ts' exists - use it as a name
resolution result.
Module name '@threadplane/design-tokens' was successfully resolved to
'<root>/libs/design-tokens/src/index.ts'.
The builds were green either way only because the npm workspace symlink
under `node_modules/@threadplane/*` happens to land on the same source
file; the `Package ID` in the before-trace is the tell that resolution
went through `node_modules` rather than the paths map.
`baseUrl` in `tsconfig.base.json` itself stays: Nx's `createTmpTsConfig`
writes build tsconfigs whose `paths` carry non-relative `dist/libs/...`
entries, and with no `baseUrl` in the chain TypeScript raises TS5090 and
discards the whole map. `libs/growth-capture` and `apps/growth-research`
pair `baseUrl` with their own `"paths": {}`, where it does real
directory-resolution work, and `apps/website` declares a complete `paths`
of its own — all left alone.
`scripts/tsconfig-path-inheritance.spec.mjs` guards both halves: a
structural check that no library tsconfig inheriting the base `paths`
re-declares `baseUrl`, and a resolution check through the TypeScript API
asserting `@threadplane/design-tokens` resolves from each of the four
libraries with no `packageId` — that is, through the paths map and not
the node_modules symlink. Both assertions fail when the `baseUrl` line is
put back.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
blove
force-pushed
the
blove/fix-library-baseurl-shadowing
branch
from
September 8, 2026 22:12
3c39667 to
14482e2
Compare
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
libs/cockpit-runtime-bridge,libs/growth,libs/example-layoutsandlibs/cockpit-registryeach re-declared"baseUrl": "."in their owntsconfig.jsonwhile extendingtsconfig.base.jsonand declaring nopathsof their own.baseUrloutranks the implicitpathsBasePath, so every inherited substitution from the basepathsmap — which is workspace-root-relative, e.g.libs/design-tokens/src/index.ts— was probed under the library folder instead of the repo root. This PR removes those fourbaseUrllines so the libraries inherit the base config'sbaseUrl(the repo root).Why it matters (the evidence)
The build passes either way, so a trace is the only proof the change does something real.
npx tsc --traceResolutionon a probe file importing@threadplane/design-tokensfrom each library.Before (identical shape in all four;
example-layoutsshown):The paths substitution missed. Resolution only landed on the right file because the npm workspace symlink under
node_modules/@threadplane/happens to point at the same source. ThePackage IDon the last line is the tell: TypeScript treated the library as an installed package, not a workspace path mapping.After (again identical in all four):
The substitution now hits on the first try, with no
node_modulesfallback and noPackage ID.What was deliberately left alone
tsconfig.base.json's ownbaseUrl— Nx'screateTmpTsConfig(@nx/js/src/utils/buildable-libs-utils.js) writes generated build tsconfigs whosepathscarry non-relativedist/libs/...entries. With nobaseUrlanywhere in the chain TypeScript raises TS5090 and discards the entire paths map, breakingrender:build:productionandcockpit-shell:build. The existing comment there already explains this.libs/growth-captureandapps/growth-research— both pairbaseUrlwith their own"paths": {}, sobaseUrlis doing real directory-resolution work and shadows nothing.apps/website— declares its own completepaths.cockpit/**andexamples/**e2e/python tsconfigs — each declares its ownpathsor does not extend the base.apps/lifecycle/tsconfig.json— noted as out of scope, see below.Guard spec
scripts/tsconfig-path-inheritance.spec.mjs(new, 5 tests) guards both halves of the fix:libs/*/tsconfig*.json, follow itsextendschain, and fail any config that reachestsconfig.base.jsonwithout declaring its ownpathsyet re-declaresbaseUrl.ts.getParsedCommandLineOfConfigFile, callts.resolveModuleName('@threadplane/design-tokens', …), and assert both that it lands onlibs/design-tokens/src/index.tsand thatresolvedModule.packageIdisundefined— i.e. it came through the paths map, not the node_modules symlink.Mutation-checked: restoring
"baseUrl": "."inlibs/growth/tsconfig.jsonfails both assertions.Verification
nx run-many -t lint,test,buildacrosscockpit-runtime-bridge, growth, example-layouts, cockpit-registry, chat, ag-ui, langgraph, render, a2ui, telemetry, cockpit-shell, scriptsnx run chat:type-tests,ag-ui:type-tests,langgraph:type-testsnx run render:build:productionnx run cockpit-shell:buildvitest run --root apps/websiteGROWTH_FORM_POLICY=growth_v1 nx build websitenx run-many -t build --projects=examples-chat-angular,cockpit-chat-debug-angular,cockpit-render-repeat-loops-angularFollow-up noted, not taken
apps/lifecycle/tsconfig.jsonhas the same shape — it extendsapps/lifecycle/tsconfig.runtime-base.json, which extendstsconfig.base.json, and it declares"baseUrl": "."with nopathsof its own. It was outside this task's scope, and nothing underapps/lifecyclecurrently imports a mapped@threadplane/*specifier, so the shadowing is inert there today. The new guard spec is scoped tolibs/**and therefore does not cover it. Worth cleaning up separately, alongside a decision about widening the guard toapps/**.🤖 Generated with Claude Code