Skip to content

fix: add --src-root for subdirectory-aware package.json paths (#50)#51

Merged
danfma merged 3 commits intomainfrom
fix/src-root-prefix
Apr 13, 2026
Merged

fix: add --src-root for subdirectory-aware package.json paths (#50)#51
danfma merged 3 commits intomainfrom
fix/src-root-prefix

Conversation

@danfma
Copy link
Copy Markdown
Owner

@danfma danfma commented Apr 13, 2026

Summary

  • Add --src-root CLI parameter (MetanoSrcRoot MSBuild property) to specify the TypeScript source root relative to the package root
  • When output targets a subdirectory (e.g., src/domain/), the output prefix (domain) is applied to dist paths and export subpaths
  • Change imports/exports to merge into existing objects instead of overwriting — user-defined entries for other subpaths survive untouched
  • Update FR-030 spec and feature matrix

Example

With MetanoOutputDir = src/domain/:

Before (wrong):

"exports": { ".": { "types": "./dist/index.d.ts" } }

After (correct):

"exports": { "./domain": { "types": "./dist/domain/index.d.ts" } }

Test plan

  • 5 new/modified tests in EmitPackageTests (subdirectory prefix, merge behavior, explicit srcRoot)
  • 351 .NET tests pass
  • sample-todo: 18 JS tests pass
  • sample-issue-tracker: 65 JS tests pass
  • sample-todo-service: 19 JS tests pass
  • Existing sample package.json exports unchanged (backward compatible)

Closes #50

When MetanoOutputDir targets a subdirectory of the source tree (e.g.,
src/domain/), the generated package.json imports/exports now include
the correct dist prefix (e.g., dist/domain/) so paths mirror the
build tool's directory structure.

Also changes imports/exports to merge into existing objects instead of
overwriting — user-defined entries for other subpaths survive untouched.

New CLI parameter: --src-root / MetanoSrcRoot (default: inferred from
first segment of the output path).

Closes #50
Copilot AI review requested due to automatic review settings April 13, 2026 23:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds subdirectory-aware package.json generation for TypeScript outputs by introducing a configurable source root and applying an output prefix to dist/import/export paths, while changing imports/exports updates to merge into existing objects.

Changes:

  • Add --src-root CLI option / MetanoSrcRoot MSBuild property and plumb it into PackageJsonWriter.
  • Compute an output prefix (e.g., domain) when output targets a subdirectory (e.g., src/domain) and apply it to dist paths and export subpaths.
  • Switch imports/exports updates from overwrite to merge; update/extend tests accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Metano.Tests/EmitPackageTests.cs Updates tests for merge semantics and new subdirectory-prefix behavior; adds srcRoot override test.
src/Metano.Compiler.TypeScript/PackageJsonWriter.cs Implements srcRoot/output prefix logic and merges generated imports/exports into existing package.json.
src/Metano.Compiler.TypeScript/Commands.cs Adds srcRoot parameter to CLI and passes it through to PackageJsonWriter.
src/Metano.Build/buildTransitive/Metano.Build.targets Adds MetanoSrcRoot property wiring to --src-root for transitive builds.
src/Metano.Build/build/Metano.Build.targets Adds MetanoSrcRoot property wiring to --src-root for direct builds.
js/sample-todo-service/package.json Reorders imports entries (likely due to merge/serialization order).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Metano.Compiler.TypeScript/PackageJsonWriter.cs Outdated
Comment thread src/Metano.Compiler.TypeScript/PackageJsonWriter.cs
Comment thread src/Metano.Compiler.TypeScript/PackageJsonWriter.cs Outdated
Comment thread tests/Metano.Tests/EmitPackageTests.cs
danfma added 2 commits April 13, 2026 20:26
)

- Replace MergeJsonObject with ReplacePreservingUserEntries that starts
  from the generated object, copying only non-managed user entries.
  Stale transpiler keys are automatically dropped.
- Exports are fully replaced (not merged) since all subpaths are
  transpiler-controlled and cannot be distinguished from user entries.
- Imports use managedKeys set ({"#", "#/*"}) to remove stale aliases
  while preserving user-defined entries like "#custom/*".
- Validate that srcRelative is a descendant of resolvedSrcRoot. Emit
  MS0007 warning and fall back to empty prefix on mismatch.
- Add tests for stale export removal and stale import alias removal.
Replace in-place when the key already exists to avoid moving it to the
end of the JSON object. Remove stale "#" import alias from
sample-todo-service (executable — no exports, so hasRootIndex is false).
@danfma danfma merged commit 5adc18a into main Apr 13, 2026
2 checks passed
@danfma danfma deleted the fix/src-root-prefix branch April 13, 2026 23:38
danfma added a commit that referenced this pull request Apr 15, 2026
…56)

RecordClassTransformer now synthesizes a record's equals / hashCode /
with members via the IR-driven IrToTsRecordSynthesisBridge instead of
the Roslyn-symbol-driven RecordSynthesizer. The call site extracts an
IrClassDeclaration on the fly (shape only — no body) and passes the
already-built TsConstructorParam list, so the emitted TS stays
byte-identical to what RecordSynthesizer produced (the bridge has a
parity test pinning that invariant).

Walks through the same gate RecordSynthesizer applied:
`type.IsRecord && !HasPlainObject(type)` — [PlainObject] records
continue to emit as bare object literals without synthesized members.

Also reverts UseIrBodiesWhenCovered back to false by default.
Regenerating SampleOperatorOverloading against the flipped flag
surfaced a gap that wasn't visible in the earlier three-sample
sweep: decimal arithmetic operators (+, -, *, /) flow through the
IR as plain IrBinaryExpressions and lower to raw TS `/` and `*`,
bypassing the decimal.js Decimal.times/.div/.plus/.minus helpers
that the legacy ExpressionTransformer dispatches via the BCL
registry. The flip is blocked on that gap; task #51 tracks it. The
record-synth swap itself is independent of the flag and produces
the canonical legacy output in both modes.

522/522 .NET tests; all bun suites still green; every regenerated
sample (TS + Dart) shows zero diff with the flag off.

Part of #56
danfma added a commit that referenced this pull request Apr 15, 2026
)

Closes task #51 — the first of three gaps that block
UseIrBodiesWhenCovered from going default-on.

C#'s `decimal` surfaces as the decimal.js Decimal class at runtime,
which needs method-call dispatch (`a.plus(b)`, `.times`, `.div`,
`.mod`, `.eq`, `.lt`, …) instead of the raw TS `+` / `*` / `/`
operators. The legacy OperatorHandler detects both operands as
System_Decimal via the SemanticModel and rewrites the call accordingly
(TryMapDecimalBinary). The IR path used to emit raw IrBinary-
Expressions and the TS bridge had no way to recover the type info to
rewrite at lowering time.

IrExpressionExtractor.ExtractBinary now runs the same detection:
when both sides are Decimal and the operator maps to a decimal.js
method, it normalizes the expression to an IrCallExpression
(`IrMemberAccess(left, method)`, [right]). Inequality (`!=`) wraps
the call in `IrUnaryExpression(LogicalNot, …)` to preserve the
`!eq` convention. The rest of the binary-op path stays untouched
so non-Decimal operands continue to use IrBinaryExpression — no
behavior change for any other type.

Regenerating SampleOperatorOverloading under
UseIrBodiesWhenCovered = true confirms the fix: `this.cents / 100m`
now lowers through `this.cents.div(100)` instead of raw `/`, and
`amount * 100m` through `amount.times(100)`.

Three decimal-adjacent gaps remain and block the flag flip:
1. Decimal literals (`100m`) don't wrap as `new Decimal("100")`.
2. `decimal → BigInt` conversions skip the
   `.round().toFixed(0)` chain legacy emits.
3. `Math.Round(decimal)` doesn't dispatch to `.round()`.

Each is a separate extractor-side rewrite captured as task #52.
Flag goes back to false at the end of this commit so production
samples keep their canonical output.

522/522 .NET tests; all bun suites still green; every sample
(regenerated with the flag off) shows zero diff.

Part of #56
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.

Subdirectory-aware package.json imports/exports (--src-root)

2 participants