88 */
99
1010import { ResourceDescriptor } from '../models/types.js' ;
11- import { ResourceType } from '../models/resource-types.js' ;
11+ import { ResourceType , RESOURCE_TYPE_METADATA } from '../models/resource-types.js' ;
1212import { OverrideConfig , OverrideSection , OverrideEntry } from '../models/config.js' ;
1313import { logger } from '../lib/logger.js' ;
14- import { getNameFromNameParts } from '../lib/resource-path.js' ;
14+ import { getNameFromNameParts , isSingletonType } from '../lib/resource-path.js' ;
1515
1616/**
1717 * Map resource types to their top-level override config section key.
@@ -98,13 +98,15 @@ export function applyOverrides(
9898
9999/**
100100 * Apply override from a direct section match.
101+ * For singleton resources (e.g., ServicePolicy with nameParts: []),
102+ * uses the fixed singleton name from the ARM path (e.g., "policy").
101103 */
102104function applyFromSection (
103105 descriptor : ResourceDescriptor ,
104106 json : Record < string , unknown > ,
105107 section : OverrideSection
106108) : Record < string , unknown > {
107- const resourceName = getNameFromNameParts ( descriptor . nameParts ) ;
109+ const resourceName = getSingletonOrNamePartName ( descriptor ) ;
108110 const entry = findEntryByName ( section , resourceName ) ;
109111
110112 if ( ! entry ) {
@@ -116,7 +118,25 @@ function applyFromSection(
116118 }
117119
118120 // ARM resources have all overridable fields inside 'properties'
119- const wrappedOverride = { properties : entry . properties } ;
121+ let overrideProperties = entry . properties ;
122+
123+ // Strip apiRevision and isCurrent from API overrides — matches Toolkit behavior.
124+ // These fields should not be overridden per environment.
125+ if ( descriptor . type === ResourceType . Api ) {
126+ const { apiRevision, isCurrent, ...rest } = overrideProperties ;
127+ if ( apiRevision !== undefined || isCurrent !== undefined ) {
128+ logger . warn (
129+ `Ignoring 'apiRevision' and/or 'isCurrent' in API override for '${ resourceName } '; ` +
130+ `these fields cannot be overridden (matching Toolkit behavior).`
131+ ) ;
132+ }
133+ overrideProperties = rest ;
134+ if ( Object . keys ( overrideProperties ) . length === 0 ) {
135+ return { ...json } ;
136+ }
137+ }
138+
139+ const wrappedOverride = { properties : overrideProperties } ;
120140 const result = deepMerge ( json , wrappedOverride ) ;
121141
122142 logger . debug (
@@ -129,7 +149,10 @@ function applyFromSection(
129149
130150/**
131151 * Apply nested child override (e.g., ApiDiagnostic under apis.children.diagnostics).
132- * nameParts layout: [parentName, childName, ...]
152+ *
153+ * nameParts layout varies by resource type:
154+ * - Named children (ApiDiagnostic, ApiOperation, ApiRelease): [parentName, childName]
155+ * - Singleton children (ApiPolicy, ProductPolicy): [parentName] (child name is always "policy")
133156 */
134157function applyNestedOverride (
135158 descriptor : ResourceDescriptor ,
@@ -149,8 +172,11 @@ function applyNestedOverride(
149172 const childSection = parentEntry . children [ mapping . childKey ] ;
150173 if ( ! childSection ) return { ...json } ;
151174
152- // Child name is the second name part (e.g., the diagnostic name, operation name)
153- const childName = descriptor . nameParts [ 1 ] ;
175+ // For singleton children (e.g., ApiPolicy), the name is fixed (e.g., "policy"),
176+ // NOT in nameParts. For named children, it's nameParts[1].
177+ const childName = isSingletonType ( descriptor . type )
178+ ? getSingletonName ( descriptor . type )
179+ : descriptor . nameParts [ 1 ] ;
154180 if ( ! childName ) return { ...json } ;
155181
156182 const childEntry = findEntryByName ( childSection , childName ) ;
@@ -171,8 +197,10 @@ function applyNestedOverride(
171197
172198/**
173199 * Apply grandchild (3-level) override.
174- * E.g., ApiOperationPolicy: apis[apiName].children.operations[opName].children.policies[policyName]
175- * nameParts layout: [parentName, childName, grandchildName]
200+ * E.g., ApiOperationPolicy: apis[apiName].children.operations[opName].children.policies[policy]
201+ *
202+ * nameParts layout for ApiOperationPolicy: [apiName, operationName]
203+ * The grandchild name is always the fixed singleton name (e.g., "policy").
176204 */
177205function applyGrandchildOverride (
178206 descriptor : ResourceDescriptor ,
@@ -201,7 +229,8 @@ function applyGrandchildOverride(
201229 const grandchildSection = childEntry . children [ mapping . grandchildKey ] ;
202230 if ( ! grandchildSection ) return { ...json } ;
203231
204- const grandchildName = descriptor . nameParts [ 2 ] ;
232+ // Grandchild is always a singleton (e.g., "policy") — name is NOT in nameParts
233+ const grandchildName = getSingletonName ( descriptor . type ) ;
205234 if ( ! grandchildName ) return { ...json } ;
206235
207236 const grandchildEntry = findEntryByName ( grandchildSection , grandchildName ) ;
@@ -220,6 +249,37 @@ function applyGrandchildOverride(
220249 return result ;
221250}
222251
252+ /**
253+ * Get the resource name for override lookup, handling singletons correctly.
254+ * - Top-level singletons (ServicePolicy with nameParts: []) use the fixed singleton name
255+ * - Named resources use the last element of nameParts
256+ */
257+ function getSingletonOrNamePartName ( descriptor : ResourceDescriptor ) : string {
258+ if ( descriptor . nameParts . length === 0 ) {
259+ // Top-level singleton (e.g., ServicePolicy)
260+ const name = getSingletonName ( descriptor . type ) ;
261+ if ( ! name ) {
262+ throw new RangeError (
263+ `getSingletonOrNamePartName: ${ descriptor . type } has empty nameParts ` +
264+ `but no known singleton name`
265+ ) ;
266+ }
267+ return name ;
268+ }
269+ return getNameFromNameParts ( descriptor . nameParts ) ;
270+ }
271+
272+ /**
273+ * Get the fixed singleton name for a resource type from its ARM path.
274+ * E.g., ServicePolicy → "policy", ApiPolicy → "policy", ApiWiki → "default"
275+ * Returns undefined if the resource type is not a singleton.
276+ */
277+ function getSingletonName ( type : ResourceType ) : string | undefined {
278+ if ( ! isSingletonType ( type ) ) return undefined ;
279+ const meta = RESOURCE_TYPE_METADATA [ type ] ;
280+ return meta . armPathSuffix . split ( '/' ) . pop ( ) ;
281+ }
282+
223283/**
224284 * Find an override entry by name using case-insensitive matching.
225285 */
0 commit comments