Skip to content

feat: add configurable isolated subpath-import alias for generated packages - #236

Merged
danfma merged 1 commit into
mainfrom
004-configurable-import-alias
Jun 23, 2026
Merged

feat: add configurable isolated subpath-import alias for generated packages#236
danfma merged 1 commit into
mainfrom
004-configurable-import-alias

Conversation

@danfma

@danfma danfma commented Jun 23, 2026

Copy link
Copy Markdown
Owner

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 the MetanoImportAlias MSBuild property) sets the alias. When set (e.g. contracts):

  • internal specifiers become #contracts/<ns> (bare #contracts for root-namespace types);
  • the generated package.json gets only #contracts / #contracts/* entries scoped to the output directory;
  • the host project's own # / #/* 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 #/x collided with the host's own #/* alias and resolved to the wrong path, and Metano wrote a conflicting #/#/* entry into the host's package.json. One mechanism — an isolated alias key scoped to the output dir — fixes both the key collision and the depth problem.

Notable details

  • Cross-package imports (external [EmitPackage]) are unaffected — they use the referenced package's npm name, which never sees the producer's local imports.
  • Companion fix: trims a trailing slash that produced .../contracts//index.d.ts in nested-output package.json imports/exports.
  • The change is confined to the TypeScript target adapter; the target-agnostic core (Metano.Compiler) is untouched.
  • Alias-key normalization is centralized in PathNaming.NormalizeImportAliasPrefix, shared by the specifier builder, the package.json writer, and the cycle detector.
  • The alias participates in the incremental-cache fingerprint, so changing it invalidates stale output.

Verification

  • dotnet build clean (0 warnings/errors); dotnet csharpier . clean.
  • 1171 tests pass. New coverage: PathNaming unit, e2e golden with/without alias, package.json alias keys, host-# isolation, double-slash regression, cycle-under-alias.
  • In-repo samples regenerate byte-identical (backward compatible).
  • Manual e2e: transpiled SampleIssueTracker into src/abc/contracts of a host package that already defines #/* → generated imports use #contracts/..., the host #/* is preserved, no // in paths, and #contracts/issues/domain resolves to a real file.
  • Dual-agent review (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

  • Backfill the baseline flag catalog (specs/001-project-baseline-evolution/baseline/) when this merges.
  • Optional: an alias-validation diagnostic for invalid characters, and a cross-package no-op regression test.

Copilot AI review requested due to automatic review settings June 23, 2026 19:26
…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/
@danfma
danfma force-pushed the 004-configurable-import-alias branch from 689acb0 to a13c2e4 Compare June 23, 2026 19:28

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ImportAlias through the TypeScript target pipeline (CLI/MSBuild → TypeScriptTargetTypeTransformerPathNaming + CyclicReferenceDetector) and include it in the cache fingerprint.
  • Emit alias-scoped package.json#imports keys (#<alias> / #<alias>/*) when configured, preserving host #/* entries; keep output byte-identical when unset.
  • Add focused unit + e2e tests covering normalization, emitted import specifiers, package.json behavior, 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.

@danfma
danfma merged commit a0856f2 into main Jun 23, 2026
2 checks passed
@danfma
danfma deleted the 004-configurable-import-alias branch June 23, 2026 19:33

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines 381 to 382
["types"] = $"./{distBase}/*.d.ts",
["import"] = $"./{distBase}/*.js",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants