@intentius/chant-lexicon-gitlab 0.124.31 → 0.124.33

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.
Files changed (137) hide show
  1. package/dist/codegen/docs-cli.js +2 -0
  2. package/dist/codegen/docs.js +157 -0
  3. package/dist/codegen/fetch.js +93 -0
  4. package/dist/codegen/generate-cli.js +30 -0
  5. package/dist/codegen/generate-lexicon.js +36 -0
  6. package/dist/codegen/generate-typescript.js +105 -0
  7. package/dist/codegen/generate.js +125 -0
  8. package/dist/codegen/naming.js +48 -0
  9. package/dist/codegen/package.js +41 -0
  10. package/dist/codegen/parse.js +539 -0
  11. package/dist/codegen/patches.js +84 -0
  12. package/dist/codegen/typecheck.js +4 -0
  13. package/dist/components/ci-jobs.js +16 -0
  14. package/dist/components/generate-op-pipeline.d.ts.map +1 -1
  15. package/dist/components/generate-op-pipeline.js +294 -0
  16. package/dist/components/generate-op-waves-pipeline.js +125 -0
  17. package/dist/components/generate-pipeline.d.ts.map +1 -1
  18. package/dist/components/generate-pipeline.js +376 -0
  19. package/dist/composites/catalog.js +418 -0
  20. package/dist/composites/docker-build.js +38 -0
  21. package/dist/composites/index.js +5 -0
  22. package/dist/composites/mr-plan-report.js +42 -0
  23. package/dist/composites/node-pipeline.js +67 -0
  24. package/dist/composites/python-pipeline.js +45 -0
  25. package/dist/composites/review-app.js +32 -0
  26. package/dist/coverage.js +35 -0
  27. package/dist/generated/index.js +25 -0
  28. package/dist/generated/runtime.js +4 -0
  29. package/dist/graph-edges.js +202 -0
  30. package/dist/import/generator.js +196 -0
  31. package/dist/import/parser.js +111 -0
  32. package/dist/index.js +26 -0
  33. package/dist/integrity.json +3 -3
  34. package/dist/intrinsics.js +33 -0
  35. package/dist/lint/audit-catalog.js +53 -0
  36. package/dist/lint/audit-lineage.js +64 -0
  37. package/dist/lint/post-synth/index.js +84 -0
  38. package/dist/lint/post-synth/wgl010.js +34 -0
  39. package/dist/lint/post-synth/wgl011.js +48 -0
  40. package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
  41. package/dist/lint/post-synth/wgl012.js +68 -0
  42. package/dist/lint/post-synth/wgl013.js +57 -0
  43. package/dist/lint/post-synth/wgl014.js +43 -0
  44. package/dist/lint/post-synth/wgl015.js +70 -0
  45. package/dist/lint/post-synth/wgl016.js +70 -0
  46. package/dist/lint/post-synth/wgl017.js +38 -0
  47. package/dist/lint/post-synth/wgl018.js +34 -0
  48. package/dist/lint/post-synth/wgl019.js +43 -0
  49. package/dist/lint/post-synth/wgl020.js +47 -0
  50. package/dist/lint/post-synth/wgl021.js +52 -0
  51. package/dist/lint/post-synth/wgl022.js +38 -0
  52. package/dist/lint/post-synth/wgl023.js +44 -0
  53. package/dist/lint/post-synth/wgl024.js +41 -0
  54. package/dist/lint/post-synth/wgl025.js +42 -0
  55. package/dist/lint/post-synth/wgl026.js +58 -0
  56. package/dist/lint/post-synth/wgl027.js +49 -0
  57. package/dist/lint/post-synth/wgl028.js +58 -0
  58. package/dist/lint/post-synth/wgl029.js +55 -0
  59. package/dist/lint/post-synth/wgl030.js +43 -0
  60. package/dist/lint/post-synth/wgl031.js +33 -0
  61. package/dist/lint/post-synth/wgl032.js +64 -0
  62. package/dist/lint/post-synth/wgl033.js +32 -0
  63. package/dist/lint/post-synth/wgl034.js +37 -0
  64. package/dist/lint/post-synth/wgl035.js +40 -0
  65. package/dist/lint/post-synth/wgl036.js +38 -0
  66. package/dist/lint/post-synth/wgl037.js +41 -0
  67. package/dist/lint/post-synth/wgl038.js +43 -0
  68. package/dist/lint/post-synth/wgl039.js +34 -0
  69. package/dist/lint/post-synth/wgl040.js +35 -0
  70. package/dist/lint/post-synth/wgl041.js +53 -0
  71. package/dist/lint/post-synth/wgl042.js +41 -0
  72. package/dist/lint/post-synth/wgl043.js +39 -0
  73. package/dist/lint/post-synth/wgl044.js +35 -0
  74. package/dist/lint/post-synth/wgl045.js +54 -0
  75. package/dist/lint/post-synth/wgl046.js +40 -0
  76. package/dist/lint/post-synth/wgl047.js +34 -0
  77. package/dist/lint/post-synth/wgl048.js +32 -0
  78. package/dist/lint/post-synth/wgl049.js +53 -0
  79. package/dist/lint/post-synth/wgl050.js +42 -0
  80. package/dist/lint/post-synth/yaml-helpers.js +432 -0
  81. package/dist/lint/rules/artifact-no-expiry.js +53 -0
  82. package/dist/lint/rules/data/known-include-sources.js +44 -0
  83. package/dist/lint/rules/data/untrusted-variables.js +30 -0
  84. package/dist/lint/rules/deprecated-only-except.js +46 -0
  85. package/dist/lint/rules/import-source.js +61 -0
  86. package/dist/lint/rules/index.js +7 -0
  87. package/dist/lint/rules/missing-script.js +67 -0
  88. package/dist/lint/rules/missing-stage.js +60 -0
  89. package/dist/lsp/completions.d.ts.map +1 -1
  90. package/dist/lsp/completions.js +18 -0
  91. package/dist/lsp/hover.d.ts.map +1 -1
  92. package/dist/lsp/hover.js +33 -0
  93. package/dist/manifest.json +1 -1
  94. package/dist/mcp/context-tools.js +262 -0
  95. package/dist/migrate/from-github/actions/index.js +17 -0
  96. package/dist/migrate/from-github/actions/registry.js +61 -0
  97. package/dist/migrate/from-github/actions/tier-1.js +298 -0
  98. package/dist/migrate/from-github/actions/tier-2.js +241 -0
  99. package/dist/migrate/from-github/actions/tier-3.js +96 -0
  100. package/dist/migrate/from-github/composites/patterns.js +151 -0
  101. package/dist/migrate/from-github/composites/rewriter.js +22 -0
  102. package/dist/migrate/from-github/diagnostics.js +53 -0
  103. package/dist/migrate/from-github/emit-yaml.js +121 -0
  104. package/dist/migrate/from-github/expressions.js +275 -0
  105. package/dist/migrate/from-github/index.js +96 -0
  106. package/dist/migrate/from-github/provenance.js +35 -0
  107. package/dist/migrate/from-github/rules.js +74 -0
  108. package/dist/migrate/from-github/security.js +189 -0
  109. package/dist/migrate/from-github/stages.js +156 -0
  110. package/dist/migrate/from-github/transformer.js +633 -0
  111. package/dist/op/activities/gitlab.js +38 -0
  112. package/dist/op/activities/index.js +10 -0
  113. package/dist/op/builders.js +26 -0
  114. package/dist/package-cli.js +21 -0
  115. package/dist/plugin.js +536 -0
  116. package/dist/rules/wgl012.ts +2 -2
  117. package/dist/serializer.js +197 -0
  118. package/dist/testdata/create-fixture.js +40 -0
  119. package/dist/testdata/load-fixtures.js +20 -0
  120. package/dist/validate-cli.js +16 -0
  121. package/dist/validate.js +29 -0
  122. package/dist/variables.js +30 -0
  123. package/package.json +8 -8
  124. package/src/codegen/fetch.ts +2 -2
  125. package/src/codegen/package.ts +2 -2
  126. package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
  127. package/src/components/__fixtures__/pr-loop-record.gitlab.golden.yml +46 -0
  128. package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
  129. package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
  130. package/src/components/generate-op-pipeline.test.ts +10 -0
  131. package/src/components/generate-op-pipeline.ts +8 -1
  132. package/src/components/generate-pipeline.ts +71 -30
  133. package/src/components/generate-pr-pipeline.test.ts +61 -18
  134. package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
  135. package/src/lint/post-synth/wgl012.ts +2 -2
  136. package/src/lsp/completions.ts +2 -1
  137. package/src/lsp/hover.ts +2 -1
@@ -0,0 +1,539 @@
1
+ /**
2
+ * GitLab CI JSON Schema parser.
3
+ *
4
+ * Parses the single CI schema into multiple entity results — one per CI
5
+ * entity (Job, Pipeline, Artifacts, Cache, etc.). The schema is a single
6
+ * document unlike CloudFormation which has one schema per resource type.
7
+ */
8
+ import { extractConstraints as coreExtractConstraints, constraintsIsEmpty as coreConstraintsIsEmpty, primaryType, } from "@intentius/chant/codegen/json-schema";
9
+ export { coreConstraintsIsEmpty as constraintsIsEmpty };
10
+ // ── Entity extraction mapping ──────────────────────────────────────
11
+ /**
12
+ * Top-level entities (resources) to extract from the schema.
13
+ */
14
+ const RESOURCE_ENTITIES = [
15
+ {
16
+ typeName: "GitLab::CI::Job",
17
+ source: "#/definitions/job_template",
18
+ description: "A GitLab CI/CD job definition",
19
+ },
20
+ {
21
+ typeName: "GitLab::CI::Default",
22
+ source: "root:default",
23
+ description: "Default settings inherited by all jobs",
24
+ },
25
+ {
26
+ typeName: "GitLab::CI::Workflow",
27
+ source: "root:workflow",
28
+ description: "Pipeline-level workflow configuration",
29
+ },
30
+ {
31
+ // The top-level keys that are neither a job, `default` nor `workflow`
32
+ // (#3669). The serializer writes them first, so an explicit `stages`
33
+ // list can name a stage no declared job uses (one an included file's
34
+ // jobs run in) and set the order.
35
+ typeName: "GitLab::CI::Pipeline",
36
+ source: "root",
37
+ description: "Top-level pipeline keys: include, stages and variables",
38
+ pick: ["include", "stages", "variables"],
39
+ },
40
+ ];
41
+ /**
42
+ * Property types (nested) to extract from definitions.
43
+ */
44
+ const PROPERTY_ENTITIES = [
45
+ { typeName: "GitLab::CI::Artifacts", source: "#/definitions/artifacts", description: "Job artifact configuration" },
46
+ { typeName: "GitLab::CI::Cache", source: "#/definitions/cache_item", description: "Cache configuration" },
47
+ { typeName: "GitLab::CI::Image", source: "#/definitions/image", description: "Docker image for a job" },
48
+ { typeName: "GitLab::CI::Service", source: "#/definitions/services:item", description: "Docker service container for a job" },
49
+ { typeName: "GitLab::CI::Rule", source: "#/definitions/rules:item", description: "Conditional rule for job execution" },
50
+ { typeName: "GitLab::CI::Retry", source: "#/definitions/retry", description: "Job retry configuration" },
51
+ { typeName: "GitLab::CI::AllowFailure", source: "#/definitions/allow_failure", description: "Allow failure configuration" },
52
+ { typeName: "GitLab::CI::Parallel", source: "#/definitions/parallel", description: "Parallel job configuration" },
53
+ { typeName: "GitLab::CI::Include", source: "#/definitions/include_item", description: "Include configuration item" },
54
+ { typeName: "GitLab::CI::Release", source: "job_template:release", description: "Release configuration" },
55
+ { typeName: "GitLab::CI::Environment", source: "job_template:environment", description: "Deployment environment" },
56
+ { typeName: "GitLab::CI::Trigger", source: "job_template:trigger", description: "Trigger downstream pipeline" },
57
+ { typeName: "GitLab::CI::AutoCancel", source: "#/definitions/workflowAutoCancel", description: "Auto-cancel configuration" },
58
+ { typeName: "GitLab::CI::WorkflowRule", source: "root:workflow:rules:item", description: "Workflow rule with restricted when values" },
59
+ { typeName: "GitLab::CI::Need", source: "job_template:needs:item", description: "Job dependency specification" },
60
+ { typeName: "GitLab::CI::Inherit", source: "job_template:inherit", description: "Control default/variable inheritance" },
61
+ ];
62
+ // ── Parser ─────────────────────────────────────────────────────────
63
+ /**
64
+ * Parse the GitLab CI JSON Schema into multiple entity results.
65
+ * Returns one result per CI entity with its properties and nested types.
66
+ */
67
+ export function parseCISchema(data) {
68
+ const schema = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
69
+ const results = [];
70
+ // Extract resource entities
71
+ for (const entity of RESOURCE_ENTITIES) {
72
+ const result = extractResourceEntity(schema, entity);
73
+ if (result)
74
+ results.push(result);
75
+ }
76
+ // Extract property entities as pseudo-resources (each gets its own ParseResult)
77
+ for (const entity of PROPERTY_ENTITIES) {
78
+ const result = extractPropertyEntity(schema, entity);
79
+ if (result) {
80
+ result.isProperty = true;
81
+ results.push(result);
82
+ }
83
+ }
84
+ return results;
85
+ }
86
+ /**
87
+ * Extract a resource entity from the schema.
88
+ */
89
+ function extractResourceEntity(schema, entity) {
90
+ const def = resolveSource(schema, entity.source);
91
+ if (!def)
92
+ return null;
93
+ // Find the object variant if it's a oneOf/anyOf
94
+ const found = findObjectVariant(def, schema);
95
+ if (!found?.properties)
96
+ return null;
97
+ const pick = entity.pick;
98
+ const objectDef = pick
99
+ ? { ...found, properties: Object.fromEntries(Object.entries(found.properties).filter(([k]) => pick.includes(k))), required: undefined }
100
+ : found;
101
+ if (!objectDef.properties)
102
+ return null;
103
+ const requiredSet = new Set(objectDef.required ?? []);
104
+ const properties = parseProperties(objectDef.properties, requiredSet, schema);
105
+ const shortName = gitlabShortName(entity.typeName);
106
+ // Apply property type overrides for known entity references
107
+ const overrides = RESOURCE_PROPERTY_OVERRIDES[entity.typeName];
108
+ if (overrides) {
109
+ for (const prop of properties) {
110
+ if (overrides[prop.name]) {
111
+ prop.tsType = overrides[prop.name];
112
+ }
113
+ }
114
+ }
115
+ const comment = COMMENT_PROPERTIES[entity.typeName];
116
+ if (comment)
117
+ properties.push({ name: "comment", tsType: "string", required: false, description: comment, constraints: {} });
118
+ // Extract nested property types from definition properties
119
+ const { propertyTypes, enums } = extractNestedTypes(objectDef, shortName, schema);
120
+ const deprecatedProperties = mineDeprecatedProperties(properties);
121
+ return {
122
+ resource: {
123
+ typeName: entity.typeName,
124
+ description: entity.description ?? objectDef.description,
125
+ properties,
126
+ attributes: [], // CI entities have no read-only attributes
127
+ deprecatedProperties,
128
+ },
129
+ propertyTypes,
130
+ enums,
131
+ };
132
+ }
133
+ /**
134
+ * Extract a property entity — represented as a ParseResult with the entity
135
+ * as the "resource" (the pipeline treats all parsed results uniformly).
136
+ */
137
+ function extractPropertyEntity(schema, entity) {
138
+ const def = resolveSource(schema, entity.source);
139
+ if (!def)
140
+ return null;
141
+ const objectDef = findObjectVariant(def, schema);
142
+ if (!objectDef?.properties) {
143
+ // Some entities like Parallel might be simple types
144
+ // Create a minimal entry with no properties
145
+ return {
146
+ resource: {
147
+ typeName: entity.typeName,
148
+ description: entity.description ?? def.description,
149
+ properties: [],
150
+ attributes: [],
151
+ deprecatedProperties: [],
152
+ },
153
+ propertyTypes: [],
154
+ enums: [],
155
+ };
156
+ }
157
+ const requiredSet = new Set(objectDef.required ?? []);
158
+ const properties = parseProperties(objectDef.properties, requiredSet, schema);
159
+ const shortName = gitlabShortName(entity.typeName);
160
+ const { propertyTypes, enums } = extractNestedTypes(objectDef, shortName, schema);
161
+ const deprecatedProperties = mineDeprecatedProperties(properties);
162
+ return {
163
+ resource: {
164
+ typeName: entity.typeName,
165
+ description: entity.description ?? objectDef.description,
166
+ properties,
167
+ attributes: [],
168
+ deprecatedProperties,
169
+ },
170
+ propertyTypes,
171
+ enums,
172
+ };
173
+ }
174
+ /**
175
+ * Resources that carry a `comment`: not a GitLab key, but `#` lines the
176
+ * serializer writes above the entry (#3669, as #3667 for github).
177
+ */
178
+ const COMMENT_PROPERTIES = {
179
+ "GitLab::CI::Pipeline": "YAML comment lines written at the top of the file. Not a GitLab key; each line of the string becomes a `#` line",
180
+ "GitLab::CI::Job": "YAML comment lines written above the job's key. Not a GitLab key; each line of the string becomes a `#` line",
181
+ };
182
+ // ── Helpers ────────────────────────────────────────────────────────
183
+ /**
184
+ * Resolve a source path to a schema definition.
185
+ */
186
+ function resolveSource(schema, source) {
187
+ if (source === "root")
188
+ return { properties: schema.properties };
189
+ if (source.startsWith("#/definitions/")) {
190
+ // Array item extraction: "#/definitions/foo:item" → foo.items object variant
191
+ if (source.includes(":item")) {
192
+ const defName = source.slice("#/definitions/".length).replace(":item", "");
193
+ const arrayDef = schema.definitions?.[defName];
194
+ if (!arrayDef?.items)
195
+ return null;
196
+ return findObjectVariant(arrayDef.items, schema);
197
+ }
198
+ const defName = source.slice("#/definitions/".length);
199
+ return schema.definitions?.[defName] ?? null;
200
+ }
201
+ // Multi-segment path under root: root:prop:subprop:...:item
202
+ if (source.startsWith("root:")) {
203
+ const segments = source.slice("root:".length).split(":");
204
+ let current = { properties: schema.properties };
205
+ for (const seg of segments) {
206
+ if (!current)
207
+ return null;
208
+ if (seg === "item") {
209
+ if (!current.items)
210
+ return null;
211
+ return findObjectVariant(current.items, schema);
212
+ }
213
+ if (!current.properties)
214
+ return null;
215
+ const prop = current.properties[seg];
216
+ if (!prop)
217
+ return null;
218
+ current = prop.$ref ? resolveRef(prop.$ref, schema) : prop;
219
+ }
220
+ return current;
221
+ }
222
+ // Multi-segment path under job_template: job_template:prop:...:item
223
+ if (source.startsWith("job_template:")) {
224
+ const segments = source.slice("job_template:".length).split(":");
225
+ let current = schema.definitions?.job_template ?? null;
226
+ if (!current)
227
+ return null;
228
+ for (const seg of segments) {
229
+ if (!current)
230
+ return null;
231
+ if (seg === "item") {
232
+ if (!current.items)
233
+ return null;
234
+ return findObjectVariant(current.items, schema);
235
+ }
236
+ if (!current.properties)
237
+ return null;
238
+ const prop = current.properties[seg];
239
+ if (!prop)
240
+ return null;
241
+ current = prop.$ref ? resolveRef(prop.$ref, schema) : prop;
242
+ }
243
+ return current;
244
+ }
245
+ return null;
246
+ }
247
+ /**
248
+ * Resolve a $ref string to a schema definition.
249
+ */
250
+ function resolveRef(ref, schema) {
251
+ const prefix = "#/definitions/";
252
+ if (!ref.startsWith(prefix))
253
+ return null;
254
+ const defName = ref.slice(prefix.length);
255
+ return schema.definitions?.[defName] ?? null;
256
+ }
257
+ /**
258
+ * Find the object variant from a oneOf/anyOf union, or return the
259
+ * definition itself if it already has properties.
260
+ *
261
+ * When multiple object variants exist, merges their properties:
262
+ * - Picks the variant with the most properties as the base
263
+ * - Adds unique properties from other variants
264
+ * - Union-merges overlapping property types (e.g. exit_codes: integer | integer[])
265
+ * - Required = intersection of all variants' required sets
266
+ */
267
+ function findObjectVariant(def, schema) {
268
+ if (def.properties)
269
+ return def;
270
+ const variants = def.oneOf ?? def.anyOf;
271
+ if (!variants)
272
+ return null;
273
+ // Collect all object variants, resolving $refs
274
+ const objectVariants = [];
275
+ for (const v of variants) {
276
+ let resolved = v;
277
+ if (v.$ref && schema) {
278
+ const r = resolveRef(v.$ref, schema);
279
+ if (r)
280
+ resolved = r;
281
+ else
282
+ continue;
283
+ }
284
+ else if (v.$ref) {
285
+ continue;
286
+ }
287
+ if (resolved.properties) {
288
+ objectVariants.push(resolved);
289
+ }
290
+ }
291
+ if (objectVariants.length === 0)
292
+ return null;
293
+ if (objectVariants.length === 1)
294
+ return objectVariants[0];
295
+ // Find the variant with the most properties as the base
296
+ let best = objectVariants[0];
297
+ let bestCount = Object.keys(best.properties).length;
298
+ for (let i = 1; i < objectVariants.length; i++) {
299
+ const count = Object.keys(objectVariants[i].properties).length;
300
+ if (count > bestCount) {
301
+ best = objectVariants[i];
302
+ bestCount = count;
303
+ }
304
+ }
305
+ // Merge properties from other object variants into the base
306
+ const mergedProperties = { ...best.properties };
307
+ const allRequiredSets = objectVariants.map((v) => new Set(v.required ?? []));
308
+ for (const variant of objectVariants) {
309
+ if (variant === best)
310
+ continue;
311
+ for (const [propName, propDef] of Object.entries(variant.properties)) {
312
+ if (propName in mergedProperties) {
313
+ // Property exists in base — if types differ, create a merged oneOf
314
+ const existing = mergedProperties[propName];
315
+ if (JSON.stringify(existing) !== JSON.stringify(propDef)) {
316
+ mergedProperties[propName] = {
317
+ oneOf: [existing, propDef],
318
+ description: existing.description ?? propDef.description,
319
+ };
320
+ }
321
+ }
322
+ else {
323
+ // New property from another variant
324
+ mergedProperties[propName] = propDef;
325
+ }
326
+ }
327
+ }
328
+ // Required = intersection across all object variants
329
+ const requiredIntersection = [...allRequiredSets[0]].filter((r) => allRequiredSets.every((s) => s.has(r)));
330
+ return {
331
+ ...best,
332
+ properties: mergedProperties,
333
+ required: requiredIntersection.length > 0 ? requiredIntersection : undefined,
334
+ };
335
+ }
336
+ /**
337
+ * Parse properties from a schema definition into ParsedProperty[].
338
+ */
339
+ function parseProperties(properties, requiredSet, schema) {
340
+ const result = [];
341
+ for (const [name, prop] of Object.entries(properties)) {
342
+ // Skip internal/hidden properties
343
+ if (name === "!reference")
344
+ continue;
345
+ const tsType = resolvePropertyType(prop, schema);
346
+ result.push({
347
+ name,
348
+ tsType,
349
+ required: requiredSet.has(name),
350
+ description: prop.description,
351
+ enum: prop.enum,
352
+ constraints: coreExtractConstraints(prop),
353
+ });
354
+ }
355
+ return result;
356
+ }
357
+ /**
358
+ * Resolve a schema property to its TypeScript type string.
359
+ */
360
+ function resolvePropertyType(prop, schema) {
361
+ if (!prop)
362
+ return "any";
363
+ // Handle $ref
364
+ if (prop.$ref) {
365
+ const ref = prop.$ref;
366
+ const prefix = "#/definitions/";
367
+ if (ref.startsWith(prefix)) {
368
+ const defName = ref.slice(prefix.length);
369
+ const def = schema.definitions?.[defName];
370
+ if (def) {
371
+ // Check for known entity types
372
+ const entityType = definitionToTsType(defName);
373
+ if (entityType)
374
+ return entityType;
375
+ // Enum
376
+ if (def.enum && def.enum.length > 0 && !def.properties) {
377
+ return def.enum.map((v) => JSON.stringify(v)).join(" | ");
378
+ }
379
+ // Primitive type
380
+ if (def.type && !def.properties) {
381
+ return jsonTypeToTs(primaryType(def.type));
382
+ }
383
+ // Object with properties
384
+ if (def.properties)
385
+ return "Record<string, any>";
386
+ }
387
+ }
388
+ return "any";
389
+ }
390
+ // Inline enum
391
+ if (prop.enum && prop.enum.length > 0) {
392
+ return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
393
+ }
394
+ // Handle oneOf/anyOf
395
+ if (prop.oneOf || prop.anyOf) {
396
+ const variants = prop.oneOf ?? prop.anyOf ?? [];
397
+ const types = new Set();
398
+ for (const v of variants) {
399
+ types.add(resolvePropertyType(v, schema));
400
+ }
401
+ // Simplify if all variants resolve to the same type
402
+ const uniqueTypes = [...types].filter((t) => t !== "any");
403
+ if (uniqueTypes.length === 0)
404
+ return "any";
405
+ if (uniqueTypes.length === 1)
406
+ return uniqueTypes[0];
407
+ return uniqueTypes.join(" | ");
408
+ }
409
+ const pt = primaryType(prop.type);
410
+ switch (pt) {
411
+ case "string":
412
+ return "string";
413
+ case "integer":
414
+ case "number":
415
+ return "number";
416
+ case "boolean":
417
+ return "boolean";
418
+ case "array":
419
+ if (prop.items) {
420
+ const itemType = resolvePropertyType(prop.items, schema);
421
+ // Wrap union types in parens so [] applies to the whole union
422
+ if (itemType.includes(" | ")) {
423
+ return `(${itemType})[]`;
424
+ }
425
+ return `${itemType}[]`;
426
+ }
427
+ return "any[]";
428
+ case "object":
429
+ return "Record<string, any>";
430
+ default:
431
+ return "any";
432
+ }
433
+ }
434
+ /**
435
+ * Property type overrides for resource entities.
436
+ * Maps entity typeName → property name → desired TS type.
437
+ * Applied after parseProperties to replace generic types with entity references.
438
+ */
439
+ const RESOURCE_PROPERTY_OVERRIDES = {
440
+ "GitLab::CI::Job": {
441
+ environment: "Environment | string",
442
+ trigger: "Trigger | string",
443
+ release: "Release",
444
+ // GitLab takes the plain forms too: `image: node:22`, `needs: [build]`.
445
+ image: "Image | string",
446
+ needs: "(Need | string)[]",
447
+ inherit: "Inherit",
448
+ },
449
+ "GitLab::CI::Default": {
450
+ image: "Image | string",
451
+ },
452
+ "GitLab::CI::Pipeline": {
453
+ include: "Include | string | (Include | string)[]",
454
+ stages: "string[]",
455
+ },
456
+ "GitLab::CI::Workflow": {
457
+ rules: "WorkflowRule[]",
458
+ },
459
+ };
460
+ /**
461
+ * Map well-known definition names to their TypeScript entity types.
462
+ */
463
+ function definitionToTsType(defName) {
464
+ const map = {
465
+ image: "Image",
466
+ services: "Service[]",
467
+ artifacts: "Artifacts",
468
+ cache: "Cache | Cache[]",
469
+ cache_item: "Cache",
470
+ rules: "Rule[]",
471
+ retry: "Retry | number",
472
+ allow_failure: "AllowFailure | boolean",
473
+ parallel: "Parallel | number",
474
+ include_item: "Include",
475
+ before_script: "string | string[]",
476
+ after_script: "string | string[]",
477
+ script: "string | string[]",
478
+ optional_script: "string | string[]",
479
+ globalVariables: "Record<string, any>",
480
+ jobVariables: "Record<string, any>",
481
+ rulesVariables: "Record<string, any>",
482
+ when: '"on_success" | "on_failure" | "always" | "never" | "manual" | "delayed"',
483
+ workflowAutoCancel: "AutoCancel",
484
+ id_tokens: "Record<string, { aud: string | string[] }>",
485
+ secrets: "Record<string, any>",
486
+ timeout: "string",
487
+ start_in: "string",
488
+ };
489
+ return map[defName] ?? null;
490
+ }
491
+ function jsonTypeToTs(type) {
492
+ switch (type) {
493
+ case "string": return "string";
494
+ case "integer":
495
+ case "number": return "number";
496
+ case "boolean": return "boolean";
497
+ case "array": return "any[]";
498
+ case "object": return "Record<string, any>";
499
+ default: return "any";
500
+ }
501
+ }
502
+ /**
503
+ * Extract nested property types and enums from a definition.
504
+ * For now, we keep this minimal — the main types are extracted as
505
+ * top-level entities via PROPERTY_ENTITIES.
506
+ */
507
+ function extractNestedTypes(_def, _shortName, _schema) {
508
+ // GitLab CI entities don't have deeply nested property types like CFN.
509
+ // The main nested types (Artifacts, Cache, etc.) are extracted as
510
+ // top-level property entities instead.
511
+ return { propertyTypes: [], enums: [] };
512
+ }
513
+ const DEPRECATION_RE = /\bDeprecated\b|\bdeprecated\b|\blegacy\b|no longer (available|recommended|used|supported)|is not recommended/i;
514
+ /**
515
+ * Mine property descriptions for deprecation signals.
516
+ */
517
+ function mineDeprecatedProperties(properties) {
518
+ const deprecated = [];
519
+ for (const prop of properties) {
520
+ if (prop.description && DEPRECATION_RE.test(prop.description)) {
521
+ deprecated.push(prop.name);
522
+ }
523
+ }
524
+ return deprecated;
525
+ }
526
+ /**
527
+ * Extract short name: "GitLab::CI::Job" → "Job"
528
+ */
529
+ export function gitlabShortName(typeName) {
530
+ const parts = typeName.split("::");
531
+ return parts[parts.length - 1];
532
+ }
533
+ /**
534
+ * Extract service name: "GitLab::CI::Job" → "CI"
535
+ */
536
+ export function gitlabServiceName(typeName) {
537
+ const parts = typeName.split("::");
538
+ return parts.length >= 2 ? parts[1] : "CI";
539
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Schema augmentation patches for GitLab CI JSON Schema.
3
+ *
4
+ * The official GitLab CI schema sometimes has gaps, missing properties,
5
+ * or incorrect types. Patches are applied after fetch but before parse
6
+ * to fix these issues.
7
+ */
8
+ /**
9
+ * Registry of known schema patches.
10
+ */
11
+ export const schemaPatches = [
12
+ {
13
+ description: "Add 'pages' property type to job_template if missing",
14
+ path: "definitions.job_template",
15
+ apply(def) {
16
+ const props = def.properties;
17
+ if (props && !props.pages) {
18
+ props.pages = {
19
+ description: "GitLab Pages configuration for publishing static sites.",
20
+ oneOf: [
21
+ { type: "object", properties: { publish: { type: "string" } } },
22
+ { type: "boolean" },
23
+ ],
24
+ };
25
+ }
26
+ },
27
+ },
28
+ {
29
+ description: "Add 'manual_confirmation' property to job_template if missing",
30
+ path: "definitions.job_template",
31
+ apply(def) {
32
+ const props = def.properties;
33
+ if (props && !props.manual_confirmation) {
34
+ props.manual_confirmation = {
35
+ type: "string",
36
+ description: "Confirmation message displayed when a manual job is triggered.",
37
+ };
38
+ }
39
+ },
40
+ },
41
+ {
42
+ description: "Add 'inputs' property to job_template if missing",
43
+ path: "definitions.job_template",
44
+ apply(def) {
45
+ const props = def.properties;
46
+ if (props && !props.inputs) {
47
+ props.inputs = {
48
+ type: "object",
49
+ description: "Input parameters for CI/CD component jobs.",
50
+ additionalProperties: true,
51
+ };
52
+ }
53
+ },
54
+ },
55
+ ];
56
+ /**
57
+ * Apply all registered patches to a parsed schema.
58
+ * Modifies the schema in place.
59
+ */
60
+ export function applyPatches(schema) {
61
+ const applied = [];
62
+ const skipped = [];
63
+ for (const patch of schemaPatches) {
64
+ const segments = patch.path.split(".");
65
+ let target = schema;
66
+ for (const segment of segments) {
67
+ if (target && typeof target === "object" && segment in target) {
68
+ target = target[segment];
69
+ }
70
+ else {
71
+ target = undefined;
72
+ break;
73
+ }
74
+ }
75
+ if (target) {
76
+ patch.apply(target);
77
+ applied.push(patch.description);
78
+ }
79
+ else {
80
+ skipped.push(`${patch.description} (path "${patch.path}" not found)`);
81
+ }
82
+ }
83
+ return { applied, skipped };
84
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export from core — typecheck is lexicon-agnostic.
3
+ */
4
+ export { typecheckDTS } from "@intentius/chant/codegen/typecheck";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The GitLab CI pieces the three generators share (#3765): the Op pipeline
3
+ * (`./generate-op-pipeline.ts`), the Op waves pipeline
4
+ * (`./generate-op-waves-pipeline.ts`) and the component pipeline
5
+ * (`./generate-pipeline.ts`). The `rules:` expressions, `id_tokens:`, setup
6
+ * and environment checks are core's, in `@intentius/chant/ci-jobs`.
7
+ */
8
+ import { emitYAMLEntry } from "@intentius/chant/yaml";
9
+ /**
10
+ * A `.gitlab-ci.yml` document: the comment header, when there is one, then
11
+ * each top-level entry in the order given, a blank line between each.
12
+ */
13
+ export function emitGitlabFile(entries, header = []) {
14
+ const body = entries.map(([key, value]) => emitYAMLEntry(key, value)).join("\n\n") + "\n";
15
+ return header.length > 0 ? header.join("\n") + "\n\n" + body : body;
16
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AAeH,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAInD,gBAAgB,IAAI,sBAAsB,EAE1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAqHhE;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAsGxB"}
1
+ {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AAeH,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAInD,gBAAgB,IAAI,sBAAsB,EAE1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AA4HhE;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAsGxB"}