@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.
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +157 -0
- package/dist/codegen/fetch.js +93 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +36 -0
- package/dist/codegen/generate-typescript.js +105 -0
- package/dist/codegen/generate.js +125 -0
- package/dist/codegen/naming.js +48 -0
- package/dist/codegen/package.js +41 -0
- package/dist/codegen/parse.js +539 -0
- package/dist/codegen/patches.js +84 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/components/ci-jobs.js +16 -0
- package/dist/components/generate-op-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-pipeline.js +294 -0
- package/dist/components/generate-op-waves-pipeline.js +125 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +376 -0
- package/dist/composites/catalog.js +418 -0
- package/dist/composites/docker-build.js +38 -0
- package/dist/composites/index.js +5 -0
- package/dist/composites/mr-plan-report.js +42 -0
- package/dist/composites/node-pipeline.js +67 -0
- package/dist/composites/python-pipeline.js +45 -0
- package/dist/composites/review-app.js +32 -0
- package/dist/coverage.js +35 -0
- package/dist/generated/index.js +25 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/graph-edges.js +202 -0
- package/dist/import/generator.js +196 -0
- package/dist/import/parser.js +111 -0
- package/dist/index.js +26 -0
- package/dist/integrity.json +3 -3
- package/dist/intrinsics.js +33 -0
- package/dist/lint/audit-catalog.js +53 -0
- package/dist/lint/audit-lineage.js +64 -0
- package/dist/lint/post-synth/index.js +84 -0
- package/dist/lint/post-synth/wgl010.js +34 -0
- package/dist/lint/post-synth/wgl011.js +48 -0
- package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl012.js +68 -0
- package/dist/lint/post-synth/wgl013.js +57 -0
- package/dist/lint/post-synth/wgl014.js +43 -0
- package/dist/lint/post-synth/wgl015.js +70 -0
- package/dist/lint/post-synth/wgl016.js +70 -0
- package/dist/lint/post-synth/wgl017.js +38 -0
- package/dist/lint/post-synth/wgl018.js +34 -0
- package/dist/lint/post-synth/wgl019.js +43 -0
- package/dist/lint/post-synth/wgl020.js +47 -0
- package/dist/lint/post-synth/wgl021.js +52 -0
- package/dist/lint/post-synth/wgl022.js +38 -0
- package/dist/lint/post-synth/wgl023.js +44 -0
- package/dist/lint/post-synth/wgl024.js +41 -0
- package/dist/lint/post-synth/wgl025.js +42 -0
- package/dist/lint/post-synth/wgl026.js +58 -0
- package/dist/lint/post-synth/wgl027.js +49 -0
- package/dist/lint/post-synth/wgl028.js +58 -0
- package/dist/lint/post-synth/wgl029.js +55 -0
- package/dist/lint/post-synth/wgl030.js +43 -0
- package/dist/lint/post-synth/wgl031.js +33 -0
- package/dist/lint/post-synth/wgl032.js +64 -0
- package/dist/lint/post-synth/wgl033.js +32 -0
- package/dist/lint/post-synth/wgl034.js +37 -0
- package/dist/lint/post-synth/wgl035.js +40 -0
- package/dist/lint/post-synth/wgl036.js +38 -0
- package/dist/lint/post-synth/wgl037.js +41 -0
- package/dist/lint/post-synth/wgl038.js +43 -0
- package/dist/lint/post-synth/wgl039.js +34 -0
- package/dist/lint/post-synth/wgl040.js +35 -0
- package/dist/lint/post-synth/wgl041.js +53 -0
- package/dist/lint/post-synth/wgl042.js +41 -0
- package/dist/lint/post-synth/wgl043.js +39 -0
- package/dist/lint/post-synth/wgl044.js +35 -0
- package/dist/lint/post-synth/wgl045.js +54 -0
- package/dist/lint/post-synth/wgl046.js +40 -0
- package/dist/lint/post-synth/wgl047.js +34 -0
- package/dist/lint/post-synth/wgl048.js +32 -0
- package/dist/lint/post-synth/wgl049.js +53 -0
- package/dist/lint/post-synth/wgl050.js +42 -0
- package/dist/lint/post-synth/yaml-helpers.js +432 -0
- package/dist/lint/rules/artifact-no-expiry.js +53 -0
- package/dist/lint/rules/data/known-include-sources.js +44 -0
- package/dist/lint/rules/data/untrusted-variables.js +30 -0
- package/dist/lint/rules/deprecated-only-except.js +46 -0
- package/dist/lint/rules/import-source.js +61 -0
- package/dist/lint/rules/index.js +7 -0
- package/dist/lint/rules/missing-script.js +67 -0
- package/dist/lint/rules/missing-stage.js +60 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +33 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +262 -0
- package/dist/migrate/from-github/actions/index.js +17 -0
- package/dist/migrate/from-github/actions/registry.js +61 -0
- package/dist/migrate/from-github/actions/tier-1.js +298 -0
- package/dist/migrate/from-github/actions/tier-2.js +241 -0
- package/dist/migrate/from-github/actions/tier-3.js +96 -0
- package/dist/migrate/from-github/composites/patterns.js +151 -0
- package/dist/migrate/from-github/composites/rewriter.js +22 -0
- package/dist/migrate/from-github/diagnostics.js +53 -0
- package/dist/migrate/from-github/emit-yaml.js +121 -0
- package/dist/migrate/from-github/expressions.js +275 -0
- package/dist/migrate/from-github/index.js +96 -0
- package/dist/migrate/from-github/provenance.js +35 -0
- package/dist/migrate/from-github/rules.js +74 -0
- package/dist/migrate/from-github/security.js +189 -0
- package/dist/migrate/from-github/stages.js +156 -0
- package/dist/migrate/from-github/transformer.js +633 -0
- package/dist/op/activities/gitlab.js +38 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +26 -0
- package/dist/package-cli.js +21 -0
- package/dist/plugin.js +536 -0
- package/dist/rules/wgl012.ts +2 -2
- package/dist/serializer.js +197 -0
- package/dist/testdata/create-fixture.js +40 -0
- package/dist/testdata/load-fixtures.js +20 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +29 -0
- package/dist/variables.js +30 -0
- package/package.json +8 -8
- package/src/codegen/fetch.ts +2 -2
- package/src/codegen/package.ts +2 -2
- package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
- package/src/components/__fixtures__/pr-loop-record.gitlab.golden.yml +46 -0
- package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
- package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
- package/src/components/generate-op-pipeline.test.ts +10 -0
- package/src/components/generate-op-pipeline.ts +8 -1
- package/src/components/generate-pipeline.ts +71 -30
- package/src/components/generate-pr-pipeline.test.ts +61 -18
- package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
- package/src/lint/post-synth/wgl012.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- 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,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;
|
|
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"}
|