@red-hat-developer-hub/backstage-plugin-ai-skills-common 0.2.0 → 0.4.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.
@@ -0,0 +1,341 @@
1
+ import { MAX_TAG_LENGTH, CATALOG_TAG_PATTERN } from './tag-constants.esm.js';
2
+
3
+ const DIAGNOSTIC_VALUE_MAX_LENGTH = 80;
4
+ function truncateForDiagnostic(value) {
5
+ if (value.length <= DIAGNOSTIC_VALUE_MAX_LENGTH) {
6
+ return value;
7
+ }
8
+ return `${value.slice(0, DIAGNOSTIC_VALUE_MAX_LENGTH)}\u2026`;
9
+ }
10
+ function asNonEmptyString(value) {
11
+ if (typeof value === "string") {
12
+ const trimmed = value.trim();
13
+ if (trimmed.length > 0) {
14
+ return trimmed;
15
+ }
16
+ }
17
+ return void 0;
18
+ }
19
+ function firstNonEmptyString(...candidates) {
20
+ for (const c of candidates) {
21
+ const s = asNonEmptyString(c);
22
+ if (s !== void 0) {
23
+ return s;
24
+ }
25
+ }
26
+ return void 0;
27
+ }
28
+ function validateAuthorEntry(entry) {
29
+ if (typeof entry !== "object" || entry === null) {
30
+ return null;
31
+ }
32
+ const obj = entry;
33
+ const name = asNonEmptyString(obj.name);
34
+ if (!name) {
35
+ return null;
36
+ }
37
+ const author = { name };
38
+ if (typeof obj.email === "string" && obj.email.trim().length > 0) {
39
+ author.email = obj.email.trim();
40
+ }
41
+ return author;
42
+ }
43
+ function normalizeAuthors(candidates, diagnostics) {
44
+ for (const candidate of candidates) {
45
+ if (candidate === void 0 || candidate === null) {
46
+ continue;
47
+ }
48
+ if (typeof candidate === "string") {
49
+ const trimmed = candidate.trim();
50
+ if (trimmed.length > 0) {
51
+ return [{ name: trimmed }];
52
+ }
53
+ continue;
54
+ }
55
+ if (Array.isArray(candidate)) {
56
+ if (candidate.length === 0) {
57
+ continue;
58
+ }
59
+ const validAuthors = [];
60
+ for (let i = 0; i < candidate.length; i++) {
61
+ const author = validateAuthorEntry(candidate[i]);
62
+ if (author) {
63
+ validAuthors.push(author);
64
+ } else {
65
+ diagnostics.push({
66
+ field: "authors",
67
+ message: `authors[${i}]: invalid author entry omitted \u2014 must be an object with a non-empty name`
68
+ });
69
+ }
70
+ }
71
+ if (validAuthors.length > 0) {
72
+ return validAuthors;
73
+ }
74
+ continue;
75
+ }
76
+ diagnostics.push({
77
+ field: "authors",
78
+ message: `authors: unsupported type '${typeof candidate}' \u2014 expected array or string`
79
+ });
80
+ }
81
+ return void 0;
82
+ }
83
+ function normalizeSingleTag(tag) {
84
+ if (typeof tag !== "string") {
85
+ return null;
86
+ }
87
+ const normalized = tag.trim().toLocaleLowerCase("en-US");
88
+ if (normalized.length === 0) {
89
+ return null;
90
+ }
91
+ if (normalized.length > MAX_TAG_LENGTH) {
92
+ return null;
93
+ }
94
+ if (!CATALOG_TAG_PATTERN.test(normalized)) {
95
+ return null;
96
+ }
97
+ return normalized;
98
+ }
99
+ function normalizeTagCandidates(candidates, diagnostics) {
100
+ for (const candidate of candidates) {
101
+ if (candidate === void 0 || candidate === null) {
102
+ continue;
103
+ }
104
+ if (!Array.isArray(candidate)) {
105
+ diagnostics.push({
106
+ field: "tags",
107
+ message: `tags: unsupported type '${typeof candidate}' \u2014 expected array; value omitted`
108
+ });
109
+ continue;
110
+ }
111
+ if (candidate.length === 0) {
112
+ continue;
113
+ }
114
+ const seen = /* @__PURE__ */ new Set();
115
+ const validTags = [];
116
+ for (let i = 0; i < candidate.length; i++) {
117
+ const normalized = normalizeSingleTag(candidate[i]);
118
+ if (normalized === null) {
119
+ if (typeof candidate[i] === "string") {
120
+ const raw = candidate[i].trim();
121
+ if (raw.length === 0) {
122
+ diagnostics.push({
123
+ field: "tags",
124
+ message: `tags[${i}]: empty after trim, omitted`
125
+ });
126
+ } else if (raw.length > MAX_TAG_LENGTH) {
127
+ diagnostics.push({
128
+ field: "tags",
129
+ message: `tags[${i}]: overlength tag '${truncateForDiagnostic(
130
+ raw
131
+ )}' omitted \u2014 exceeds ${MAX_TAG_LENGTH} characters`
132
+ });
133
+ } else {
134
+ diagnostics.push({
135
+ field: "tags",
136
+ message: `tags[${i}]: invalid tag '${truncateForDiagnostic(
137
+ raw
138
+ )}' omitted \u2014 does not match catalog tag pattern`
139
+ });
140
+ }
141
+ } else {
142
+ diagnostics.push({
143
+ field: "tags",
144
+ message: `tags[${i}]: non-string value omitted`
145
+ });
146
+ }
147
+ continue;
148
+ }
149
+ if (!seen.has(normalized)) {
150
+ seen.add(normalized);
151
+ validTags.push(normalized);
152
+ }
153
+ }
154
+ if (validTags.length > 0) {
155
+ return validTags;
156
+ }
157
+ }
158
+ return void 0;
159
+ }
160
+ function diagnoseOptionalStringField(fieldName, value, diagnostics) {
161
+ if (value !== void 0 && value !== null && typeof value !== "string") {
162
+ diagnostics.push({
163
+ field: fieldName,
164
+ message: `${fieldName}: unsupported type '${typeof value}' \u2014 expected string; value omitted`
165
+ });
166
+ }
167
+ }
168
+ function diagnoseNonStringCandidates(fieldName, candidates, diagnostics) {
169
+ for (const c of candidates) {
170
+ diagnoseOptionalStringField(fieldName, c, diagnostics);
171
+ }
172
+ }
173
+ function normalizeOciMetadata(native, trusted) {
174
+ const diagnostics = [];
175
+ const meta = native.skillCard?.metadata;
176
+ const spec = native.skillCard?.spec;
177
+ const fm = native.frontmatter;
178
+ const name = firstNonEmptyString(
179
+ meta?.["display-name"],
180
+ meta?.name,
181
+ fm?.name
182
+ );
183
+ if (!name) {
184
+ diagnostics.push({
185
+ field: "name",
186
+ message: "name: no valid display name found in SkillCard metadata or frontmatter"
187
+ });
188
+ return { record: null, diagnostics };
189
+ }
190
+ const description = firstNonEmptyString(meta?.description, fm?.description);
191
+ diagnoseNonStringCandidates(
192
+ "description",
193
+ [meta?.description, fm?.description],
194
+ diagnostics
195
+ );
196
+ const version = firstNonEmptyString(
197
+ meta?.version,
198
+ fm?.metadata?.version,
199
+ fm?.version
200
+ );
201
+ diagnoseNonStringCandidates(
202
+ "version",
203
+ [meta?.version, fm?.metadata?.version, fm?.version],
204
+ diagnostics
205
+ );
206
+ const license = firstNonEmptyString(meta?.license, fm?.license);
207
+ diagnoseNonStringCandidates(
208
+ "license",
209
+ [meta?.license, fm?.license],
210
+ diagnostics
211
+ );
212
+ const compatibility = firstNonEmptyString(
213
+ meta?.compatibility,
214
+ fm?.compatibility
215
+ );
216
+ diagnoseNonStringCandidates(
217
+ "compatibility",
218
+ [meta?.compatibility, fm?.compatibility],
219
+ diagnostics
220
+ );
221
+ const owner = firstNonEmptyString(fm?.metadata?.owner);
222
+ diagnoseNonStringCandidates("owner", [fm?.metadata?.owner], diagnostics);
223
+ const lifecycle = firstNonEmptyString(fm?.metadata?.lifecycle);
224
+ diagnoseNonStringCandidates(
225
+ "lifecycle",
226
+ [fm?.metadata?.lifecycle],
227
+ diagnostics
228
+ );
229
+ const authors = normalizeAuthors(
230
+ [meta?.authors, fm?.metadata?.author],
231
+ diagnostics
232
+ );
233
+ const tags = normalizeTagCandidates(
234
+ [meta?.tags, fm?.metadata?.tags],
235
+ diagnostics
236
+ );
237
+ const record = {
238
+ key: trusted.key,
239
+ name,
240
+ sourceUri: trusted.sourceUri,
241
+ digest: trusted.digest
242
+ };
243
+ if (description) record.description = description;
244
+ if (version) record.version = version;
245
+ if (license) record.license = license;
246
+ if (authors) record.authors = authors;
247
+ if (tags) record.tags = tags;
248
+ if (compatibility) record.compatibility = compatibility;
249
+ if (owner) record.owner = owner;
250
+ if (lifecycle) record.lifecycle = lifecycle;
251
+ const namespace = asNonEmptyString(meta?.namespace);
252
+ diagnoseOptionalStringField(
253
+ "extensions.oci.namespace",
254
+ meta?.namespace,
255
+ diagnostics
256
+ );
257
+ const prompt = asNonEmptyString(spec?.prompt);
258
+ diagnoseOptionalStringField(
259
+ "extensions.oci.prompt",
260
+ spec?.prompt,
261
+ diagnostics
262
+ );
263
+ if (namespace || prompt) {
264
+ const oci = {};
265
+ if (namespace) oci.namespace = namespace;
266
+ if (prompt) oci.prompt = prompt;
267
+ record.extensions = { oci };
268
+ }
269
+ return { record, diagnostics };
270
+ }
271
+ function normalizeNpxMetadata(native, trusted) {
272
+ const diagnostics = [];
273
+ const entry = native.entry;
274
+ const fm = native.frontmatter;
275
+ const name = firstNonEmptyString(fm?.name, entry?.name);
276
+ if (!name) {
277
+ diagnostics.push({
278
+ field: "name",
279
+ message: "name: no valid display name found in frontmatter or discovery entry"
280
+ });
281
+ return { record: null, diagnostics };
282
+ }
283
+ const description = firstNonEmptyString(fm?.description, entry?.description);
284
+ diagnoseNonStringCandidates(
285
+ "description",
286
+ [fm?.description, entry?.description],
287
+ diagnostics
288
+ );
289
+ const version = firstNonEmptyString(fm?.metadata?.version, fm?.version);
290
+ diagnoseNonStringCandidates(
291
+ "version",
292
+ [fm?.metadata?.version, fm?.version],
293
+ diagnostics
294
+ );
295
+ const license = firstNonEmptyString(fm?.license);
296
+ diagnoseNonStringCandidates("license", [fm?.license], diagnostics);
297
+ const compatibility = firstNonEmptyString(fm?.compatibility);
298
+ diagnoseNonStringCandidates(
299
+ "compatibility",
300
+ [fm?.compatibility],
301
+ diagnostics
302
+ );
303
+ const owner = firstNonEmptyString(fm?.metadata?.owner);
304
+ diagnoseNonStringCandidates("owner", [fm?.metadata?.owner], diagnostics);
305
+ const lifecycle = firstNonEmptyString(fm?.metadata?.lifecycle);
306
+ diagnoseNonStringCandidates(
307
+ "lifecycle",
308
+ [fm?.metadata?.lifecycle],
309
+ diagnostics
310
+ );
311
+ const authors = normalizeAuthors([fm?.metadata?.author], diagnostics);
312
+ const tags = normalizeTagCandidates([fm?.metadata?.tags], diagnostics);
313
+ const record = {
314
+ key: trusted.key,
315
+ name,
316
+ sourceUri: trusted.sourceUri,
317
+ digest: trusted.digest
318
+ };
319
+ if (description) record.description = description;
320
+ if (version) record.version = version;
321
+ if (license) record.license = license;
322
+ if (authors) record.authors = authors;
323
+ if (tags) record.tags = tags;
324
+ if (compatibility) record.compatibility = compatibility;
325
+ if (owner) record.owner = owner;
326
+ if (lifecycle) record.lifecycle = lifecycle;
327
+ if (entry?.type === "skill-md") {
328
+ record.extensions = { npx: { type: "skill-md" } };
329
+ } else if (entry?.type !== void 0 && entry?.type !== null) {
330
+ diagnostics.push({
331
+ field: "extensions.npx.type",
332
+ message: `extensions.npx.type: unsupported type value '${truncateForDiagnostic(
333
+ String(entry.type)
334
+ )}' \u2014 only 'skill-md' is allowed; extension omitted`
335
+ });
336
+ }
337
+ return { record, diagnostics };
338
+ }
339
+
340
+ export { normalizeNpxMetadata, normalizeOciMetadata };
341
+ //# sourceMappingURL=normalizer.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizer.esm.js","sources":["../src/normalizer.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\n/**\n * Pure OCI and npx metadata normalizers implementing design D3\n * native-field mappings and precedence.\n *\n * These functions accept already-parsed, verified inputs and return\n * schema-valid SkillRecords or explicit per-record diagnostics.\n * They perform no network, scheduler, database, or catalog operations.\n *\n * @packageDocumentation\n */\n\nimport { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH } from './tag-constants';\nimport type {\n NpxSkillRecord,\n OciExtensions,\n OciSkillRecord,\n SkillAuthor,\n} from './types';\n\n// ─── Input types ─────────────────────────────────────────────────────\n\n/**\n * Parsed OCI SkillCard metadata fields.\n *\n * Fields use `unknown` because the YAML parser may produce any type.\n * The normalizer validates and selects correctly typed values.\n *\n * @remarks\n * The `unknown` field types are deliberate and stable for the v1 contract.\n * Native metadata arrives as parsed YAML/JSON and may contain any type.\n * Narrowing to concrete types in a future minor release would be a\n * breaking TypeScript change; any such narrowing will be reserved for v2.\n *\n * @public\n */\nexport interface OciSkillCardMetadata {\n /** Display name (`metadata.display-name`). */\n 'display-name'?: unknown;\n /** Name (`metadata.name`). */\n name?: unknown;\n /** Description (`metadata.description`). */\n description?: unknown;\n /** Declared version (`metadata.version`). */\n version?: unknown;\n /** License identifier (`metadata.license`). */\n license?: unknown;\n /** Author list (`metadata.authors`). */\n authors?: unknown;\n /** Metadata tags (`metadata.tags`). */\n tags?: unknown;\n /** Compatibility info (`metadata.compatibility`). */\n compatibility?: unknown;\n /** Namespace scope (`metadata.namespace`). */\n namespace?: unknown;\n}\n\n/**\n * Parsed OCI SkillCard spec fields.\n *\n * @public\n */\nexport interface OciSkillCardSpec {\n /** System prompt (`spec.prompt`). */\n prompt?: unknown;\n}\n\n/**\n * Parsed OCI SkillCard document.\n *\n * @public\n */\nexport interface OciSkillCard {\n /** SkillCard metadata section. */\n metadata?: OciSkillCardMetadata;\n /** SkillCard spec section. */\n spec?: OciSkillCardSpec;\n}\n\n/**\n * Shared base interface for parsed Markdown frontmatter.\n *\n * Both OCI and npx skill documents use the same YAML frontmatter\n * structure. This base captures the common shape. Dedicated aliases\n * ({@link OciMarkdownFrontmatter}, {@link NpxFrontmatter}) represent\n * semantically distinct domains and may diverge independently in\n * future versions.\n *\n * @remarks\n * The `unknown` field types are deliberate and stable for the v1 contract.\n * See {@link OciSkillCardMetadata} for the rationale.\n *\n * @public\n */\nexport interface MarkdownFrontmatter {\n /** Top-level name. */\n name?: unknown;\n /** Top-level description. */\n description?: unknown;\n /** Top-level version (frontmatter `version`). */\n version?: unknown;\n /** Top-level license. */\n license?: unknown;\n /** Top-level compatibility. */\n compatibility?: unknown;\n /** Nested metadata block in frontmatter. */\n metadata?: FrontmatterMetadata;\n}\n\n/**\n * Nested metadata block within parsed Markdown frontmatter.\n *\n * @remarks\n * The `unknown` field types are deliberate and stable for the v1 contract.\n * See {@link OciSkillCardMetadata} for the rationale.\n *\n * @public\n */\nexport interface FrontmatterMetadata {\n /** Frontmatter `metadata.version`. */\n version?: unknown;\n /** Frontmatter `metadata.author` (string or object). */\n author?: unknown;\n /** Frontmatter `metadata.tags`. */\n tags?: unknown;\n /** Frontmatter `metadata.owner`. */\n owner?: unknown;\n /** Frontmatter `metadata.lifecycle`. */\n lifecycle?: unknown;\n}\n\n/**\n * Parsed Markdown frontmatter from an OCI skill document\n * (SKILL.md or SKILLS.md).\n *\n * Structurally identical to {@link MarkdownFrontmatter} but kept as a\n * separate named type so the OCI and npx domains can diverge\n * independently in future versions without a breaking rename.\n *\n * @public\n */\nexport type OciMarkdownFrontmatter = MarkdownFrontmatter;\n\n/**\n * Combined parsed OCI native input for normalization.\n *\n * @public\n */\nexport interface OciNativeInput {\n /** Parsed SkillCard document. */\n skillCard?: OciSkillCard;\n /** Parsed Markdown frontmatter. */\n frontmatter?: OciMarkdownFrontmatter;\n}\n\n/**\n * Parsed npx discovery entry from an Agent Skills index.\n *\n * @remarks\n * The `unknown` field types are deliberate and stable for the v1 contract.\n * See {@link OciSkillCardMetadata} for the rationale.\n *\n * @public\n */\nexport interface NpxDiscoveryEntry {\n /** Discovery entry name. */\n name?: unknown;\n /** Discovery entry description. */\n description?: unknown;\n /**\n * Artifact format type.\n *\n * @remarks\n * Currently only `'skill-md'` is accepted by the normalizer. Any other\n * non-nullish value is omitted from the normalized record with a\n * diagnostic. The field is typed `unknown` because the discovery index\n * may contain arbitrary strings; the normalizer validates at runtime.\n */\n type?: unknown;\n}\n\n/**\n * Parsed Markdown frontmatter from an npx skill document.\n *\n * Structurally identical to {@link MarkdownFrontmatter} but kept as a\n * separate named type so the npx and OCI domains can diverge\n * independently in future versions without a breaking rename.\n *\n * @public\n */\nexport type NpxFrontmatter = MarkdownFrontmatter;\n\n/**\n * Combined parsed npx native input for normalization.\n *\n * @public\n */\nexport interface NpxNativeInput {\n /** Parsed discovery entry. */\n entry?: NpxDiscoveryEntry;\n /** Parsed Markdown frontmatter. */\n frontmatter?: NpxFrontmatter;\n}\n\n/**\n * Trusted source identity and integrity fields provided by the\n * connector after verified acquisition. These are never derived\n * from native metadata or user-supplied frontmatter.\n *\n * @public\n */\nexport interface TrustedSourceInput {\n /** Stable identity key within this source. */\n key: string;\n /** Digest-addressed OCI URI or fragment-free HTTPS URL. */\n sourceUri: string;\n /** Content digest in `sha256:<64 lowercase hex digits>` format. */\n digest: string;\n}\n\n/**\n * A single diagnostic produced during metadata normalization.\n *\n * @public\n */\nexport interface NormalizationDiagnostic {\n /** The normalized field that triggered the diagnostic. */\n field: string;\n /**\n * Human-readable description of the issue.\n *\n * @remarks\n * The `message` field is informational and not part of the stable API\n * contract. Diagnostic wording may change between minor releases.\n * Downstream consumers should not assert on exact message text.\n */\n message: string;\n}\n\n/**\n * Result of a metadata normalization operation.\n *\n * When `record` is non-null, the normalization succeeded and the record\n * is schema-valid. Diagnostics may still be present for omitted optional\n * fields. When `record` is null, the normalization failed due to missing\n * required fields or other fatal issues.\n *\n * @public\n */\nexport interface NormalizationResult<T> {\n /** The normalized record, or null if normalization failed. */\n record: T | null;\n /** Diagnostics produced during normalization. */\n diagnostics: NormalizationDiagnostic[];\n}\n\n/** Maximum length for user-controlled values interpolated into diagnostics. */\nconst DIAGNOSTIC_VALUE_MAX_LENGTH = 80;\n\n/**\n * Truncates a string for safe interpolation into diagnostic messages.\n * Values longer than {@link DIAGNOSTIC_VALUE_MAX_LENGTH} are sliced and\n * suffixed with an ellipsis.\n */\nfunction truncateForDiagnostic(value: string): string {\n if (value.length <= DIAGNOSTIC_VALUE_MAX_LENGTH) {\n return value;\n }\n return `${value.slice(0, DIAGNOSTIC_VALUE_MAX_LENGTH)}…`;\n}\n\n// ─── Internal helpers ────────────────────────────────────────────────\n\n/**\n * Returns the trimmed string if the value is a non-empty string after\n * trimming, or `undefined` otherwise.\n */\nfunction asNonEmptyString(value: unknown): string | undefined {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0) {\n return trimmed;\n }\n }\n return undefined;\n}\n\n/**\n * Selects the first non-empty, correctly typed string value from an\n * ordered list of candidates. Implements D3 precedence for scalar fields.\n */\nfunction firstNonEmptyString(...candidates: unknown[]): string | undefined {\n for (const c of candidates) {\n const s = asNonEmptyString(c);\n if (s !== undefined) {\n return s;\n }\n }\n return undefined;\n}\n\n/**\n * Validates a single author entry from a SkillCard `metadata.authors`\n * array. Returns a valid `SkillAuthor` or null.\n */\nfunction validateAuthorEntry(entry: unknown): SkillAuthor | null {\n if (typeof entry !== 'object' || entry === null) {\n return null;\n }\n const obj = entry as Record<string, unknown>;\n const name = asNonEmptyString(obj.name);\n if (!name) {\n return null;\n }\n const author: SkillAuthor = { name };\n if (typeof obj.email === 'string' && obj.email.trim().length > 0) {\n author.email = obj.email.trim();\n }\n return author;\n}\n\n/**\n * Normalizes author data from an ordered list of candidates.\n *\n * SkillCard `metadata.authors` is expected as an array of objects.\n * Frontmatter `metadata.author` may be a single string, which is\n * normalized to `[{ name: value }]`.\n *\n * Returns the first valid, non-empty author list, or undefined.\n */\nfunction normalizeAuthors(\n candidates: unknown[],\n diagnostics: NormalizationDiagnostic[],\n): SkillAuthor[] | undefined {\n for (const candidate of candidates) {\n if (candidate === undefined || candidate === null) {\n continue;\n }\n\n // String author → normalize to [{ name: value }]\n if (typeof candidate === 'string') {\n const trimmed = candidate.trim();\n if (trimmed.length > 0) {\n return [{ name: trimmed }];\n }\n continue;\n }\n\n // Array of author objects\n if (Array.isArray(candidate)) {\n if (candidate.length === 0) {\n continue;\n }\n const validAuthors: SkillAuthor[] = [];\n for (let i = 0; i < candidate.length; i++) {\n const author = validateAuthorEntry(candidate[i]);\n if (author) {\n validAuthors.push(author);\n } else {\n diagnostics.push({\n field: 'authors',\n message: `authors[${i}]: invalid author entry omitted — must be an object with a non-empty name`,\n });\n }\n }\n if (validAuthors.length > 0) {\n return validAuthors;\n }\n // All entries were invalid — fall through to next candidate\n continue;\n }\n\n // Unsupported type\n diagnostics.push({\n field: 'authors',\n message: `authors: unsupported type '${typeof candidate}' — expected array or string`,\n });\n }\n return undefined;\n}\n\n/**\n * Validates a tag value against Backstage's catalog tag rules.\n * Tags are trimmed and lowercased before validation.\n *\n * Returns the normalized tag if valid, or null.\n */\nfunction normalizeSingleTag(tag: unknown): string | null {\n if (typeof tag !== 'string') {\n return null;\n }\n const normalized = tag.trim().toLocaleLowerCase('en-US');\n if (normalized.length === 0) {\n return null;\n }\n if (normalized.length > MAX_TAG_LENGTH) {\n return null;\n }\n if (!CATALOG_TAG_PATTERN.test(normalized)) {\n return null;\n }\n return normalized;\n}\n\n/**\n * Normalizes tags from an ordered list of candidates.\n *\n * Each candidate should be an array of strings. Selects the first\n * non-empty valid array. Individual tags are trimmed, lowercased,\n * validated against Backstage's tag rules, and deduplicated.\n * Invalid or overlength tags are omitted with diagnostics.\n */\nfunction normalizeTagCandidates(\n candidates: unknown[],\n diagnostics: NormalizationDiagnostic[],\n): string[] | undefined {\n for (const candidate of candidates) {\n if (candidate === undefined || candidate === null) {\n continue;\n }\n\n if (!Array.isArray(candidate)) {\n diagnostics.push({\n field: 'tags',\n message: `tags: unsupported type '${typeof candidate}' — expected array; value omitted`,\n });\n continue;\n }\n\n if (candidate.length === 0) {\n continue;\n }\n\n const seen = new Set<string>();\n const validTags: string[] = [];\n\n for (let i = 0; i < candidate.length; i++) {\n const normalized = normalizeSingleTag(candidate[i]);\n if (normalized === null) {\n if (typeof candidate[i] === 'string') {\n const raw = (candidate[i] as string).trim();\n if (raw.length === 0) {\n diagnostics.push({\n field: 'tags',\n message: `tags[${i}]: empty after trim, omitted`,\n });\n } else if (raw.length > MAX_TAG_LENGTH) {\n diagnostics.push({\n field: 'tags',\n message: `tags[${i}]: overlength tag '${truncateForDiagnostic(\n raw,\n )}' omitted — exceeds ${MAX_TAG_LENGTH} characters`,\n });\n } else {\n diagnostics.push({\n field: 'tags',\n message: `tags[${i}]: invalid tag '${truncateForDiagnostic(\n raw,\n )}' omitted — does not match catalog tag pattern`,\n });\n }\n } else {\n diagnostics.push({\n field: 'tags',\n message: `tags[${i}]: non-string value omitted`,\n });\n }\n continue;\n }\n\n if (!seen.has(normalized)) {\n seen.add(normalized);\n validTags.push(normalized);\n }\n }\n\n if (validTags.length > 0) {\n return validTags;\n }\n // All tags were invalid — fall through to next candidate\n }\n return undefined;\n}\n\n/**\n * Adds a diagnostic for an optional string field that has the wrong type.\n */\nfunction diagnoseOptionalStringField(\n fieldName: string,\n value: unknown,\n diagnostics: NormalizationDiagnostic[],\n): void {\n if (value !== undefined && value !== null && typeof value !== 'string') {\n diagnostics.push({\n field: fieldName,\n message: `${fieldName}: unsupported type '${typeof value}' — expected string; value omitted`,\n });\n }\n}\n\n/**\n * Emits diagnostics for non-string candidates at any priority level\n * in the candidate list. Called regardless of whether a valid string\n * was found at a lower priority.\n */\nfunction diagnoseNonStringCandidates(\n fieldName: string,\n candidates: unknown[],\n diagnostics: NormalizationDiagnostic[],\n): void {\n for (const c of candidates) {\n diagnoseOptionalStringField(fieldName, c, diagnostics);\n }\n}\n\n// ─── Public normalizer functions ─────────────────────────────────────\n\n/**\n * Normalizes OCI metadata into an `OciSkillRecord` using design D3\n * precedence rules.\n *\n * Accepts parsed, verified OCI SkillCard metadata and Markdown\n * frontmatter plus trusted source identity/integrity fields from\n * the connector. Returns a schema-valid record or null with\n * diagnostics.\n *\n * D3 OCI precedence order per field:\n * - name: `metadata.display-name`, `metadata.name`, frontmatter `name`\n * - description: `metadata.description`, frontmatter `description`\n * - version: `metadata.version`, frontmatter `metadata.version`,\n * frontmatter `version`\n * - license: `metadata.license`, frontmatter `license`\n * - authors: `metadata.authors`, frontmatter `metadata.author`\n * - tags: `metadata.tags`, frontmatter `metadata.tags`\n * - compatibility: `metadata.compatibility`, frontmatter `compatibility`\n * - owner: frontmatter `metadata.owner`\n * - lifecycle: frontmatter `metadata.lifecycle`\n * - extensions.oci.namespace: `metadata.namespace`\n * - extensions.oci.prompt: `spec.prompt`\n *\n * @public\n */\nexport function normalizeOciMetadata(\n native: OciNativeInput,\n trusted: TrustedSourceInput,\n): NormalizationResult<OciSkillRecord> {\n const diagnostics: NormalizationDiagnostic[] = [];\n const meta = native.skillCard?.metadata;\n const spec = native.skillCard?.spec;\n const fm = native.frontmatter;\n\n // Required: name — first non-empty from D3 precedence.\n // Unlike optional scalars, name uses a single terminal diagnostic\n // when no valid candidate is found rather than per-candidate\n // type-mismatch warnings. This is intentional: name is a required\n // field whose absence aborts normalization, so the terminal\n // diagnostic is authoritative and individual candidate warnings\n // would be noise on an already-failed result.\n const name = firstNonEmptyString(\n meta?.['display-name'],\n meta?.name,\n fm?.name,\n );\n if (!name) {\n diagnostics.push({\n field: 'name',\n message:\n 'name: no valid display name found in SkillCard metadata or frontmatter',\n });\n return { record: null, diagnostics };\n }\n\n // Optional scalars with D3 precedence\n // Always emit diagnostics for non-string candidates at any priority level,\n // even when a valid string is found at a lower priority.\n const description = firstNonEmptyString(meta?.description, fm?.description);\n diagnoseNonStringCandidates(\n 'description',\n [meta?.description, fm?.description],\n diagnostics,\n );\n\n // Version: metadata.version → frontmatter metadata.version → frontmatter version\n // Invalid first-choice version is retained for D5 fallback\n const version = firstNonEmptyString(\n meta?.version,\n fm?.metadata?.version,\n fm?.version,\n );\n diagnoseNonStringCandidates(\n 'version',\n [meta?.version, fm?.metadata?.version, fm?.version],\n diagnostics,\n );\n\n const license = firstNonEmptyString(meta?.license, fm?.license);\n diagnoseNonStringCandidates(\n 'license',\n [meta?.license, fm?.license],\n diagnostics,\n );\n\n const compatibility = firstNonEmptyString(\n meta?.compatibility,\n fm?.compatibility,\n );\n diagnoseNonStringCandidates(\n 'compatibility',\n [meta?.compatibility, fm?.compatibility],\n diagnostics,\n );\n\n // Owner and lifecycle — only from frontmatter metadata\n const owner = firstNonEmptyString(fm?.metadata?.owner);\n diagnoseNonStringCandidates('owner', [fm?.metadata?.owner], diagnostics);\n\n const lifecycle = firstNonEmptyString(fm?.metadata?.lifecycle);\n diagnoseNonStringCandidates(\n 'lifecycle',\n [fm?.metadata?.lifecycle],\n diagnostics,\n );\n\n // Authors: metadata.authors → frontmatter metadata.author\n const authors = normalizeAuthors(\n [meta?.authors, fm?.metadata?.author],\n diagnostics,\n );\n\n // Tags: metadata.tags → frontmatter metadata.tags\n const tags = normalizeTagCandidates(\n [meta?.tags, fm?.metadata?.tags],\n diagnostics,\n );\n\n // Build the record with trusted identity fields\n const record: OciSkillRecord = {\n key: trusted.key,\n name,\n sourceUri: trusted.sourceUri,\n digest: trusted.digest,\n };\n\n // Add optional fields only when present\n if (description) record.description = description;\n if (version) record.version = version;\n if (license) record.license = license;\n if (authors) record.authors = authors;\n if (tags) record.tags = tags;\n if (compatibility) record.compatibility = compatibility;\n if (owner) record.owner = owner;\n if (lifecycle) record.lifecycle = lifecycle;\n\n // Allowlisted OCI extensions: namespace and prompt\n const namespace = asNonEmptyString(meta?.namespace);\n diagnoseOptionalStringField(\n 'extensions.oci.namespace',\n meta?.namespace,\n diagnostics,\n );\n const prompt = asNonEmptyString(spec?.prompt);\n diagnoseOptionalStringField(\n 'extensions.oci.prompt',\n spec?.prompt,\n diagnostics,\n );\n\n if (namespace || prompt) {\n const oci: OciExtensions = {};\n if (namespace) oci.namespace = namespace;\n if (prompt) oci.prompt = prompt;\n record.extensions = { oci };\n }\n\n return { record, diagnostics };\n}\n\n/**\n * Normalizes npx metadata into an `NpxSkillRecord` using design D3\n * precedence rules.\n *\n * Accepts parsed, verified npx discovery entry and Markdown frontmatter\n * plus trusted source identity/integrity fields from the connector.\n * Returns a schema-valid record or null with diagnostics.\n *\n * D3 npx precedence order per field:\n * - name: frontmatter `name`, discovery entry `name`\n * - description: frontmatter `description`, discovery entry `description`\n * - version: frontmatter `metadata.version`, frontmatter `version`\n * - license: frontmatter `license`\n * - authors: frontmatter `metadata.author`\n * - tags: frontmatter `metadata.tags`\n * - compatibility: frontmatter `compatibility`\n * - owner: frontmatter `metadata.owner`\n * - lifecycle: frontmatter `metadata.lifecycle`\n * - extensions.npx.type: discovery entry `type`\n *\n * @public\n */\nexport function normalizeNpxMetadata(\n native: NpxNativeInput,\n trusted: TrustedSourceInput,\n): NormalizationResult<NpxSkillRecord> {\n const diagnostics: NormalizationDiagnostic[] = [];\n const entry = native.entry;\n const fm = native.frontmatter;\n\n // Required: name — frontmatter name → discovery entry name.\n // See normalizeOciMetadata for the rationale on terminal-only\n // diagnostics for the required name field.\n const name = firstNonEmptyString(fm?.name, entry?.name);\n if (!name) {\n diagnostics.push({\n field: 'name',\n message:\n 'name: no valid display name found in frontmatter or discovery entry',\n });\n return { record: null, diagnostics };\n }\n\n // Optional scalars with D3 precedence\n // Always emit diagnostics for non-string candidates at any priority level.\n const description = firstNonEmptyString(fm?.description, entry?.description);\n diagnoseNonStringCandidates(\n 'description',\n [fm?.description, entry?.description],\n diagnostics,\n );\n\n // Version: frontmatter metadata.version → frontmatter version\n const version = firstNonEmptyString(fm?.metadata?.version, fm?.version);\n diagnoseNonStringCandidates(\n 'version',\n [fm?.metadata?.version, fm?.version],\n diagnostics,\n );\n\n const license = firstNonEmptyString(fm?.license);\n diagnoseNonStringCandidates('license', [fm?.license], diagnostics);\n\n const compatibility = firstNonEmptyString(fm?.compatibility);\n diagnoseNonStringCandidates(\n 'compatibility',\n [fm?.compatibility],\n diagnostics,\n );\n\n // Owner and lifecycle — only from frontmatter metadata\n const owner = firstNonEmptyString(fm?.metadata?.owner);\n diagnoseNonStringCandidates('owner', [fm?.metadata?.owner], diagnostics);\n\n const lifecycle = firstNonEmptyString(fm?.metadata?.lifecycle);\n diagnoseNonStringCandidates(\n 'lifecycle',\n [fm?.metadata?.lifecycle],\n diagnostics,\n );\n\n // Authors: frontmatter metadata.author only\n const authors = normalizeAuthors([fm?.metadata?.author], diagnostics);\n\n // Tags: frontmatter metadata.tags only\n const tags = normalizeTagCandidates([fm?.metadata?.tags], diagnostics);\n\n // Build the record with trusted identity fields\n const record: NpxSkillRecord = {\n key: trusted.key,\n name,\n sourceUri: trusted.sourceUri,\n digest: trusted.digest,\n };\n\n // Add optional fields only when present\n if (description) record.description = description;\n if (version) record.version = version;\n if (license) record.license = license;\n if (authors) record.authors = authors;\n if (tags) record.tags = tags;\n if (compatibility) record.compatibility = compatibility;\n if (owner) record.owner = owner;\n if (lifecycle) record.lifecycle = lifecycle;\n\n // Allowlisted npx extension: type\n if (entry?.type === 'skill-md') {\n record.extensions = { npx: { type: 'skill-md' } };\n } else if (entry?.type !== undefined && entry?.type !== null) {\n diagnostics.push({\n field: 'extensions.npx.type',\n message: `extensions.npx.type: unsupported type value '${truncateForDiagnostic(\n String(entry.type),\n )}' — only 'skill-md' is allowed; extension omitted`,\n });\n }\n\n return { record, diagnostics };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const CATALOG_TAG_PATTERN = /^[a-z0-9:+#]+(-[a-z0-9:+#]+)*$/;
4
+ const MAX_TAG_LENGTH = 63;
5
+
6
+ exports.CATALOG_TAG_PATTERN = CATALOG_TAG_PATTERN;
7
+ exports.MAX_TAG_LENGTH = MAX_TAG_LENGTH;
8
+ //# sourceMappingURL=tag-constants.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-constants.cjs.js","sources":["../src/tag-constants.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\n/**\n * Shared catalog tag validation constants used by both the D3\n * metadata normalizers and the D5 catalog helpers.\n *\n * @packageDocumentation\n */\n\n/**\n * Catalog tag validation pattern.\n *\n * Tags contain lowercase alphanumeric characters, colons, plus signs,\n * and hash characters, separated by single hyphens.\n *\n * @remarks\n * This pattern matches Backstage's default catalog tag validator.\n * Tests compare it and the length limit with `makeValidator().isValidTag`\n * from `@backstage/catalog-model`, a development-only dependency.\n * Runtime consumers do not need a Backstage dependency for validation.\n *\n * @public\n */\nexport const CATALOG_TAG_PATTERN = /^[a-z0-9:+#]+(-[a-z0-9:+#]+)*$/;\n\n/**\n * Maximum tag length accepted by the Backstage catalog.\n *\n * @public\n */\nexport const MAX_TAG_LENGTH = 63;\n"],"names":[],"mappings":";;AAqCO,MAAM,mBAAA,GAAsB;AAO5B,MAAM,cAAA,GAAiB;;;;;"}
@@ -0,0 +1,5 @@
1
+ const CATALOG_TAG_PATTERN = /^[a-z0-9:+#]+(-[a-z0-9:+#]+)*$/;
2
+ const MAX_TAG_LENGTH = 63;
3
+
4
+ export { CATALOG_TAG_PATTERN, MAX_TAG_LENGTH };
5
+ //# sourceMappingURL=tag-constants.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-constants.esm.js","sources":["../src/tag-constants.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\n/**\n * Shared catalog tag validation constants used by both the D3\n * metadata normalizers and the D5 catalog helpers.\n *\n * @packageDocumentation\n */\n\n/**\n * Catalog tag validation pattern.\n *\n * Tags contain lowercase alphanumeric characters, colons, plus signs,\n * and hash characters, separated by single hyphens.\n *\n * @remarks\n * This pattern matches Backstage's default catalog tag validator.\n * Tests compare it and the length limit with `makeValidator().isValidTag`\n * from `@backstage/catalog-model`, a development-only dependency.\n * Runtime consumers do not need a Backstage dependency for validation.\n *\n * @public\n */\nexport const CATALOG_TAG_PATTERN = /^[a-z0-9:+#]+(-[a-z0-9:+#]+)*$/;\n\n/**\n * Maximum tag length accepted by the Backstage catalog.\n *\n * @public\n */\nexport const MAX_TAG_LENGTH = 63;\n"],"names":[],"mappings":"AAqCO,MAAM,mBAAA,GAAsB;AAO5B,MAAM,cAAA,GAAiB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/backstage-plugin-ai-skills-common",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared v1 skill record and snapshot contract for RHDH skill connectors and the common catalog provider",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.cjs.js",
@@ -34,6 +34,7 @@
34
34
  "postpack": "backstage-cli package postpack"
35
35
  },
36
36
  "devDependencies": {
37
+ "@backstage/catalog-model": "^1.10.1",
37
38
  "@backstage/cli": "^0.36.5"
38
39
  },
39
40
  "files": [