@@ -20,6 +20,15 @@ const ASSOCIATION_TYPES = new Set<ResourceType>([
2020 ResourceType . GatewayApi ,
2121] ) ;
2222
23+ const SUPPORTED_SPECIFICATION_EXTENSIONS = new Set ( [
24+ 'yaml' ,
25+ 'yml' ,
26+ 'json' ,
27+ 'graphql' ,
28+ 'wsdl' ,
29+ 'wadl' ,
30+ ] ) ;
31+
2332/**
2433 * Fills all positional `{i}` tokens in a template string with `nameParts[i]`.
2534 * Throws if a placeholder index has no corresponding entry in `nameParts`.
@@ -385,6 +394,79 @@ export function parseArtifactPath(
385394 return undefined ;
386395}
387396
397+ /**
398+ * Parse a changed artifact file path into a ResourceDescriptor.
399+ *
400+ * This extends `parseArtifactPath` with support for supplemental artifact
401+ * files that belong to a resource but are not the primary info file.
402+ *
403+ * Currently supports:
404+ * - API specification files (`apis/{api}/specification.{ext}`)
405+ * - Workspace-scoped API specification files
406+ * (`workspaces/{workspace}/apis/{api}/specification.{ext}`)
407+ */
408+ export function parseArtifactChangePath (
409+ baseDir : string ,
410+ filePath : string
411+ ) : ResourceDescriptor | undefined {
412+ const directDescriptor = parseArtifactPath ( baseDir , filePath ) ;
413+ if ( directDescriptor ) {
414+ return directDescriptor ;
415+ }
416+
417+ const relativePath = toTemplatePath ( path . relative ( baseDir , filePath ) ) ;
418+
419+ const rootApiSpec = parseTemplatePath ( 'apis/{0}/specification.{1}' , relativePath ) ;
420+ if ( rootApiSpec && rootApiSpec . length === 2 ) {
421+ const apiName = getCapture ( rootApiSpec , 0 ) ;
422+ const extension = getCapture ( rootApiSpec , 1 ) ;
423+ if ( apiName && isSupportedSpecificationExtension ( extension ) ) {
424+ return {
425+ type : ResourceType . Api ,
426+ nameParts : [ apiName ] ,
427+ } ;
428+ }
429+ }
430+
431+ const workspaceApiSpec = parseTemplatePath (
432+ 'workspaces/{0}/apis/{1}/specification.{2}' ,
433+ relativePath
434+ ) ;
435+ if ( workspaceApiSpec && workspaceApiSpec . length === 3 ) {
436+ const workspace = getCapture ( workspaceApiSpec , 0 ) ;
437+ const apiName = getCapture ( workspaceApiSpec , 1 ) ;
438+ const extension = getCapture ( workspaceApiSpec , 2 ) ;
439+ if ( workspace && apiName && isSupportedSpecificationExtension ( extension ) ) {
440+ return {
441+ type : ResourceType . Api ,
442+ nameParts : [ apiName ] ,
443+ workspace,
444+ } ;
445+ }
446+ }
447+
448+ return undefined ;
449+ }
450+
451+ function toTemplatePath ( relativePath : string ) : string {
452+ return relativePath . split ( path . sep ) . join ( '/' ) ;
453+ }
454+
455+ function getCapture ( captures : string [ ] , index : number ) : string | undefined {
456+ if ( captures . length <= index ) {
457+ return undefined ;
458+ }
459+ return captures [ index ] ;
460+ }
461+
462+ function isSupportedSpecificationExtension ( extension : string | undefined ) : boolean {
463+ if ( ! extension ) {
464+ return false ;
465+ }
466+
467+ return SUPPORTED_SPECIFICATION_EXTENSIONS . has ( extension . toLowerCase ( ) ) ;
468+ }
469+
388470/**
389471 * Check if a resource type is a singleton (no list, only get).
390472 * Singletons have armPathSuffix ending with a fixed segment (no `{n}` placeholder).
0 commit comments