feat(typescript): add opt-in packageJsonMergeStrategy for deep-merging packageJson overrides - #17609
Conversation
…nditions keep generated ones Co-Authored-By: bot_apk <apk@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
AI Review Summary
Solid fix: the old customizer short-circuited lodash's recursion, so nested exports entries were replaced wholesale. The new explicit deepMerge with source-first key ordering is correct and well-tested. One nit: wrapping deepMerge in mergeWith is now redundant and leaves a subtle undefined-return fallback path in lodash.
- 🟡 1 warning(s)
- 🔵 1 suggestion(s)
To request another review, comment /ai-review on this pull request.
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
…ocument merge semantics Co-Authored-By: bot_apk <apk@cognition.ai>
…erge as default Co-Authored-By: bot_apk <apk@cognition.ai>
Description
Linear ticket: Refs (TrueFoundry request, no ticket)
Adds an opt-in
packageJsonMergeStrategy: shallow | deepoption to the TypeScript SDK generator. Defaultshallowis the exact released behavior (byte-for-byte);deeprecursively mergespackageJsonoverrides so a user can add a single customexportscondition without redeclaring the generatedimport/require/defaultones.Motivation: with the released (shallow) merge, a
mergeWithcustomizer returning{...obj, ...src}stops recursion, so overridingexports["."]with only{"trueforge-dev": ...}replaces the whole entry and the package becomes unresolvable (ERR_PACKAGE_PATH_NOT_EXPORTED). Making deep merge the default would resurrect generated keys for anyone relying on wholesale replacement, so it is gated behind the flag.deepsemantics: user keys win at every level; user keys are emitted first in user order (a custom condition precedes generated ones; a full entry written asrequire, import, defaultkeeps that order); unmentioned generated keys are inherited; arrays are unioned user-first (same as shallow). Removing a generated key is not expressible under either strategy.Changes Made
TypescriptCustomConfigSchema:packageJsonMergeStrategy: z.optional(z.enum(["shallow", "deep"]));SdkGeneratorClidefaults to"shallow".SdkCustomConfig→SdkGenerator→TypescriptProject.Init.extraConfigsMergeStrategy→mergeExtraConfigs(packageJson, extraConfigs, strategy = "shallow"). PHP's separatemergeExtraConfigsis untouched.mergeExtraConfigs:shallowMergeis the original customizer verbatim;deepMergeis the recursive source-order-first merge (usesObject.hasOwnfor prototype-name keys).changes/unreleased/feat-package-json-merge-strategy.yml(typefeat); documentedpackageJson/packageJsonMergeStrategyingenerators/typescript/README.md. Public docs PR: fern-api/docs (see linked PR).seed/ts-sdk/simple-api/custom-package-json/package.jsonreverted tomain— regenerating under the default produces zero diff.Testing
mergeExtraConfigs.test.ts: shared cases run under both strategies (incl.useLegacyExports/no generated exports passthrough); shallow suite locks in released behavior, including the explicit "condition-only override of.drops generated conditions" case; deep suite covers add/override/order/subset/non-removal/new subpath/arrays/prototype keys.pnpm seed test --generator ts-sdk --fixture simple-api --outputFolder custom-package-json --localregenerates the snapshot byte-identical tomain; earlier revision validated end-to-end with TrueFoundry's real config (require.resolveunder plain,-C trueforge-dev, and ESM).Link to Devin session: https://app.devin.ai/sessions/df07e774e6de4b6e9f4a78074bffa093
Open in Devin Desktop: https://app.devin.ai/desktop/session/df07e774e6de4b6e9f4a78074bffa093?variant=devin