fix: add --src-root for subdirectory-aware package.json paths (#50)#51
Merged
fix: add --src-root for subdirectory-aware package.json paths (#50)#51
Conversation
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
There was a problem hiding this comment.
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-rootCLI option /MetanoSrcRootMSBuild property and plumb it intoPackageJsonWriter. - 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/exportsupdates 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.
) - 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
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
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.
Summary
--src-rootCLI parameter (MetanoSrcRootMSBuild property) to specify the TypeScript source root relative to the package rootsrc/domain/), the output prefix (domain) is applied to dist paths and export subpathsExample
With
MetanoOutputDir = src/domain/:Before (wrong):
After (correct):
Test plan
Closes #50