feat: add configurable isolated subpath-import alias for generated packages - #236
Conversation
…ckages Add an opt-in `--import-alias` CLI flag (and matching `MetanoImportAlias` MSBuild property) that sets an isolated Node.js subpath-imports key for internal (same-project) imports in the generated TypeScript. When set (e.g. `contracts`), internal specifiers become `#contracts/<ns>` and the package.json gets only `#contracts`/`#contracts/*` entries scoped to the output directory, leaving a host project's own `#` alias untouched. This lets generated code be emitted into a subfolder of an existing npm project without colliding with that project's `#` alias or resolving to the wrong path. When unset, output is byte-identical to before (default `#`/`#/*`) — fully backward compatible. Cross-package imports (external [EmitPackage]) are unaffected. Also fixes a double-slash path bug in nested-output package.json imports/exports. The change is confined to the TypeScript target adapter; the target-agnostic core is untouched. Alias normalization is centralized in PathNaming.NormalizeImportAliasPrefix. Spec: specs/004-configurable-import-alias/
689acb0 to
a13c2e4
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces an opt-in, configurable, isolated Node.js subpath-import alias for internal imports in Metano-generated TypeScript. Developers can specify a custom alias via the new --import-alias CLI flag or the MetanoImportAlias MSBuild property. When configured, generated internal imports and package.json entries use the isolated alias key (e.g., #contracts/...) instead of the default #, preventing collisions when emitting code into a subfolder of an existing npm project. The changes are isolated to the TypeScript target adapter and include a fix for a nested-output double-slash path bug in package.json entries, along with comprehensive unit and integration tests. There are no review comments to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR adds an opt-in --import-alias / MetanoImportAlias configuration to isolate internal TypeScript subpath imports for generated code (e.g. #contracts/... instead of #/...), allowing Metano output to live under a host package subfolder without colliding with the host project’s own #/#/* mappings. It also includes a companion fix to prevent doubled slashes in generated package.json import/export paths for nested outputs.
Changes:
- Thread
ImportAliasthrough the TypeScript target pipeline (CLI/MSBuild →TypeScriptTarget→TypeTransformer→PathNaming+CyclicReferenceDetector) and include it in the cache fingerprint. - Emit alias-scoped
package.json#importskeys (#<alias>/#<alias>/*) when configured, preserving host#/*entries; keep output byte-identical when unset. - Add focused unit + e2e tests covering normalization, emitted import specifiers,
package.jsonbehavior, cycle diagnostics under alias, and the double-slash regression.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Metano.Tests/TranspileHelper.cs | Adds importAlias plumbing for test transpilation helpers. |
| tests/Metano.Tests/ImportAliasTests.cs | New unit/e2e coverage for alias normalization and emitted import specifiers. |
| tests/Metano.Tests/EmitPackageTests.cs | Adds package.json#imports tests for alias-scoped keys, host #/* preservation, and no-// regression. |
| tests/Metano.Tests/CyclicReferenceTests.cs | Verifies cyclic-reference diagnostics still work and render the configured alias key. |
| src/Metano.Compiler.TypeScript/TypeScriptTarget.cs | Adds ImportAlias and includes it in the cache configuration fingerprint; forwards to transformer. |
| src/Metano.Compiler.TypeScript/Transformation/TypeTransformer.cs | Forwards alias into PathNaming and CyclicReferenceDetector. |
| src/Metano.Compiler.TypeScript/Transformation/PathNaming.cs | Centralizes alias-key normalization and uses it when building internal import specifiers. |
| src/Metano.Compiler.TypeScript/Transformation/CyclicReferenceDetector.cs | Makes local-import detection + diagnostic display alias-aware. |
| src/Metano.Compiler.TypeScript/PackageJsonWriter.cs | Adds importAlias parameter and emits alias-scoped imports keys; trims outputPrefix to avoid doubled slashes. |
| src/Metano.Compiler.TypeScript/Commands.cs | Adds CLI parameter and threads it into target + package.json writer. |
| src/Metano.Build/build/Metano.Build.targets | Adds MetanoImportAlias property → --import-alias argument wiring + docs. |
| specs/004-configurable-import-alias/* | Adds full spec/plan/research/tasks/contracts/quickstart artifacts for the feature. |
| CLAUDE.md | Updates the “Active feature plan” pointer to spec 004. |
| .specify/feature.json | Points Spec Kit feature directory to spec 004. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 689acb0bb4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ["types"] = $"./{distBase}/*.d.ts", | ||
| ["import"] = $"./{distBase}/*.js", |
There was a problem hiding this comment.
Point alias wildcard imports at namespace barrels
When --import-alias is used, generated cross-namespace imports are shaped like #contracts/models, but these package-import targets rewrite that specifier to ./dist/.../models.js / ./src/.../models.ts. The generator creates namespace barrels at models/index.ts (and the built output is models/index.js), so TypeScript/Node/Bun do not fall through to the directory index after the package imports match fails; any aliased generated file that imports a non-root namespace will fail to resolve in the core nested-output scenario.
Useful? React with 👍 / 👎.
What
Adds an opt-in, configurable, isolated Node.js subpath-import alias for internal (same-project) imports in generated TypeScript.
A new
--import-alias <name>CLI flag (mirrored by theMetanoImportAliasMSBuild property) sets the alias. When set (e.g.contracts):#contracts/<ns>(bare#contractsfor root-namespace types);package.jsongets only#contracts/#contracts/*entries scoped to the output directory;#/#/*alias is left untouched.When unset, output is byte-identical to today (
#/#/*) — fully backward compatible.Why
Emitting generated code into a subfolder of an existing npm project broke twice: the hardcoded
#/xcollided with the host's own#/*alias and resolved to the wrong path, and Metano wrote a conflicting#/#/*entry into the host'spackage.json. One mechanism — an isolated alias key scoped to the output dir — fixes both the key collision and the depth problem.Notable details
[EmitPackage]) are unaffected — they use the referenced package's npm name, which never sees the producer's localimports..../contracts//index.d.tsin nested-outputpackage.jsonimports/exports.Metano.Compiler) is untouched.PathNaming.NormalizeImportAliasPrefix, shared by the specifier builder, thepackage.jsonwriter, and the cycle detector.Verification
dotnet buildclean (0 warnings/errors);dotnet csharpier .clean.PathNamingunit, e2e golden with/without alias,package.jsonalias keys, host-#isolation, double-slash regression, cycle-under-alias.SampleIssueTrackerintosrc/abc/contractsof a host package that already defines#/*→ generated imports use#contracts/..., the host#/*is preserved, no//in paths, and#contracts/issues/domainresolves to a real file.compiler-man+bob): both approve; findings applied.Spec
Feature spec and design artifacts under
specs/004-configurable-import-alias/(FR-001..010).Deferred follow-ups
specs/001-project-baseline-evolution/baseline/) when this merges.