@intentius/chant-lexicon-gitlab 0.0.8 → 0.0.10
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/integrity.json +10 -6
- package/dist/manifest.json +1 -1
- package/dist/meta.json +186 -8
- package/dist/rules/wgl012.ts +86 -0
- package/dist/rules/wgl013.ts +62 -0
- package/dist/rules/wgl014.ts +51 -0
- package/dist/rules/wgl015.ts +85 -0
- package/dist/rules/yaml-helpers.ts +65 -3
- package/dist/skills/chant-gitlab.md +467 -24
- package/dist/types/index.d.ts +55 -16
- package/package.json +2 -2
- package/src/codegen/__snapshots__/snapshot.test.ts.snap +58 -0
- package/src/codegen/docs.ts +32 -9
- package/src/codegen/generate-lexicon.ts +6 -1
- package/src/codegen/generate.ts +45 -50
- package/src/codegen/naming.ts +3 -0
- package/src/codegen/parse.test.ts +154 -4
- package/src/codegen/parse.ts +161 -49
- package/src/codegen/snapshot.test.ts +7 -5
- package/src/composites/composites.test.ts +452 -0
- package/src/composites/docker-build.ts +81 -0
- package/src/composites/index.ts +8 -0
- package/src/composites/node-pipeline.ts +104 -0
- package/src/composites/python-pipeline.ts +75 -0
- package/src/composites/review-app.ts +63 -0
- package/src/generated/index.d.ts +55 -16
- package/src/generated/index.ts +3 -0
- package/src/generated/lexicon-gitlab.json +186 -8
- package/src/import/generator.ts +3 -2
- package/src/index.ts +4 -0
- package/src/lint/post-synth/wgl012.test.ts +131 -0
- package/src/lint/post-synth/wgl012.ts +86 -0
- package/src/lint/post-synth/wgl013.test.ts +164 -0
- package/src/lint/post-synth/wgl013.ts +62 -0
- package/src/lint/post-synth/wgl014.test.ts +97 -0
- package/src/lint/post-synth/wgl014.ts +51 -0
- package/src/lint/post-synth/wgl015.test.ts +139 -0
- package/src/lint/post-synth/wgl015.ts +85 -0
- package/src/lint/post-synth/yaml-helpers.ts +65 -3
- package/src/plugin.test.ts +39 -13
- package/src/plugin.ts +636 -40
- package/src/serializer.test.ts +140 -0
- package/src/serializer.ts +63 -5
- package/src/validate.ts +1 -0
- package/src/variables.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": ["src/", "dist/"],
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"prepack": "bun run bundle && bun run validate"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@intentius/chant": "0.0.
|
|
24
|
+
"@intentius/chant": "0.0.9"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"typescript": "^5.9.3"
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`generated lexicon-gitlab.json entries match snapshot 1`] = `
|
|
4
4
|
{
|
|
5
|
+
"constraints": {
|
|
6
|
+
"coverage": {
|
|
7
|
+
"format": "regex",
|
|
8
|
+
"pattern": "^/.+/$",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
5
11
|
"kind": "resource",
|
|
6
12
|
"lexicon": "gitlab",
|
|
7
13
|
"resourceType": "GitLab::CI::Job",
|
|
@@ -10,6 +16,30 @@ exports[`generated lexicon-gitlab.json entries match snapshot 1`] = `
|
|
|
10
16
|
|
|
11
17
|
exports[`generated lexicon-gitlab.json entries match snapshot 2`] = `
|
|
12
18
|
{
|
|
19
|
+
"constraints": {
|
|
20
|
+
"access": {
|
|
21
|
+
"default": "all",
|
|
22
|
+
"enum": [
|
|
23
|
+
"none",
|
|
24
|
+
"developer",
|
|
25
|
+
"all",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
"expire_in": {
|
|
29
|
+
"default": "30 days",
|
|
30
|
+
},
|
|
31
|
+
"untracked": {
|
|
32
|
+
"default": false,
|
|
33
|
+
},
|
|
34
|
+
"when": {
|
|
35
|
+
"default": "on_success",
|
|
36
|
+
"enum": [
|
|
37
|
+
"on_success",
|
|
38
|
+
"on_failure",
|
|
39
|
+
"always",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
13
43
|
"kind": "property",
|
|
14
44
|
"lexicon": "gitlab",
|
|
15
45
|
"resourceType": "GitLab::CI::Artifacts",
|
|
@@ -18,6 +48,26 @@ exports[`generated lexicon-gitlab.json entries match snapshot 2`] = `
|
|
|
18
48
|
|
|
19
49
|
exports[`generated lexicon-gitlab.json entries match snapshot 3`] = `
|
|
20
50
|
{
|
|
51
|
+
"constraints": {
|
|
52
|
+
"policy": {
|
|
53
|
+
"default": "pull-push",
|
|
54
|
+
"pattern": "pull-push|pull|push|\\$\\w{1,255}",
|
|
55
|
+
},
|
|
56
|
+
"unprotect": {
|
|
57
|
+
"default": false,
|
|
58
|
+
},
|
|
59
|
+
"untracked": {
|
|
60
|
+
"default": false,
|
|
61
|
+
},
|
|
62
|
+
"when": {
|
|
63
|
+
"default": "on_success",
|
|
64
|
+
"enum": [
|
|
65
|
+
"on_success",
|
|
66
|
+
"on_failure",
|
|
67
|
+
"always",
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
21
71
|
"kind": "property",
|
|
22
72
|
"lexicon": "gitlab",
|
|
23
73
|
"resourceType": "GitLab::CI::Cache",
|
|
@@ -26,6 +76,14 @@ exports[`generated lexicon-gitlab.json entries match snapshot 3`] = `
|
|
|
26
76
|
|
|
27
77
|
exports[`generated lexicon-gitlab.json entries match snapshot 4`] = `
|
|
28
78
|
{
|
|
79
|
+
"constraints": {
|
|
80
|
+
"name": {
|
|
81
|
+
"minLength": 1,
|
|
82
|
+
},
|
|
83
|
+
"pull_policy": {
|
|
84
|
+
"default": "always",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
29
87
|
"kind": "property",
|
|
30
88
|
"lexicon": "gitlab",
|
|
31
89
|
"resourceType": "GitLab::CI::Image",
|
package/src/codegen/docs.ts
CHANGED
|
@@ -161,7 +161,7 @@ The lexicon provides 3 resource types and 13 property types:
|
|
|
161
161
|
|
|
162
162
|
Extract reusable objects into a shared config file and import them across your pipeline files:
|
|
163
163
|
|
|
164
|
-
{{file:docs-snippets/src/pipeline-
|
|
164
|
+
{{file:docs-snippets/src/pipeline-shared-config.ts}}
|
|
165
165
|
|
|
166
166
|
## Jobs
|
|
167
167
|
|
|
@@ -338,9 +338,9 @@ lint:
|
|
|
338
338
|
- npm run lint
|
|
339
339
|
\`\`\`
|
|
340
340
|
|
|
341
|
-
### When to use \`reference()\` vs
|
|
341
|
+
### When to use \`reference()\` vs direct imports
|
|
342
342
|
|
|
343
|
-
{{file:docs-snippets/src/reference-vs-
|
|
343
|
+
{{file:docs-snippets/src/reference-vs-import.ts}}
|
|
344
344
|
`,
|
|
345
345
|
},
|
|
346
346
|
{
|
|
@@ -403,6 +403,30 @@ Flags jobs where all \`rules:\` entries have \`when: "never"\`, making the job u
|
|
|
403
403
|
|
|
404
404
|
{{file:docs-snippets/src/lint-wgl011.ts}}
|
|
405
405
|
|
|
406
|
+
### WGL012 — Deprecated property usage
|
|
407
|
+
|
|
408
|
+
**Severity:** warning
|
|
409
|
+
|
|
410
|
+
Flags properties marked as deprecated in the GitLab CI schema. Deprecation signals are mined from property descriptions (keywords like "deprecated", "legacy", "no longer available"). Using deprecated properties may cause unexpected behavior in future GitLab versions.
|
|
411
|
+
|
|
412
|
+
### WGL013 — Invalid \`needs:\` target
|
|
413
|
+
|
|
414
|
+
**Severity:** error
|
|
415
|
+
|
|
416
|
+
Flags jobs whose \`needs:\` entries reference a job not defined in the pipeline, or reference themselves. Both cause GitLab pipeline validation failures. When \`include:\` is present, the check is skipped since needed jobs may come from included files.
|
|
417
|
+
|
|
418
|
+
### WGL014 — Invalid \`extends:\` target
|
|
419
|
+
|
|
420
|
+
**Severity:** error
|
|
421
|
+
|
|
422
|
+
Flags jobs whose \`extends:\` references a template or hidden job not defined in the pipeline. GitLab rejects pipelines with unresolved extends references. When \`include:\` is present, the check is skipped since templates may come from included files.
|
|
423
|
+
|
|
424
|
+
### WGL015 — Circular \`needs:\` chain
|
|
425
|
+
|
|
426
|
+
**Severity:** error
|
|
427
|
+
|
|
428
|
+
Detects cycles in the \`needs:\` dependency graph. If job A needs B and B needs A (directly or transitively), GitLab rejects the pipeline. Reports the full cycle chain in the diagnostic message.
|
|
429
|
+
|
|
406
430
|
## Running lint
|
|
407
431
|
|
|
408
432
|
\`\`\`bash
|
|
@@ -531,11 +555,10 @@ deploy:
|
|
|
531
555
|
|
|
532
556
|
**Patterns demonstrated:**
|
|
533
557
|
|
|
534
|
-
1. **
|
|
535
|
-
2. **
|
|
536
|
-
3. **
|
|
537
|
-
4. **
|
|
538
|
-
5. **JUnit reports** — test artifacts include JUnit XML for GitLab MR display
|
|
558
|
+
1. **Shared config** — reusable images, caches, artifacts, and rules extracted into \`config.ts\`
|
|
559
|
+
2. **Conditional execution** — merge request and branch rules control when jobs run
|
|
560
|
+
3. **Manual deployment** — deploy requires manual trigger on the default branch
|
|
561
|
+
4. **JUnit reports** — test artifacts include JUnit XML for GitLab MR display
|
|
539
562
|
`,
|
|
540
563
|
},
|
|
541
564
|
{
|
|
@@ -571,7 +594,7 @@ The \`chant-gitlab\` skill covers the full deployment lifecycle:
|
|
|
571
594
|
- **Status** — GitLab UI or pipelines API
|
|
572
595
|
- **Retry** — retry failed jobs via UI or API
|
|
573
596
|
- **Cancel** — cancel running pipelines via API
|
|
574
|
-
- **Troubleshooting** — job logs, lint rule codes (WGL001–WGL004), post-synth checks (WGL010
|
|
597
|
+
- **Troubleshooting** — job logs, lint rule codes (WGL001–WGL004), post-synth checks (WGL010–WGL015)
|
|
575
598
|
|
|
576
599
|
The skill is invocable as a slash command: \`/chant-gitlab\`
|
|
577
600
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* for all GitLab CI entities.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
|
|
6
7
|
import type { GitLabParseResult } from "./parse";
|
|
7
8
|
import { gitlabShortName } from "./parse";
|
|
8
9
|
import type { NamingStrategy } from "./naming";
|
|
@@ -16,6 +17,8 @@ export interface LexiconEntry {
|
|
|
16
17
|
resourceType: string;
|
|
17
18
|
kind: "resource" | "property";
|
|
18
19
|
lexicon: "gitlab";
|
|
20
|
+
deprecatedProperties?: string[];
|
|
21
|
+
constraints?: Record<string, PropertyConstraints>;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -34,12 +37,14 @@ export function generateLexiconJSON(
|
|
|
34
37
|
|
|
35
38
|
const entries = buildRegistry<LexiconEntry>(registryResources, naming, {
|
|
36
39
|
shortName: gitlabShortName,
|
|
37
|
-
buildEntry: (resource, _tsName, _attrs,
|
|
40
|
+
buildEntry: (resource, _tsName, _attrs, propConstraints) => {
|
|
38
41
|
const r = results.find((res) => res.resource.typeName === resource.typeName);
|
|
39
42
|
return {
|
|
40
43
|
resourceType: resource.typeName,
|
|
41
44
|
kind: (r?.isProperty ? "property" : "resource") as "resource" | "property",
|
|
42
45
|
lexicon: "gitlab" as const,
|
|
46
|
+
...(r?.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
|
|
47
|
+
...(propConstraints && Object.keys(propConstraints).length > 0 && { constraints: propConstraints }),
|
|
43
48
|
};
|
|
44
49
|
},
|
|
45
50
|
buildPropertyEntry: (resourceType, propertyType) => ({
|
package/src/codegen/generate.ts
CHANGED
|
@@ -30,61 +30,56 @@ export interface GitLabGenerateOptions extends GenerateOptions {
|
|
|
30
30
|
schemaVersion?: string;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
parseSchema: (_typeName, data) => {
|
|
42
|
-
// The CI schema is a single document — parseCISchema returns multiple results.
|
|
43
|
-
// The pipeline calls this once per schema entry. We return the first result
|
|
44
|
-
// and use augmentResults to inject the rest.
|
|
45
|
-
const results = parseCISchema(data);
|
|
46
|
-
if (results.length === 0) return null;
|
|
47
|
-
// Return the first result; stash the rest for augmentResults
|
|
48
|
-
pendingResults = results.slice(1);
|
|
49
|
-
return results[0];
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
createNaming: (results) => new NamingStrategy(results),
|
|
53
|
-
|
|
54
|
-
augmentResults: (results, _opts, log) => {
|
|
55
|
-
// Add the remaining results from the single-schema parse
|
|
56
|
-
if (pendingResults.length > 0) {
|
|
57
|
-
results.push(...pendingResults);
|
|
58
|
-
log(`Added ${pendingResults.length} additional CI entities from single schema`);
|
|
59
|
-
pendingResults = [];
|
|
60
|
-
}
|
|
61
|
-
log(`Total: ${results.length} CI entity schemas`);
|
|
62
|
-
return { results };
|
|
63
|
-
},
|
|
33
|
+
/**
|
|
34
|
+
* Run the full GitLab generation pipeline.
|
|
35
|
+
*/
|
|
36
|
+
export async function generate(opts: GitLabGenerateOptions = {}): Promise<GenerateResult> {
|
|
37
|
+
// Pipeline state captured in closure — no module-level mutation
|
|
38
|
+
let pendingResults: GitLabParseResult[] = [];
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
40
|
+
const config: GeneratePipelineConfig<GitLabParseResult> = {
|
|
41
|
+
fetchSchemas: async (fetchOpts) => {
|
|
42
|
+
return fetchSchemas(fetchOpts.force, opts.schemaVersion);
|
|
43
|
+
},
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
45
|
+
parseSchema: (_typeName, data) => {
|
|
46
|
+
// The CI schema is a single document — parseCISchema returns multiple results.
|
|
47
|
+
// The pipeline calls this once per schema entry. We return the first result
|
|
48
|
+
// and use augmentResults to inject the rest.
|
|
49
|
+
const results = parseCISchema(data);
|
|
50
|
+
if (results.length === 0) return null;
|
|
51
|
+
// Return the first result; stash the rest for augmentResults
|
|
52
|
+
pendingResults = results.slice(1);
|
|
53
|
+
return results[0];
|
|
54
|
+
},
|
|
72
55
|
|
|
73
|
-
|
|
74
|
-
return generateRuntimeIndex(results, naming as NamingStrategy);
|
|
75
|
-
},
|
|
76
|
-
};
|
|
56
|
+
createNaming: (results) => new NamingStrategy(results),
|
|
77
57
|
|
|
78
|
-
|
|
79
|
-
|
|
58
|
+
augmentResults: (results, _opts, log) => {
|
|
59
|
+
// Add the remaining results from the single-schema parse
|
|
60
|
+
if (pendingResults.length > 0) {
|
|
61
|
+
results.push(...pendingResults);
|
|
62
|
+
log(`Added ${pendingResults.length} additional CI entities from single schema`);
|
|
63
|
+
pendingResults = [];
|
|
64
|
+
}
|
|
65
|
+
log(`Total: ${results.length} CI entity schemas`);
|
|
66
|
+
return { results };
|
|
67
|
+
},
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
generateRegistry: (results, naming) => {
|
|
70
|
+
return generateLexiconJSON(results, naming as NamingStrategy);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
generateTypes: (results, naming) => {
|
|
74
|
+
return generateTypeScriptDeclarations(results, naming as NamingStrategy);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
generateRuntimeIndex: (results, naming) => {
|
|
78
|
+
return generateRuntimeIndex(results, naming as NamingStrategy);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return generatePipeline(config, opts);
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
/**
|
package/src/codegen/naming.ts
CHANGED
|
@@ -29,6 +29,9 @@ const gitlabNamingConfig: NamingConfig = {
|
|
|
29
29
|
"GitLab::CI::Environment": "Environment",
|
|
30
30
|
"GitLab::CI::Trigger": "Trigger",
|
|
31
31
|
"GitLab::CI::AutoCancel": "AutoCancel",
|
|
32
|
+
"GitLab::CI::WorkflowRule": "WorkflowRule",
|
|
33
|
+
"GitLab::CI::Need": "Need",
|
|
34
|
+
"GitLab::CI::Inherit": "Inherit",
|
|
32
35
|
},
|
|
33
36
|
priorityAliases: {},
|
|
34
37
|
priorityPropertyAliases: {},
|
|
@@ -5,9 +5,9 @@ import { loadSchemaFixture } from "../testdata/load-fixtures";
|
|
|
5
5
|
const fixture = loadSchemaFixture();
|
|
6
6
|
|
|
7
7
|
describe("parseCISchema", () => {
|
|
8
|
-
test("returns
|
|
8
|
+
test("returns 19 entities", () => {
|
|
9
9
|
const results = parseCISchema(fixture);
|
|
10
|
-
expect(results).toHaveLength(
|
|
10
|
+
expect(results).toHaveLength(19);
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
test("returns 3 resource entities", () => {
|
|
@@ -20,10 +20,10 @@ describe("parseCISchema", () => {
|
|
|
20
20
|
expect(names).toContain("GitLab::CI::Workflow");
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
test("returns
|
|
23
|
+
test("returns 16 property entities", () => {
|
|
24
24
|
const results = parseCISchema(fixture);
|
|
25
25
|
const properties = results.filter((r) => r.isProperty);
|
|
26
|
-
expect(properties).toHaveLength(
|
|
26
|
+
expect(properties).toHaveLength(16);
|
|
27
27
|
const names = properties.map((r) => r.resource.typeName);
|
|
28
28
|
expect(names).toContain("GitLab::CI::Artifacts");
|
|
29
29
|
expect(names).toContain("GitLab::CI::Cache");
|
|
@@ -38,6 +38,9 @@ describe("parseCISchema", () => {
|
|
|
38
38
|
expect(names).toContain("GitLab::CI::Environment");
|
|
39
39
|
expect(names).toContain("GitLab::CI::Trigger");
|
|
40
40
|
expect(names).toContain("GitLab::CI::AutoCancel");
|
|
41
|
+
expect(names).toContain("GitLab::CI::WorkflowRule");
|
|
42
|
+
expect(names).toContain("GitLab::CI::Need");
|
|
43
|
+
expect(names).toContain("GitLab::CI::Inherit");
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
test("property entities have isProperty set to true", () => {
|
|
@@ -125,6 +128,108 @@ describe("Cache entity", () => {
|
|
|
125
128
|
});
|
|
126
129
|
});
|
|
127
130
|
|
|
131
|
+
describe("spec conformance — type mappings", () => {
|
|
132
|
+
const results = parseCISchema(fixture);
|
|
133
|
+
const findEntity = (name: string) => results.find((r) => r.resource.typeName === `GitLab::CI::${name}`);
|
|
134
|
+
const findProp = (entityName: string, propName: string) =>
|
|
135
|
+
findEntity(entityName)?.resource.properties.find((p) => p.name === propName);
|
|
136
|
+
|
|
137
|
+
test("Job.environment → Environment | string", () => {
|
|
138
|
+
expect(findProp("Job", "environment")?.tsType).toBe("Environment | string");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("Job.trigger → Trigger | string", () => {
|
|
142
|
+
expect(findProp("Job", "trigger")?.tsType).toBe("Trigger | string");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("Job.release → Release", () => {
|
|
146
|
+
expect(findProp("Job", "release")?.tsType).toBe("Release");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("Job.needs → Need[]", () => {
|
|
150
|
+
expect(findProp("Job", "needs")?.tsType).toBe("Need[]");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("Job.inherit → Inherit", () => {
|
|
154
|
+
expect(findProp("Job", "inherit")?.tsType).toBe("Inherit");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("Workflow.rules → WorkflowRule[]", () => {
|
|
158
|
+
expect(findProp("Workflow", "rules")?.tsType).toBe("WorkflowRule[]");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("AllowFailure.exit_codes → number | number[]", () => {
|
|
162
|
+
expect(findProp("AllowFailure", "exit_codes")?.tsType).toBe("number | number[]");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("Image.pull_policy has no duplicate enum values", () => {
|
|
166
|
+
const pp = findProp("Image", "pull_policy");
|
|
167
|
+
expect(pp).toBeDefined();
|
|
168
|
+
// Should be clean union with parens on array form
|
|
169
|
+
expect(pp!.tsType).toContain("always");
|
|
170
|
+
expect(pp!.tsType).toContain("never");
|
|
171
|
+
expect(pp!.tsType).toContain("if-not-present");
|
|
172
|
+
// Array variant should use parens
|
|
173
|
+
expect(pp!.tsType).toContain(")[]");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("Include has local, remote, template, component fields", () => {
|
|
177
|
+
const include = findEntity("Include");
|
|
178
|
+
expect(include).toBeDefined();
|
|
179
|
+
const propNames = include!.resource.properties.map((p) => p.name);
|
|
180
|
+
expect(propNames).toContain("project");
|
|
181
|
+
expect(propNames).toContain("file");
|
|
182
|
+
expect(propNames).toContain("local");
|
|
183
|
+
expect(propNames).toContain("remote");
|
|
184
|
+
expect(propNames).toContain("template");
|
|
185
|
+
expect(propNames).toContain("component");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("Trigger has include field for child pipelines", () => {
|
|
189
|
+
const trigger = findEntity("Trigger");
|
|
190
|
+
expect(trigger).toBeDefined();
|
|
191
|
+
const propNames = trigger!.resource.properties.map((p) => p.name);
|
|
192
|
+
expect(propNames).toContain("project");
|
|
193
|
+
expect(propNames).toContain("include");
|
|
194
|
+
expect(propNames).toContain("strategy");
|
|
195
|
+
expect(propNames).toContain("forward");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("Need has merged properties from all object variants", () => {
|
|
199
|
+
const need = findEntity("Need");
|
|
200
|
+
expect(need).toBeDefined();
|
|
201
|
+
const propNames = need!.resource.properties.map((p) => p.name);
|
|
202
|
+
expect(propNames).toContain("job");
|
|
203
|
+
expect(propNames).toContain("artifacts");
|
|
204
|
+
expect(propNames).toContain("project");
|
|
205
|
+
expect(propNames).toContain("ref");
|
|
206
|
+
expect(propNames).toContain("pipeline");
|
|
207
|
+
expect(propNames).toContain("optional");
|
|
208
|
+
// job should be required (in all object variants)
|
|
209
|
+
const jobProp = need!.resource.properties.find((p) => p.name === "job");
|
|
210
|
+
expect(jobProp?.required).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("WorkflowRule has restricted when enum", () => {
|
|
214
|
+
const wr = findEntity("WorkflowRule");
|
|
215
|
+
expect(wr).toBeDefined();
|
|
216
|
+
const propNames = wr!.resource.properties.map((p) => p.name);
|
|
217
|
+
expect(propNames).toContain("if");
|
|
218
|
+
expect(propNames).toContain("when");
|
|
219
|
+
expect(propNames).toContain("auto_cancel");
|
|
220
|
+
const whenProp = wr!.resource.properties.find((p) => p.name === "when");
|
|
221
|
+
expect(whenProp?.tsType).toBe('"always" | "never"');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("Inherit has default and variables properties", () => {
|
|
225
|
+
const inherit = findEntity("Inherit");
|
|
226
|
+
expect(inherit).toBeDefined();
|
|
227
|
+
const propNames = inherit!.resource.properties.map((p) => p.name);
|
|
228
|
+
expect(propNames).toContain("default");
|
|
229
|
+
expect(propNames).toContain("variables");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
128
233
|
describe("property types and enums", () => {
|
|
129
234
|
test("entities have empty propertyTypes (nested types extracted as top-level)", () => {
|
|
130
235
|
const results = parseCISchema(fixture);
|
|
@@ -141,6 +246,51 @@ describe("property types and enums", () => {
|
|
|
141
246
|
});
|
|
142
247
|
});
|
|
143
248
|
|
|
249
|
+
describe("deprecatedProperties", () => {
|
|
250
|
+
test("all entities have a deprecatedProperties array", () => {
|
|
251
|
+
const results = parseCISchema(fixture);
|
|
252
|
+
for (const r of results) {
|
|
253
|
+
expect(Array.isArray(r.resource.deprecatedProperties)).toBe(true);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("mines deprecation from property description", () => {
|
|
258
|
+
const schema = JSON.stringify({
|
|
259
|
+
definitions: {
|
|
260
|
+
job_template: {
|
|
261
|
+
properties: {
|
|
262
|
+
script: { type: "string" },
|
|
263
|
+
oldProp: { type: "string", description: "Deprecated in 12.0: use newProp instead." },
|
|
264
|
+
newProp: { type: "string", description: "The replacement property" },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
const results = parseCISchema(schema);
|
|
270
|
+
const job = results.find((r) => r.resource.typeName === "GitLab::CI::Job");
|
|
271
|
+
expect(job).toBeDefined();
|
|
272
|
+
expect(job!.resource.deprecatedProperties).toContain("oldProp");
|
|
273
|
+
expect(job!.resource.deprecatedProperties).not.toContain("newProp");
|
|
274
|
+
expect(job!.resource.deprecatedProperties).not.toContain("script");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("empty deprecatedProperties when no deprecation signals", () => {
|
|
278
|
+
const schema = JSON.stringify({
|
|
279
|
+
definitions: {
|
|
280
|
+
job_template: {
|
|
281
|
+
properties: {
|
|
282
|
+
script: { type: "string", description: "Commands to run" },
|
|
283
|
+
stage: { type: "string", description: "Pipeline stage" },
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
const results = parseCISchema(schema);
|
|
289
|
+
const job = results.find((r) => r.resource.typeName === "GitLab::CI::Job");
|
|
290
|
+
expect(job!.resource.deprecatedProperties).toEqual([]);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
144
294
|
describe("edge cases", () => {
|
|
145
295
|
test("empty schema returns empty results", () => {
|
|
146
296
|
const emptySchema = JSON.stringify({ definitions: {} });
|