@red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6ddfd98: Use the valid catalog kind spelling `AiResource` (not `AIResource`) in the
8
+ extensions processor, and rename public exports accordingly:
9
+ `AiResourceExtensionsProcessor` and `AiResourceScope`.
10
+
3
11
  ## 0.4.0
4
12
 
5
13
  ### Minor Changes
@@ -12,14 +20,14 @@
12
20
 
13
21
  ### Minor Changes
14
22
 
15
- - 55088f9: Aggregate multiple AIResource extension errors into a single response.
23
+ - 55088f9: Aggregate multiple AiResource extension errors into a single response.
16
24
  Extract OCI validation into a shared `collectOciErrors` function called by
17
- `AIResourceExtensionsProcessor` so that scope and OCI constraint violations
25
+ `AiResourceExtensionsProcessor` so that scope and OCI constraint violations
18
26
  are reported together instead of stopping at the first failure.
19
- - 8317e6e: Consolidate OCI location validation into `AIResourceExtensionsProcessor`.
20
- The standalone `AIResourceOciProcessor` class and its public export have been
27
+ - 8317e6e: Consolidate OCI location validation into `AiResourceExtensionsProcessor`.
28
+ The standalone `AiResourceOciProcessor` class and its public export have been
21
29
  removed; OCI format checks now run via the internal `collectOciErrors` helper
22
- called by `AIResourceExtensionsProcessor`.
30
+ called by `AiResourceExtensionsProcessor`.
23
31
  - 93847ae: Migrate OCI asset location validation from `spec.location` to the standard
24
32
  `backstage.io/source-location` annotation using the Backstage location-ref
25
33
  form `url:oci://...`. The annotation is parsed with upstream `parseLocationRef`
@@ -28,12 +36,12 @@
28
36
 
29
37
  ### Patch Changes
30
38
 
31
- - e168046: Add AIResource example YAML files and extend README with schema field
39
+ - e168046: Add AiResource example YAML files and extend README with schema field
32
40
  reference, registration guidance, and OCI validation-only documentation.
33
41
 
34
42
  ## 0.2.0
35
43
 
36
44
  ### Minor Changes
37
45
 
38
- - 6af0114: Add AIResource extension validation via AIResourceExtensionsProcessor, starting with spec.scope
39
- - 67be6ce: Scaffold new AIResource catalog extension and add upstream module to workspace
46
+ - 6af0114: Add AiResource extension validation via AiResourceExtensionsProcessor, starting with spec.scope
47
+ - 67be6ce: Scaffold new AiResource catalog extension and add upstream module to workspace
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions
2
2
 
3
3
  A Backstage catalog backend module that validates RHDH-specific extension
4
- fields on AIResource entities.
4
+ fields on AiResource entities.
5
5
 
6
6
  ## Validation
7
7
 
8
8
  The module registers a single catalog processor —
9
- `AIResourceExtensionsProcessor` — for `AIResource` entities.
10
- Non-AIResource entities pass through unchanged.
9
+ `AiResourceExtensionsProcessor` — for `AiResource` entities.
10
+ Non-AiResource entities pass through unchanged.
11
11
 
12
- ### `AIResourceExtensionsProcessor`
12
+ ### `AiResourceExtensionsProcessor`
13
13
 
14
- Validates RHDH extension fields on `AIResource` entities. All constraint
14
+ Validates RHDH extension fields on `AiResource` entities. All constraint
15
15
  violations are collected and reported in a single error rather than
16
16
  stopping at the first failure.
17
17
 
@@ -43,8 +43,8 @@ format rules and pass through without validation.
43
43
 
44
44
  ## Schema Fields
45
45
 
46
- An `AIResource` entity uses `apiVersion: backstage.io/v1beta1` and
47
- `kind: AIResource`. The following fields are relevant:
46
+ An `AiResource` entity uses `apiVersion: backstage.io/v1beta1` and
47
+ `kind: AiResource`. The following fields are relevant:
48
48
 
49
49
  | Field | Enforced by this module | Description |
50
50
  | ------------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------ |
@@ -66,14 +66,14 @@ pass through without error.
66
66
  | `product` | The AI asset is scoped to a specific product |
67
67
  | `team` | The AI asset is scoped to a specific team |
68
68
 
69
- ## Registering an AIResource Entity
69
+ ## Registering an AiResource Entity
70
70
 
71
- AIResource entities are registered through the standard RHDH catalog
72
- interfaces — no AIResource-specific registration flow is required.
71
+ AiResource entities are registered through the standard RHDH catalog
72
+ interfaces — no AiResource-specific registration flow is required.
73
73
 
74
74
  ### Git-backed entities
75
75
 
76
- 1. Create a `catalog-info.yaml` with `kind: AIResource`.
76
+ 1. Create a `catalog-info.yaml` with `kind: AiResource`.
77
77
  2. Set `backstage.io/source-location` to the repository URL using the
78
78
  standard location-ref form (e.g. `url:https://github.com/my-org/my-repo`)
79
79
  so consumers can locate the source repository.
@@ -85,7 +85,7 @@ for a complete example.
85
85
 
86
86
  ### OCI-backed entities
87
87
 
88
- 1. Create a `catalog-info.yaml` with `kind: AIResource`.
88
+ 1. Create a `catalog-info.yaml` with `kind: AiResource`.
89
89
  2. Set `backstage.io/source-location` to an OCI reference using the
90
90
  location-ref form `url:oci://…`
91
91
  (e.g. `url:oci://quay.io/my-org/my-model:latest`).
@@ -101,7 +101,7 @@ for a complete example.
101
101
 
102
102
  ## OCI Validation-Only Behavior
103
103
 
104
- The `AIResourceExtensionsProcessor` validates the `backstage.io/source-location`
104
+ The `AiResourceExtensionsProcessor` validates the `backstage.io/source-location`
105
105
  annotation when the location-ref target uses the `oci://` scheme. It checks:
106
106
 
107
107
  - The annotation uses the `url:oci://…` location-ref form (not bare `oci://…`).
@@ -123,6 +123,6 @@ resolve when registry access is available.
123
123
 
124
124
  | Export | Description |
125
125
  | ------------------------------- | ------------------------------------------------------- |
126
- | `AIResourceExtensionsProcessor` | `CatalogProcessor` for RHDH AIResource extension fields |
126
+ | `AiResourceExtensionsProcessor` | `CatalogProcessor` for RHDH AiResource extension fields |
127
127
  | `VALID_AI_RESOURCE_SCOPES` | Readonly tuple of accepted scope values |
128
- | `AIResourceScope` | Type union of accepted scope values |
128
+ | `AiResourceScope` | Type union of accepted scope values |
@@ -7,12 +7,12 @@ const VALID_AI_RESOURCE_SCOPES = [
7
7
  "product",
8
8
  "team"
9
9
  ];
10
- class AIResourceExtensionsProcessor {
10
+ class AiResourceExtensionsProcessor {
11
11
  getProcessorName() {
12
- return "AIResourceExtensionsProcessor";
12
+ return "AiResourceExtensionsProcessor";
13
13
  }
14
14
  async preProcessEntity(entity, _location, _emit) {
15
- if (entity.kind !== "AIResource") {
15
+ if (entity.kind !== "AiResource") {
16
16
  return entity;
17
17
  }
18
18
  const errors = [];
@@ -27,13 +27,13 @@ class AIResourceExtensionsProcessor {
27
27
  errors.push(...collectOciErrors.collectOciErrors(entity));
28
28
  if (errors.length > 0) {
29
29
  throw new Error(
30
- `Validation failed for AIResource entity: ${errors.join("; ")}`
30
+ `Validation failed for AiResource entity: ${errors.join("; ")}`
31
31
  );
32
32
  }
33
33
  return entity;
34
34
  }
35
35
  }
36
36
 
37
- exports.AIResourceExtensionsProcessor = AIResourceExtensionsProcessor;
37
+ exports.AiResourceExtensionsProcessor = AiResourceExtensionsProcessor;
38
38
  exports.VALID_AI_RESOURCE_SCOPES = VALID_AI_RESOURCE_SCOPES;
39
- //# sourceMappingURL=AIResourceExtensionsProcessor.cjs.js.map
39
+ //# sourceMappingURL=AiResourceExtensionsProcessor.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AIResourceExtensionsProcessor.cjs.js","sources":["../src/AIResourceExtensionsProcessor.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n} from '@backstage/plugin-catalog-node';\nimport { Entity } from '@backstage/catalog-model';\nimport { LocationSpec } from '@backstage/plugin-catalog-common';\nimport { collectOciErrors } from './collectOciErrors';\n\n/**\n * Valid values for spec.scope on AIResource entities.\n *\n * @public\n */\nexport const VALID_AI_RESOURCE_SCOPES = [\n 'organization',\n 'product',\n 'team',\n] as const;\n\n/**\n * Type for valid AIResource scope values.\n *\n * @public\n */\nexport type AIResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];\n\n/**\n * A CatalogProcessor that validates RHDH-specific extension\n * fields on AIResource entities.\n *\n * Validates:\n * - `spec.scope`: optional field restricted to 'organization',\n * 'product', or 'team'\n * - `backstage.io/source-location`: OCI URI format when the\n * location-ref target uses the `oci://` scheme\n *\n * All constraint violations are collected and reported in a single\n * error rather than stopping at the first failure.\n *\n * @public\n */\nexport class AIResourceExtensionsProcessor implements CatalogProcessor {\n getProcessorName(): string {\n return 'AIResourceExtensionsProcessor';\n }\n\n async preProcessEntity(\n entity: Entity,\n _location: LocationSpec,\n _emit: CatalogProcessorEmit,\n ): Promise<Entity> {\n if (entity.kind !== 'AIResource') {\n return entity;\n }\n\n const errors: string[] = [];\n\n const scope = entity.spec?.scope;\n if (\n scope !== undefined &&\n !VALID_AI_RESOURCE_SCOPES.includes(scope as AIResourceScope)\n ) {\n const accepted = VALID_AI_RESOURCE_SCOPES.map(v => `'${v}'`).join(', ');\n const sanitized = Array.from(String(scope))\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n errors.push(\n `spec.scope has invalid value '${sanitized}'; accepted values are ${accepted}`,\n );\n }\n\n errors.push(...collectOciErrors(entity));\n\n if (errors.length > 0) {\n throw new Error(\n `Validation failed for AIResource entity: ${errors.join('; ')}`,\n );\n }\n\n return entity;\n }\n}\n"],"names":["collectOciErrors"],"mappings":";;;;AA6BO,MAAM,wBAAA,GAA2B;AAAA,EACtC,cAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;AAwBO,MAAM,6BAAA,CAA0D;AAAA,EACrE,gBAAA,GAA2B;AACzB,IAAA,OAAO,+BAAA;AAAA,EACT;AAAA,EAEA,MAAM,gBAAA,CACJ,MAAA,EACA,SAAA,EACA,KAAA,EACiB;AACjB,IAAA,IAAI,MAAA,CAAO,SAAS,YAAA,EAAc;AAChC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,SAAmB,EAAC;AAE1B,IAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,EAAM,KAAA;AAC3B,IAAA,IACE,UAAU,MAAA,IACV,CAAC,wBAAA,CAAyB,QAAA,CAAS,KAAwB,CAAA,EAC3D;AACA,MAAA,MAAM,QAAA,GAAW,yBAAyB,GAAA,CAAI,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACtE,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA,CACvC,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,CAAA,8BAAA,EAAiC,SAAS,CAAA,uBAAA,EAA0B,QAAQ,CAAA;AAAA,OAC9E;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,GAAGA,iCAAA,CAAiB,MAAM,CAAC,CAAA;AAEvC,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yCAAA,EAA4C,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OAC/D;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;;"}
1
+ {"version":3,"file":"AiResourceExtensionsProcessor.cjs.js","sources":["../src/AiResourceExtensionsProcessor.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CatalogProcessor,\n CatalogProcessorEmit,\n} from '@backstage/plugin-catalog-node';\nimport { Entity } from '@backstage/catalog-model';\nimport { LocationSpec } from '@backstage/plugin-catalog-common';\nimport { collectOciErrors } from './collectOciErrors';\n\n/**\n * Valid values for spec.scope on AiResource entities.\n *\n * @public\n */\nexport const VALID_AI_RESOURCE_SCOPES = [\n 'organization',\n 'product',\n 'team',\n] as const;\n\n/**\n * Type for valid AiResource scope values.\n *\n * @public\n */\nexport type AiResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];\n\n/**\n * A CatalogProcessor that validates RHDH-specific extension\n * fields on AiResource entities.\n *\n * Validates:\n * - `spec.scope`: optional field restricted to 'organization',\n * 'product', or 'team'\n * - `backstage.io/source-location`: OCI URI format when the\n * location-ref target uses the `oci://` scheme\n *\n * All constraint violations are collected and reported in a single\n * error rather than stopping at the first failure.\n *\n * @public\n */\nexport class AiResourceExtensionsProcessor implements CatalogProcessor {\n getProcessorName(): string {\n return 'AiResourceExtensionsProcessor';\n }\n\n async preProcessEntity(\n entity: Entity,\n _location: LocationSpec,\n _emit: CatalogProcessorEmit,\n ): Promise<Entity> {\n if (entity.kind !== 'AiResource') {\n return entity;\n }\n\n const errors: string[] = [];\n\n const scope = entity.spec?.scope;\n if (\n scope !== undefined &&\n !VALID_AI_RESOURCE_SCOPES.includes(scope as AiResourceScope)\n ) {\n const accepted = VALID_AI_RESOURCE_SCOPES.map(v => `'${v}'`).join(', ');\n const sanitized = Array.from(String(scope))\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n errors.push(\n `spec.scope has invalid value '${sanitized}'; accepted values are ${accepted}`,\n );\n }\n\n errors.push(...collectOciErrors(entity));\n\n if (errors.length > 0) {\n throw new Error(\n `Validation failed for AiResource entity: ${errors.join('; ')}`,\n );\n }\n\n return entity;\n }\n}\n"],"names":["collectOciErrors"],"mappings":";;;;AA6BO,MAAM,wBAAA,GAA2B;AAAA,EACtC,cAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF;AAwBO,MAAM,6BAAA,CAA0D;AAAA,EACrE,gBAAA,GAA2B;AACzB,IAAA,OAAO,+BAAA;AAAA,EACT;AAAA,EAEA,MAAM,gBAAA,CACJ,MAAA,EACA,SAAA,EACA,KAAA,EACiB;AACjB,IAAA,IAAI,MAAA,CAAO,SAAS,YAAA,EAAc;AAChC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,SAAmB,EAAC;AAE1B,IAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,EAAM,KAAA;AAC3B,IAAA,IACE,UAAU,MAAA,IACV,CAAC,wBAAA,CAAyB,QAAA,CAAS,KAAwB,CAAA,EAC3D;AACA,MAAA,MAAM,QAAA,GAAW,yBAAyB,GAAA,CAAI,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AACtE,MAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA,CACvC,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,CAAA,8BAAA,EAAiC,SAAS,CAAA,uBAAA,EAA0B,QAAQ,CAAA;AAAA,OAC9E;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,GAAGA,iCAAA,CAAiB,MAAM,CAAC,CAAA;AAEvC,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yCAAA,EAA4C,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OAC/D;AAAA,IACF;AAEA,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"collectOciErrors.cjs.js","sources":["../src/collectOciErrors.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ANNOTATION_SOURCE_LOCATION,\n Entity,\n parseLocationRef,\n} from '@backstage/catalog-model';\n\n/**\n * Collects OCI-related validation errors for an AIResource entity\n * without throwing. Returns an array of error messages (empty if valid).\n *\n * Validates the `backstage.io/source-location` annotation when its\n * location-ref target uses the `oci://` scheme. The annotation must\n * use the Backstage location-ref form `url:oci://…`.\n *\n * Uses upstream `parseLocationRef` for parsing, which normalizes\n * leading/trailing whitespace around type and target.\n *\n * @internal\n */\nexport function collectOciErrors(entity: Entity): string[] {\n const annotation = entity.metadata?.annotations?.[ANNOTATION_SOURCE_LOCATION];\n\n if (annotation === undefined || annotation === null) {\n return [];\n }\n\n let type: string;\n let target: string;\n try {\n ({ type, target } = parseLocationRef(annotation));\n } catch {\n // Not a valid location-ref (no colon, empty type/target, etc.)\n // Leave it for other processors to handle.\n return [];\n }\n\n // Detect bare `oci://…` without the required `url:` prefix.\n // parseLocationRef parses `oci://host/path` as type `oci` with a\n // non-URL target, which breaks future UrlReader integration.\n if (type === 'oci') {\n const sanitized = Array.from(annotation.trim())\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} '${sanitized}' uses a bare oci:// URI; ` +\n `the Backstage location-ref form url:oci://… is required ` +\n `(e.g. url:oci://quay.io/org/model:tag)`,\n ];\n }\n\n // Only apply OCI validation when the location-ref type is `url` and\n // the target starts with `oci://`.\n if (type !== 'url' || !target.startsWith('oci://')) {\n return [];\n }\n\n const ociPath = target.slice('oci://'.length);\n\n if (ociPath === '') {\n return [\n `${ANNOTATION_SOURCE_LOCATION} target 'oci://' is not a valid OCI ` +\n `reference; expected format url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n const parts = ociPath.split('/');\n\n // Reject missing registry/repo, empty segments (e.g. trailing slash),\n // and whitespace inside path segments (e.g. oci:// quay.io/...).\n if (parts.length < 2 || parts.some(part => part === '' || /\\s/.test(part))) {\n const sanitized = Array.from(target)\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} target '${sanitized}' is not a valid ` +\n `OCI reference; expected format ` +\n `url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n return [];\n}\n"],"names":["ANNOTATION_SOURCE_LOCATION","parseLocationRef"],"mappings":";;;;AAmCO,SAAS,iBAAiB,MAAA,EAA0B;AACzD,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,QAAA,EAAU,WAAA,GAAcA,uCAA0B,CAAA;AAE5E,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,UAAA,KAAe,IAAA,EAAM;AACnD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,CAAC,EAAE,IAAA,EAAM,MAAA,EAAO,GAAIC,8BAAiB,UAAU,CAAA;AAAA,EACjD,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,EAAC;AAAA,EACV;AAKA,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,CAC3C,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGD,uCAA0B,CAAA,EAAA,EAAK,SAAS,CAAA,6HAAA;AAAA,KAG7C;AAAA,EACF;AAIA,EAAA,IAAI,SAAS,KAAA,IAAS,CAAC,MAAA,CAAO,UAAA,CAAW,QAAQ,CAAA,EAAG;AAClD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAE5C,EAAA,IAAI,YAAY,EAAA,EAAI;AAClB,IAAA,OAAO;AAAA,MACL,GAAGA,uCAA0B,CAAA,0GAAA;AAAA,KAE/B;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAI/B,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,IAAA,KAAS,EAAA,IAAM,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,EAAG;AAC1E,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,CAChC,MAAA,CAAO,OAAK,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGA,uCAA0B,CAAA,SAAA,EAAY,SAAS,CAAA,2FAAA;AAAA,KAGpD;AAAA,EACF;AAEA,EAAA,OAAO,EAAC;AACV;;;;"}
1
+ {"version":3,"file":"collectOciErrors.cjs.js","sources":["../src/collectOciErrors.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ANNOTATION_SOURCE_LOCATION,\n Entity,\n parseLocationRef,\n} from '@backstage/catalog-model';\n\n/**\n * Collects OCI-related validation errors for an AiResource entity\n * without throwing. Returns an array of error messages (empty if valid).\n *\n * Validates the `backstage.io/source-location` annotation when its\n * location-ref target uses the `oci://` scheme. The annotation must\n * use the Backstage location-ref form `url:oci://…`.\n *\n * Uses upstream `parseLocationRef` for parsing, which normalizes\n * leading/trailing whitespace around type and target.\n *\n * @internal\n */\nexport function collectOciErrors(entity: Entity): string[] {\n const annotation = entity.metadata?.annotations?.[ANNOTATION_SOURCE_LOCATION];\n\n if (annotation === undefined || annotation === null) {\n return [];\n }\n\n let type: string;\n let target: string;\n try {\n ({ type, target } = parseLocationRef(annotation));\n } catch {\n // Not a valid location-ref (no colon, empty type/target, etc.)\n // Leave it for other processors to handle.\n return [];\n }\n\n // Detect bare `oci://…` without the required `url:` prefix.\n // parseLocationRef parses `oci://host/path` as type `oci` with a\n // non-URL target, which breaks future UrlReader integration.\n if (type === 'oci') {\n const sanitized = Array.from(annotation.trim())\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} '${sanitized}' uses a bare oci:// URI; ` +\n `the Backstage location-ref form url:oci://… is required ` +\n `(e.g. url:oci://quay.io/org/model:tag)`,\n ];\n }\n\n // Only apply OCI validation when the location-ref type is `url` and\n // the target starts with `oci://`.\n if (type !== 'url' || !target.startsWith('oci://')) {\n return [];\n }\n\n const ociPath = target.slice('oci://'.length);\n\n if (ociPath === '') {\n return [\n `${ANNOTATION_SOURCE_LOCATION} target 'oci://' is not a valid OCI ` +\n `reference; expected format url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n const parts = ociPath.split('/');\n\n // Reject missing registry/repo, empty segments (e.g. trailing slash),\n // and whitespace inside path segments (e.g. oci:// quay.io/...).\n if (parts.length < 2 || parts.some(part => part === '' || /\\s/.test(part))) {\n const sanitized = Array.from(target)\n .filter(c => c.charCodeAt(0) > 0x1f)\n .join('')\n .slice(0, 200);\n return [\n `${ANNOTATION_SOURCE_LOCATION} target '${sanitized}' is not a valid ` +\n `OCI reference; expected format ` +\n `url:oci://registry/repository[:tag|@digest]`,\n ];\n }\n\n return [];\n}\n"],"names":["ANNOTATION_SOURCE_LOCATION","parseLocationRef"],"mappings":";;;;AAmCO,SAAS,iBAAiB,MAAA,EAA0B;AACzD,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,QAAA,EAAU,WAAA,GAAcA,uCAA0B,CAAA;AAE5E,EAAA,IAAI,UAAA,KAAe,MAAA,IAAa,UAAA,KAAe,IAAA,EAAM;AACnD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,CAAC,EAAE,IAAA,EAAM,MAAA,EAAO,GAAIC,8BAAiB,UAAU,CAAA;AAAA,EACjD,CAAA,CAAA,MAAQ;AAGN,IAAA,OAAO,EAAC;AAAA,EACV;AAKA,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,CAC3C,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGD,uCAA0B,CAAA,EAAA,EAAK,SAAS,CAAA,6HAAA;AAAA,KAG7C;AAAA,EACF;AAIA,EAAA,IAAI,SAAS,KAAA,IAAS,CAAC,MAAA,CAAO,UAAA,CAAW,QAAQ,CAAA,EAAG;AAClD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,QAAA,CAAS,MAAM,CAAA;AAE5C,EAAA,IAAI,YAAY,EAAA,EAAI;AAClB,IAAA,OAAO;AAAA,MACL,GAAGA,uCAA0B,CAAA,0GAAA;AAAA,KAE/B;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AAI/B,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,IAAK,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,IAAA,KAAS,EAAA,IAAM,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,EAAG;AAC1E,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,CAChC,MAAA,CAAO,OAAK,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,EAAI,CAAA,CAClC,IAAA,CAAK,EAAE,CAAA,CACP,KAAA,CAAM,GAAG,GAAG,CAAA;AACf,IAAA,OAAO;AAAA,MACL,CAAA,EAAGA,uCAA0B,CAAA,SAAA,EAAY,SAAS,CAAA,2FAAA;AAAA,KAGpD;AAAA,EACF;AAEA,EAAA,OAAO,EAAC;AACV;;;;"}
package/dist/index.cjs.js CHANGED
@@ -3,11 +3,11 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var module$1 = require('./module.cjs.js');
6
- var AIResourceExtensionsProcessor = require('./AIResourceExtensionsProcessor.cjs.js');
6
+ var AiResourceExtensionsProcessor = require('./AiResourceExtensionsProcessor.cjs.js');
7
7
 
8
8
 
9
9
 
10
10
  exports.default = module$1.catalogModuleCatalogBackendModuleAiResourceExtensions;
11
- exports.AIResourceExtensionsProcessor = AIResourceExtensionsProcessor.AIResourceExtensionsProcessor;
12
- exports.VALID_AI_RESOURCE_SCOPES = AIResourceExtensionsProcessor.VALID_AI_RESOURCE_SCOPES;
11
+ exports.AiResourceExtensionsProcessor = AiResourceExtensionsProcessor.AiResourceExtensionsProcessor;
12
+ exports.VALID_AI_RESOURCE_SCOPES = AiResourceExtensionsProcessor.VALID_AI_RESOURCE_SCOPES;
13
13
  //# sourceMappingURL=index.cjs.js.map
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-common';
6
6
  /**
7
7
  * catalogModuleCatalogBackendModuleAiResourceExtensions is the catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.
8
8
  *
9
- * Registers {@link AIResourceExtensionsProcessor}, which validates all
9
+ * Registers {@link AiResourceExtensionsProcessor}, which validates all
10
10
  * RHDH extension constraints (spec.scope and OCI source-location) and
11
11
  * aggregates errors into a single response.
12
12
  *
@@ -15,20 +15,20 @@ import { LocationSpec } from '@backstage/plugin-catalog-common';
15
15
  declare const catalogModuleCatalogBackendModuleAiResourceExtensions: _backstage_backend_plugin_api.BackendFeature;
16
16
 
17
17
  /**
18
- * Valid values for spec.scope on AIResource entities.
18
+ * Valid values for spec.scope on AiResource entities.
19
19
  *
20
20
  * @public
21
21
  */
22
22
  declare const VALID_AI_RESOURCE_SCOPES: readonly ["organization", "product", "team"];
23
23
  /**
24
- * Type for valid AIResource scope values.
24
+ * Type for valid AiResource scope values.
25
25
  *
26
26
  * @public
27
27
  */
28
- type AIResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];
28
+ type AiResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];
29
29
  /**
30
30
  * A CatalogProcessor that validates RHDH-specific extension
31
- * fields on AIResource entities.
31
+ * fields on AiResource entities.
32
32
  *
33
33
  * Validates:
34
34
  * - `spec.scope`: optional field restricted to 'organization',
@@ -41,7 +41,7 @@ type AIResourceScope = (typeof VALID_AI_RESOURCE_SCOPES)[number];
41
41
  *
42
42
  * @public
43
43
  */
44
- declare class AIResourceExtensionsProcessor implements CatalogProcessor {
44
+ declare class AiResourceExtensionsProcessor implements CatalogProcessor {
45
45
  getProcessorName(): string;
46
46
  preProcessEntity(entity: Entity, _location: LocationSpec, _emit: CatalogProcessorEmit): Promise<Entity>;
47
47
  }
@@ -53,5 +53,5 @@ declare class AIResourceExtensionsProcessor implements CatalogProcessor {
53
53
  */
54
54
 
55
55
 
56
- export { AIResourceExtensionsProcessor, VALID_AI_RESOURCE_SCOPES, catalogModuleCatalogBackendModuleAiResourceExtensions as default };
57
- export type { AIResourceScope };
56
+ export { AiResourceExtensionsProcessor, VALID_AI_RESOURCE_SCOPES, catalogModuleCatalogBackendModuleAiResourceExtensions as default };
57
+ export type { AiResourceScope };
@@ -2,7 +2,7 @@
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
  var pluginCatalogNode = require('@backstage/plugin-catalog-node');
5
- var AIResourceExtensionsProcessor = require('./AIResourceExtensionsProcessor.cjs.js');
5
+ var AiResourceExtensionsProcessor = require('./AiResourceExtensionsProcessor.cjs.js');
6
6
 
7
7
  const catalogModuleCatalogBackendModuleAiResourceExtensions = backendPluginApi.createBackendModule({
8
8
  pluginId: "catalog",
@@ -13,7 +13,7 @@ const catalogModuleCatalogBackendModuleAiResourceExtensions = backendPluginApi.c
13
13
  catalog: pluginCatalogNode.catalogProcessingExtensionPoint
14
14
  },
15
15
  async init({ catalog }) {
16
- catalog.addProcessor(new AIResourceExtensionsProcessor.AIResourceExtensionsProcessor());
16
+ catalog.addProcessor(new AiResourceExtensionsProcessor.AiResourceExtensionsProcessor());
17
17
  }
18
18
  });
19
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createBackendModule } from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\n\nimport { AIResourceExtensionsProcessor } from './AIResourceExtensionsProcessor';\n\n/**\n * catalogModuleCatalogBackendModuleAiResourceExtensions is the catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.\n *\n * Registers {@link AIResourceExtensionsProcessor}, which validates all\n * RHDH extension constraints (spec.scope and OCI source-location) and\n * aggregates errors into a single response.\n *\n * @public\n */\nexport const catalogModuleCatalogBackendModuleAiResourceExtensions =\n createBackendModule({\n pluginId: 'catalog',\n moduleId: 'catalog-backend-module-ai-resource-extensions',\n register(reg) {\n reg.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n },\n async init({ catalog }) {\n catalog.addProcessor(new AIResourceExtensionsProcessor());\n },\n });\n },\n });\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","AIResourceExtensionsProcessor"],"mappings":";;;;;;AA6BO,MAAM,wDACXA,oCAAA,CAAoB;AAAA,EAClB,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,+CAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC;AAAA,OACX;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,OAAA,EAAQ,EAAG;AACtB,QAAA,OAAA,CAAQ,YAAA,CAAa,IAAIC,2DAAA,EAA+B,CAAA;AAAA,MAC1D;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createBackendModule } from '@backstage/backend-plugin-api';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';\n\nimport { AiResourceExtensionsProcessor } from './AiResourceExtensionsProcessor';\n\n/**\n * catalogModuleCatalogBackendModuleAiResourceExtensions is the catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.\n *\n * Registers {@link AiResourceExtensionsProcessor}, which validates all\n * RHDH extension constraints (spec.scope and OCI source-location) and\n * aggregates errors into a single response.\n *\n * @public\n */\nexport const catalogModuleCatalogBackendModuleAiResourceExtensions =\n createBackendModule({\n pluginId: 'catalog',\n moduleId: 'catalog-backend-module-ai-resource-extensions',\n register(reg) {\n reg.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n },\n async init({ catalog }) {\n catalog.addProcessor(new AiResourceExtensionsProcessor());\n },\n });\n },\n });\n"],"names":["createBackendModule","catalogProcessingExtensionPoint","AiResourceExtensionsProcessor"],"mappings":";;;;;;AA6BO,MAAM,wDACXA,oCAAA,CAAoB;AAAA,EAClB,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,+CAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC;AAAA,OACX;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,OAAA,EAAQ,EAAG;AACtB,QAAA,OAAA,CAAQ,YAAA,CAAa,IAAIC,2DAAA,EAA+B,CAAA;AAAA,MAC1D;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-catalog-backend-module-ai-resource-extensions",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The catalog-backend-module-ai-resource-extensions backend module for the catalog plugin.",
6
6
  "main": "dist/index.cjs.js",