Skip to content

feat(typespec-client-generator-core): support per-service api-version map for multi-service packages#4650

Merged
iscai-msft merged 4 commits into
mainfrom
tadelesh/tcgc-api-version-multi-service
Jun 17, 2026
Merged

feat(typespec-client-generator-core): support per-service api-version map for multi-service packages#4650
iscai-msft merged 4 commits into
mainfrom
tadelesh/tcgc-api-version-multi-service

Conversation

@tadelesh

Copy link
Copy Markdown
Member

Fixes #4009

Summary

Extends the TCGC api-version emitter option to accept either a string or a Record<string, string> (a map from each service namespace's full name to its desired version), enabling per-service version selection for multi-service packages.

Behavior

  • string (e.g. "2024-10-01"): applies to single-service packages only. Ignored for multi-service.
  • "latest": global keyword, applies regardless of service count.
  • "all": single-service only. Multi-service packages do not support all (in either string or map form) — it is ignored and each service falls back to its latest version.
  • Record<string, string> (e.g. { "ServiceA": "av1", "ServiceB": "bv2" }): maps each service namespace full name to a version. Services not listed default to their latest version.

Implementation notes

  • Added resolveApiVersionForService as the single source of truth for resolving the version that applies to a given service.
  • getServiceNamespaces is memoized on the context (__serviceNamespaces) since it is consulted on the per-type hot path via getAvailableApiVersions.
  • handleVersioningMutationForGlobalNamespace now mutates each service independently using its resolved version.

Validation

  • Added multi-service map tests (per-service versions, fallback to latest, all ignored).
  • Full TCGC suite: 1330 passed / 2 skipped. TypeScript build and ESLint clean.

@microsoft-github-policy-service microsoft-github-policy-service Bot added lib:tcgc Issues for @azure-tools/typespec-client-generator-core library meta:website TypeSpec.io updates labels Jun 17, 2026
@azure-sdk-automation

azure-sdk-automation Bot commented Jun 17, 2026

Copy link
Copy Markdown

All changed packages have been documented.

  • @azure-tools/typespec-client-generator-core
Show changes

@azure-tools/typespec-client-generator-core - feature ✏️

Support a per-service api-version map for multi-service packages. The api-version emitter option now accepts either a string (applied to single service packages, or the latest/all keywords) or a map from each service namespace's full name to its desired version. Services not listed in the map default to their latest version.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

⚡ Benchmark Results

⚠️ 18 metric(s) regressed above the +5% threshold:

Metric Baseline Current Change
total 🔴 579.5ms 🔴 713.4ms +23.1% 🔴
loader 🟢 158.9ms 🟡 251.8ms +58.5% 🔴
checker 🟢 185.6ms 🟡 213.5ms +15.0% 🔴
validation 🟢 41.6ms 🟢 45.3ms +8.9% 🔴
 ↳ validation/@typespec/http 🟢 5.1ms 🟢 6.3ms +24.0% 🔴
linter 🟢 131.4ms 🟢 149.2ms +13.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/byos 🟢 5.5ms 🟢 7.1ms +28.8% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-header-explode 🟡 18.3ms 🔴 21.0ms +14.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-query-explode 🟡 19.5ms 🔴 22.0ms +12.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-response-body 🔴 21.8ms 🔴 25.6ms +17.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem 🔴 22.4ms 🔴 25.1ms +12.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header 🟡 11.8ms 🟡 15.0ms +26.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body 🟡 19.8ms 🔴 22.7ms +14.6% 🔴
emit 🔴 5.81s 🔴 6.26s +7.8% 🔴
 ↳ emit/@azure-tools/typespec-autorest 🟢 161.8ms 🟢 179.1ms +10.7% 🔴
 ↳ emit/@azure-tools/typespec-python 🔴 4.20s 🔴 4.60s +9.4% 🔴
 ↳ emit/@typespec/http-client-js 🔴 1.20s 🔴 1.35s +12.7% 🔴
 ↳ emit/@typespec/openapi3/compute 🟢 129.0ms 🟢 142.5ms +10.5% 🔴
Full details – comparing 57f91f2 vs baseline 681fbcb
Metric Baseline Current Change
total 🔴 579.5ms 🔴 713.4ms +23.1% 🔴
loader 🟢 158.9ms 🟡 251.8ms +58.5% 🔴
resolver 🟢 20.8ms 🟢 21.4ms +2.5%
checker 🟢 185.6ms 🟡 213.5ms +15.0% 🔴
validation 🟢 41.6ms 🟢 45.3ms +8.9% 🔴
 ↳ validation/@azure-tools/typespec-azure-core 🟢 6.6ms 🟢 7.3ms +10.4%
 ↳ validation/@typespec/http 🟢 5.1ms 🟢 6.3ms +24.0% 🔴
 ↳ validation/@typespec/rest 🟢 0.6ms 🟢 0.7ms +16.5%
 ↳ validation/@typespec/versioning 🔴 27.4ms 🔴 28.6ms +4.5%
 ↳ validation/compiler 🟢 1.6ms 🟢 1.6ms +1.2%
linter 🟢 131.4ms 🟢 149.2ms +13.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/auth-required 🟢 0.0ms 🟢 0.0ms -0.6%
 ↳ linter/@azure-tools/typespec-azure-core/bad-record-type 🟢 0.2ms 🟢 0.2ms +1.3%
 ↳ linter/@azure-tools/typespec-azure-core/byos 🟢 5.5ms 🟢 7.1ms +28.8% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/casing-style 🟢 0.6ms 🟢 0.7ms +9.6%
 ↳ linter/@azure-tools/typespec-azure-core/composition-over-inheritance 🟢 0.1ms 🟢 0.1ms +8.7%
 ↳ linter/@azure-tools/typespec-azure-core/documentation-required 🟢 0.9ms 🟢 0.9ms +5.2%
 ↳ linter/@azure-tools/typespec-azure-core/friendly-name 🟢 0.6ms 🟢 0.6ms +6.0%
 ↳ linter/@azure-tools/typespec-azure-core/key-visibility-required 🟢 0.2ms 🟢 0.3ms +102.7%
 ↳ linter/@azure-tools/typespec-azure-core/known-encoding 🟢 0.3ms 🟢 0.3ms +20.9%
 ↳ linter/@azure-tools/typespec-azure-core/long-running-polling-operation-required 🟢 0.3ms 🟢 0.3ms +16.0%
 ↳ linter/@azure-tools/typespec-azure-core/no-case-mismatch 🟢 0.2ms 🟢 0.3ms +13.3%
 ↳ linter/@azure-tools/typespec-azure-core/no-closed-literal-union 🟢 0.3ms 🟢 0.3ms +6.3%
 ↳ linter/@azure-tools/typespec-azure-core/no-enum 🟢 0.0ms 🟢 0.0ms +8.5%
 ↳ linter/@azure-tools/typespec-azure-core/no-error-status-codes 🟢 0.1ms 🟢 0.1ms +10.4%
 ↳ linter/@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops 🟢 0.1ms 🟢 0.1ms +10.5%
 ↳ linter/@azure-tools/typespec-azure-core/no-format 🟢 0.6ms 🟢 0.6ms +6.9%
 ↳ linter/@azure-tools/typespec-azure-core/no-generic-numeric 🟢 0.4ms 🟢 0.5ms +8.6%
 ↳ linter/@azure-tools/typespec-azure-core/no-header-explode 🟡 18.3ms 🔴 21.0ms +14.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-legacy-usage 🟢 1.1ms 🟢 1.2ms +8.6%
 ↳ linter/@azure-tools/typespec-azure-core/no-multiple-discriminator 🟢 0.1ms 🟢 0.1ms +14.2%
 ↳ linter/@azure-tools/typespec-azure-core/no-nullable 🟢 0.2ms 🟢 0.3ms +15.9%
 ↳ linter/@azure-tools/typespec-azure-core/no-offsetdatetime 🟢 1.3ms 🟢 1.2ms -5.4%
 ↳ linter/@azure-tools/typespec-azure-core/no-openapi 🟢 2.1ms 🟢 2.4ms +16.7%
 ↳ linter/@azure-tools/typespec-azure-core/no-private-usage 🟢 2.0ms 🟢 2.1ms +5.4%
 ↳ linter/@azure-tools/typespec-azure-core/no-query-explode 🟡 19.5ms 🔴 22.0ms +12.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-response-body 🔴 21.8ms 🔴 25.6ms +17.4% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/no-rest-library-interfaces 🟢 0.0ms 🟢 0.0ms -1.4%
 ↳ linter/@azure-tools/typespec-azure-core/no-route-parameter-name-mismatch 🟢 5.1ms 🟢 5.8ms +12.1%
 ↳ linter/@azure-tools/typespec-azure-core/no-rpc-path-params 🟢 0.2ms 🟢 0.2ms +21.0%
 ↳ linter/@azure-tools/typespec-azure-core/no-string-discriminator 🟢 0.0ms 🟢 0.0ms +10.6%
 ↳ linter/@azure-tools/typespec-azure-core/no-unknown 🟢 0.2ms 🟢 0.2ms +14.1%
 ↳ linter/@azure-tools/typespec-azure-core/no-unnamed-union 🟢 0.4ms 🟢 0.4ms +8.2%
 ↳ linter/@azure-tools/typespec-azure-core/operation-missing-api-version 🟢 0.2ms 🟢 0.2ms +3.6%
 ↳ linter/@azure-tools/typespec-azure-core/request-body-problem 🟢 0.3ms 🟢 0.3ms +4.6%
 ↳ linter/@azure-tools/typespec-azure-core/require-versioned 🟢 0.0ms 🟢 0.0ms +7.2%
 ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem 🔴 22.4ms 🔴 25.1ms +12.1% 🔴
 ↳ linter/@azure-tools/typespec-azure-core/rpc-operation-request-body 🟢 0.3ms 🟢 0.4ms +19.4%
 ↳ linter/@azure-tools/typespec-azure-core/spread-discriminated-model 🟢 0.3ms 🟢 0.3ms +6.6%
 ↳ linter/@azure-tools/typespec-azure-core/use-standard-names 🟢 5.3ms 🟢 5.9ms +11.2%
 ↳ linter/@azure-tools/typespec-azure-core/use-standard-operations 🟢 0.1ms 🟢 0.1ms +9.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-child-resources 🟢 4.1ms 🟢 4.9ms +19.8%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-lifecycle-operations 🟢 0.0ms 🟢 0.0ms -3.8%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-common-types-version 🟢 3.7ms 🟢 4.3ms +17.0%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key 🟢 0.1ms 🟢 0.1ms +25.0%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage 🟢 0.1ms 🟢 0.1ms +10.2%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes 🟢 1.1ms 🟢 1.3ms +16.7%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-path-casing-conflicts 🟢 3.9ms 🟢 4.7ms +20.0%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-record 🟢 0.3ms 🟢 0.4ms +14.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes 🟢 0.4ms 🟢 0.5ms +16.3%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes 🟢 0.0ms 🟢 0.0ms +3.0%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-action-no-segment 🟢 0.2ms 🟢 0.2ms +17.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property 🟢 0.1ms 🟢 0.1ms +22.3%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-interface-requires-decorator 🟢 0.0ms 🟢 0.0ms +21.2%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-action-verb 🟢 0.1ms 🟢 0.1ms +24.9%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property 🟢 0.1ms 🟢 0.1ms +16.8%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format 🟢 0.0ms 🟢 0.0ms +15.3%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-key-invalid-chars 🟢 0.2ms 🟢 0.3ms +20.1%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern 🟢 0.0ms 🟢 0.0ms +15.9%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation 🟢 0.2ms 🟢 0.2ms +13.4%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation-response 🟢 4.1ms 🟢 4.8ms +17.0%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-patch 🟢 0.3ms 🟢 0.3ms +30.1%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars 🟢 0.2ms 🟢 0.2ms +11.9%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state 🟢 0.1ms 🟢 0.1ms +15.4%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels 🟢 0.1ms 🟢 0.1ms +30.5%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/empty-updateable-properties 🟢 0.1ms 🟢 0.2ms +19.1%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/improper-subscription-list-operation 🟢 0.0ms 🟢 0.0ms +5.1%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header 🟡 11.8ms 🟡 15.0ms +26.5% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint 🟢 0.0ms 🟢 0.0ms +13.2%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers 🟢 0.3ms 🟢 0.3ms +11.4%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-empty-model 🟢 0.1ms 🟢 0.2ms +26.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-override-props 🟢 0.1ms 🟢 0.1ms +12.3%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation 🟢 0.2ms 🟢 0.2ms +23.5%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body 🟡 19.8ms 🔴 22.7ms +14.6% 🔴
 ↳ linter/@azure-tools/typespec-azure-resource-manager/patch-envelope 🟢 0.1ms 🟢 0.2ms +17.7%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/resource-name 🟢 0.1ms 🟢 0.2ms +17.1%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/secret-prop 🟢 2.3ms 🟢 2.6ms +15.7%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/unsupported-type 🟢 0.4ms 🟢 0.4ms +19.6%
 ↳ linter/@azure-tools/typespec-azure-resource-manager/version-progression 🟢 0.0ms 🟢 0.0ms -1.0%
 ↳ linter/@azure-tools/typespec-client-generator-core/property-name-conflict 🟢 1.1ms 🟢 1.2ms +15.0%
 ↳ linter/@azure-tools/typespec-client-generator-core/require-client-suffix 🟢 0.2ms 🟢 0.2ms -3.9%
emit 🔴 5.81s 🔴 6.26s +7.8% 🔴
 ↳ emit/@azure-tools/typespec-autorest 🟢 161.8ms 🟢 179.1ms +10.7% 🔴
 ↳ emit/@azure-tools/typespec-python 🔴 4.20s 🔴 4.60s +9.4% 🔴
 ↳ emit/@typespec/http-client-js 🔴 1.20s 🔴 1.35s +12.7% 🔴
 ↳ emit/@typespec/openapi3 🟢 152.5ms 🟢 159.8ms +4.8%
 ↳ emit/@typespec/openapi3/compute 🟢 129.0ms 🟢 142.5ms +10.5% 🔴
 ↳ emit/@typespec/openapi3/write 🟢 21.1ms 🟢 17.2ms -18.6% 🟢

Averaged across 3 specs (azure-arm-resource-manager, azure-core-dataplane, azure-full).
Threshold: changes > ±5% are highlighted.
🟢 Fast · 🟡 Moderate (stages >200ms, rules >10ms) · 🔴 Slow (stages >400ms, rules >20ms)

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-client-generator-core@4650

commit: ba52aed

@weidongxu-microsoft weidongxu-microsoft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

agent:

 1. Docs are now inconsistent with the feature and behavior.
 packages/typespec-client-generator-core/README.md*#COLON|*63-67 and website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/emitter.md*#COLON|*57-61 still say api-version is Type: string, but this PR changes the option schema to also accept a per-service map (packages/typespec-client-generator-core/src/lib.ts*#COLON|*12-27, src/interfaces.ts*#COLON|*44). The docs also say all is accepted without clarifying that multi-service packages intentionally do not support all and fall back to latest. As written, users will configure the option incorrectly.

@azure-sdk-automation

azure-sdk-automation Bot commented Jun 17, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website

Comment thread packages/typespec-client-generator-core/src/internal-utils.ts
Comment thread packages/typespec-client-generator-core/src/internal-utils.ts
Comment thread packages/typespec-client-generator-core/src/internal-utils.ts

@weidongxu-microsoft weidongxu-microsoft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

another one from agent that is too complex for me to understand :-)

therefore just throw it out for expert

 1. Per-service version resolution ignores the wrapper, so shared/common types can get the wrong apiVersions.
 getAvailableApiVersions still accepts a wrapper specifically so version filtering can be contextual (src/internal-utils.ts:409-426), but filterApiVersionsWithDecorators now resolves the selected service only from type (src/internal-utils.ts:362-370). That works when the type lives under a service namespace, but it breaks for shared models/enums/unions that are outside the service namespace tree. In those cases getServiceNamespaceForType(context, type) returns undefined, the per-service map is ignored, and the type can keep a broader apiVersions set than the selected service version should allow. You can already see affected call sites that pass type.namespace instead of an operation/service wrapper, e.g. model and enum creation in src/types.ts:876 and src/types.ts:1059.
 Suggested fix: carry the wrapper through into filterApiVersionsWithDecorators, and resolve the service from the wrapper first, then fall back to the type.

@tadelesh tadelesh force-pushed the tadelesh/tcgc-api-version-multi-service branch from 1b7f254 to 93e6bd2 Compare June 17, 2026 08:15
… map for multi-service packages

The `api-version` emitter option now accepts either a string or a map from each service namespace's full name to its desired version. Services not listed in the map default to their latest version. Multi-service packages do not support the `all` value (in either form); it is ignored and treated as latest.

Resolves #4009

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tadelesh tadelesh force-pushed the tadelesh/tcgc-api-version-multi-service branch from 93e6bd2 to 180d7aa Compare June 17, 2026 08:49
@iscai-msft iscai-msft enabled auto-merge June 17, 2026 14:42
@iscai-msft iscai-msft added this pull request to the merge queue Jun 17, 2026
Merged via the queue into main with commit 40aa6fc Jun 17, 2026
28 checks passed
@iscai-msft iscai-msft deleted the tadelesh/tcgc-api-version-multi-service branch June 17, 2026 15:23
tadelesh added a commit that referenced this pull request Jun 22, 2026
Hotfix patch release for `@azure-tools/typespec-client-generator-core` →
**0.69.1**.

Cherry-picks #4650 (per-service `api-version` map for multi-service
packages) onto `release/june-2026` and bumps the patch version.

## Changes
- Cherry-pick of the fix commit from #4650 (changeKind set to `fix` for
a patch bump)
- Version bump: tcgc `0.69.0` → `0.69.1`

## Notes
- Targets `release/june-2026` (not `main`)
- Only `@azure-tools/typespec-client-generator-core` is affected
- `core` submodule pointer unchanged

---------

Co-authored-by: tadelesh <chenjieshi@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: iscai-msft <isabellavcai@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib:tcgc Issues for @azure-tools/typespec-client-generator-core library meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to set the 'api-version' option for multi-service package in tspconfig.yaml

3 participants