@intentius/chant-lexicon-gitlab 0.118.1 → 0.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/parse.d.ts.map +1 -1
- package/dist/generated/index.d.ts +1 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/integrity.json +4 -4
- package/dist/manifest.json +1 -1
- package/dist/meta.json +14 -0
- package/dist/okf/index.md +1 -0
- package/dist/okf/rules/WGL034.md +1 -0
- package/dist/okf/rules/WGL038.md +1 -0
- package/dist/okf/rules/WGL046.md +1 -0
- package/dist/okf/rules/WGL048.md +4 -0
- package/dist/okf/types/Default.md +1 -1
- package/dist/okf/types/Job.md +3 -2
- package/dist/okf/types/Pipeline.md +23 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/types/index.d.ts +15 -3
- package/package.json +3 -3
- package/src/codegen/docs.ts +44 -4
- package/src/codegen/naming.ts +1 -0
- package/src/codegen/parse.test.ts +16 -6
- package/src/codegen/parse.ts +43 -4
- package/src/codegen/snapshot.test.ts +1 -1
- package/src/components/generate-op-pipeline.test.ts +1 -1
- package/src/components/generate-op-pipeline.ts +1 -1
- package/src/components/generate-op-waves-pipeline.ts +1 -1
- package/src/coverage.test.ts +3 -3
- package/src/generated/index.d.ts +15 -3
- package/src/generated/index.ts +1 -0
- package/src/generated/lexicon-gitlab.json +14 -0
- package/src/plugin.test.ts +1 -1
- package/src/serializer.test.ts +70 -0
- package/src/serializer.ts +51 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/codegen/docs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/codegen/docs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyHH;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC9E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/codegen/naming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,cAAc,IAAI,kBAAkB,EAGrC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAsC,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAErF,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/codegen/naming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,cAAc,IAAI,kBAAkB,EAGrC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAsC,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAErF,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AA+BnF;;;GAGG;AACH,qBAAa,cAAe,SAAQ,kBAAkB;IACpD,YAAY,OAAO,EAAE,iBAAiB,EAAE,EAMvC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/codegen/parse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAEL,kBAAkB,IAAI,sBAAsB,EAI7C,MAAM,sCAAsC,CAAC;AAI9C,YAAY,EAAE,mBAAmB,EAAE,CAAC;AACpC,OAAO,EAAE,sBAAsB,IAAI,kBAAkB,EAAE,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/codegen/parse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAEL,kBAAkB,IAAI,sBAAsB,EAI7C,MAAM,sCAAsC,CAAC;AAI9C,YAAY,EAAE,mBAAmB,EAAE,CAAC;AACpC,OAAO,EAAE,sBAAsB,IAAI,kBAAkB,EAAE,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA2GD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,iBAAiB,EAAE,CAoBxE;AA6dD;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const Default: new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, string>;
|
|
2
2
|
export declare const Job: new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, string>;
|
|
3
|
+
export declare const Pipeline: new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, string>;
|
|
3
4
|
export declare const Workflow: new (props: Record<string, unknown>, attributes?: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, string>;
|
|
4
5
|
export declare const AllowFailure: new (props: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, unknown>;
|
|
5
6
|
export declare const Artifacts: new (props: Record<string, unknown>) => import("@intentius/chant").Declarable & Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generated/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,8IAAsE,CAAC;AAC3F,eAAO,MAAM,GAAG,8IAAkE,CAAC;AACnF,eAAO,MAAM,QAAQ,8IAAuE,CAAC;AAE7F,eAAO,MAAM,YAAY,yGAAuE,CAAC;AACjG,eAAO,MAAM,SAAS,yGAAoE,CAAC;AAC3F,eAAO,MAAM,UAAU,yGAAqE,CAAC;AAC7F,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,WAAW,yGAAsE,CAAC;AAC/F,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,IAAI,yGAA+D,CAAC;AACjF,eAAO,MAAM,QAAQ,yGAAmE,CAAC;AACzF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,IAAI,yGAA+D,CAAC;AACjF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,YAAY,yGAAuE,CAAC;AAGjG,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generated/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,8IAAsE,CAAC;AAC3F,eAAO,MAAM,GAAG,8IAAkE,CAAC;AACnF,eAAO,MAAM,QAAQ,8IAAuE,CAAC;AAC7F,eAAO,MAAM,QAAQ,8IAAuE,CAAC;AAE7F,eAAO,MAAM,YAAY,yGAAuE,CAAC;AACjG,eAAO,MAAM,SAAS,yGAAoE,CAAC;AAC3F,eAAO,MAAM,UAAU,yGAAqE,CAAC;AAC7F,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,WAAW,yGAAsE,CAAC;AAC/F,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,IAAI,yGAA+D,CAAC;AACjF,eAAO,MAAM,QAAQ,yGAAmE,CAAC;AACzF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,KAAK,yGAAgE,CAAC;AACnF,eAAO,MAAM,IAAI,yGAA+D,CAAC;AACjF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,OAAO,yGAAkE,CAAC;AACvF,eAAO,MAAM,YAAY,yGAAuE,CAAC;AAGjG,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
5
|
-
"meta.json": "
|
|
6
|
-
"types/index.d.ts": "
|
|
4
|
+
"manifest.json": "b2e850bbb36b4d9288070eaa6c6f240149f2487a1534b498184d9282a1f0982e",
|
|
5
|
+
"meta.json": "db082fbf90c5941c498b2bee0f106103449c1a4c257716ca2220f0dd490450b2",
|
|
6
|
+
"types/index.d.ts": "a048a66ebe20da0cb5fc889054cac69044d9bf882e2c56b5c5bf113d1f3ba57c",
|
|
7
7
|
"rules/artifact-no-expiry.ts": "3f3cabf9792cbf8207e53a25f506715466b19ec25e9c3b4d0d77fed6b2eb4542",
|
|
8
8
|
"rules/deprecated-only-except.ts": "2341b95c0aaf3013c375fb404969858e23451e3430f3d1a63b128fcb21b90156",
|
|
9
9
|
"rules/import-source.ts": "b5b2e79ef4c18a21711a380dce60e8bbab840223eda568b18039879d3a0a549f",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"skills/chant-gitlab-migrate.md": "4853d04980560b379e5b0f7267dcb0a1e5d5879cb9e9931681ce76b2a5fe69d8",
|
|
56
56
|
"skills/chant-gitlab-patterns.md": "6d9a44e9e8de4c3820be9d65381b0d2ede4cb3626aaaf0011e21838a9fbf556e"
|
|
57
57
|
},
|
|
58
|
-
"composite": "
|
|
58
|
+
"composite": "c0d291cbf1bda2ce61c417054a931919434b86868d2022cb9d6efc9d5f0175b2"
|
|
59
59
|
}
|
package/dist/manifest.json
CHANGED
package/dist/meta.json
CHANGED
|
@@ -181,6 +181,20 @@
|
|
|
181
181
|
"kind": "property",
|
|
182
182
|
"lexicon": "gitlab"
|
|
183
183
|
},
|
|
184
|
+
"Pipeline": {
|
|
185
|
+
"resourceType": "GitLab::CI::Pipeline",
|
|
186
|
+
"kind": "resource",
|
|
187
|
+
"lexicon": "gitlab",
|
|
188
|
+
"constraints": {
|
|
189
|
+
"stages": {
|
|
190
|
+
"default": [
|
|
191
|
+
"build",
|
|
192
|
+
"test",
|
|
193
|
+
"deploy"
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
184
198
|
"Release": {
|
|
185
199
|
"resourceType": "GitLab::CI::Release",
|
|
186
200
|
"kind": "property",
|
package/dist/okf/index.md
CHANGED
package/dist/okf/rules/WGL034.md
CHANGED
package/dist/okf/rules/WGL038.md
CHANGED
package/dist/okf/rules/WGL046.md
CHANGED
package/dist/okf/rules/WGL048.md
CHANGED
|
@@ -17,7 +17,7 @@ resource_type: GitLab::CI::Default
|
|
|
17
17
|
- `hooks` (`Record<string, unknown>`, optional)
|
|
18
18
|
- `id_tokens` (`Record<string, { aud: string | string[] }>`, optional)
|
|
19
19
|
- `identity` (`"google_cloud"`, optional)
|
|
20
|
-
- `image` (`Image`, optional)
|
|
20
|
+
- `image` (`Image | string`, optional)
|
|
21
21
|
- `interruptible` (`boolean`, optional)
|
|
22
22
|
- `retry` (`Retry | number`, optional)
|
|
23
23
|
- `services` (`Service[]`, optional)
|
package/dist/okf/types/Job.md
CHANGED
|
@@ -15,6 +15,7 @@ resource_type: GitLab::CI::Job
|
|
|
15
15
|
- `artifacts` (`Artifacts`, optional)
|
|
16
16
|
- `before_script` (`string | string[]`, optional)
|
|
17
17
|
- `cache` (`Cache | Cache[]`, optional)
|
|
18
|
+
- `comment` (`string`, optional): YAML comment lines written above the job's key. Not a GitLab key; each line of the string becomes a `#` line
|
|
18
19
|
- `coverage` (`string`, optional): Must be a regular expression, optionally but recommended to be quoted, and must be surrounded with '/'. Example: '/Code coverage: \d+\.\d+/'
|
|
19
20
|
- `dependencies` (`string[]`, optional): Specify a list of job names from earlier stages from which artifacts should be loaded. By default, all previous artifacts are passed. Use an empty array to skip downloading artifacts.
|
|
20
21
|
- `environment` (`Environment | string`, optional): Used to associate environment metadata with a deploy. Environment can have a name and URL attached to it, and will be displayed under /environments under the project.
|
|
@@ -23,11 +24,11 @@ resource_type: GitLab::CI::Job
|
|
|
23
24
|
- `hooks` (`Record<string, unknown>`, optional)
|
|
24
25
|
- `id_tokens` (`Record<string, { aud: string | string[] }>`, optional)
|
|
25
26
|
- `identity` (`"google_cloud"`, optional)
|
|
26
|
-
- `image` (`Image`, optional)
|
|
27
|
+
- `image` (`Image | string`, optional)
|
|
27
28
|
- `inherit` (`Inherit`, optional)
|
|
28
29
|
- `interruptible` (`boolean`, optional)
|
|
29
30
|
- `manual_confirmation` (`string`, optional)
|
|
30
|
-
- `needs` (`Need[]`, optional): The list of jobs in previous stages whose sole completion is needed to start the current job.
|
|
31
|
+
- `needs` (`(Need | string)[]`, optional): The list of jobs in previous stages whose sole completion is needed to start the current job.
|
|
31
32
|
- `only` (`any`, optional): Job will run *only* when these filtering options match.
|
|
32
33
|
- `pages` (`Record<string, any> | boolean`, optional)
|
|
33
34
|
- `parallel` (`Parallel | number`, optional)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: resource-type
|
|
3
|
+
title: Pipeline
|
|
4
|
+
description: gitlab resource type GitLab::CI::Pipeline
|
|
5
|
+
name: Pipeline
|
|
6
|
+
lexicon: gitlab
|
|
7
|
+
resource_type: GitLab::CI::Pipeline
|
|
8
|
+
---
|
|
9
|
+
`GitLab::CI::Pipeline`, a resource type of the gitlab lexicon.
|
|
10
|
+
|
|
11
|
+
## Properties
|
|
12
|
+
|
|
13
|
+
- `comment` (`string`, optional): YAML comment lines written at the top of the file. Not a GitLab key; each line of the string becomes a `#` line
|
|
14
|
+
- `include` (`Include | string | (Include | string)[]`, optional)
|
|
15
|
+
- `stages` (`string[]`, optional)
|
|
16
|
+
- `variables` (`Record<string, unknown>`, optional)
|
|
17
|
+
|
|
18
|
+
## Governed by
|
|
19
|
+
|
|
20
|
+
- [WGL034](/rules/WGL034.md): OIDC id_token mintable from a merge-request pipeline
|
|
21
|
+
- [WGL038](/rules/WGL038.md): Secret-like variable reachable from a merge-request pipeline
|
|
22
|
+
- [WGL046](/rules/WGL046.md): Cache populated in a merge-request pipeline (poisoning risk)
|
|
23
|
+
- [WGL048](/rules/WGL048.md): Pipeline defines workflow: but no workflow:name
|
package/dist/serializer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AA0F9D;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,UA+G9B,CAAC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class Default {
|
|
|
50
50
|
hooks?: Record<string, unknown>;
|
|
51
51
|
id_tokens?: Record<string, { aud: string | string[] }>;
|
|
52
52
|
identity?: "google_cloud";
|
|
53
|
-
image?: Image;
|
|
53
|
+
image?: Image | string;
|
|
54
54
|
interruptible?: boolean;
|
|
55
55
|
retry?: Retry | number;
|
|
56
56
|
services?: Service[];
|
|
@@ -123,6 +123,8 @@ export declare class Job {
|
|
|
123
123
|
artifacts?: Artifacts;
|
|
124
124
|
before_script?: string | string[];
|
|
125
125
|
cache?: Cache | Cache[];
|
|
126
|
+
/** YAML comment lines written above the job's key. Not a GitLab key; each line of the string becomes a `#` line */
|
|
127
|
+
comment?: string;
|
|
126
128
|
/** Must be a regular expression, optionally but recommended to be quoted, and must be surrounded with '/'. Example: '/Code coverage: \d+\.\d+/' */
|
|
127
129
|
coverage?: string;
|
|
128
130
|
/** Specify a list of job names from earlier stages from which artifacts should be loaded. By default, all previous artifacts are passed. Use an empty array to skip downloading artifacts. */
|
|
@@ -136,12 +138,12 @@ export declare class Job {
|
|
|
136
138
|
hooks?: Record<string, unknown>;
|
|
137
139
|
id_tokens?: Record<string, { aud: string | string[] }>;
|
|
138
140
|
identity?: "google_cloud";
|
|
139
|
-
image?: Image;
|
|
141
|
+
image?: Image | string;
|
|
140
142
|
inherit?: Inherit;
|
|
141
143
|
interruptible?: boolean;
|
|
142
144
|
manual_confirmation?: string;
|
|
143
145
|
/** The list of jobs in previous stages whose sole completion is needed to start the current job. */
|
|
144
|
-
needs?: Need[];
|
|
146
|
+
needs?: (Need | string)[];
|
|
145
147
|
/** Job will run *only* when these filtering options match. */
|
|
146
148
|
only?: any;
|
|
147
149
|
pages?: Record<string, any> | boolean;
|
|
@@ -188,6 +190,16 @@ export declare class Parallel {
|
|
|
188
190
|
});
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
export declare class Pipeline {
|
|
194
|
+
constructor(props: {
|
|
195
|
+
/** YAML comment lines written at the top of the file. Not a GitLab key; each line of the string becomes a `#` line */
|
|
196
|
+
comment?: string;
|
|
197
|
+
include?: Include | string | (Include | string)[];
|
|
198
|
+
stages?: string[];
|
|
199
|
+
variables?: Record<string, unknown>;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
191
203
|
export declare class Release {
|
|
192
204
|
constructor(props: {
|
|
193
205
|
/** Specifies the longer description of the Release. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.120.0",
|
|
4
4
|
"description": "GitLab CI lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"typescript": "^5.9.3"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@intentius/chant": "^0.
|
|
71
|
-
"@intentius/chant-lexicon-github": "^0.
|
|
70
|
+
"@intentius/chant": "^0.120.0",
|
|
71
|
+
"@intentius/chant-lexicon-github": "^0.120.0",
|
|
72
72
|
"typescript": "^5.9.3"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
package/src/codegen/docs.ts
CHANGED
|
@@ -30,12 +30,12 @@ npm install --save-dev @intentius/chant-lexicon-gitlab
|
|
|
30
30
|
|
|
31
31
|
{{file:docs-snippets/src/quickstart.ts}}
|
|
32
32
|
|
|
33
|
-
The lexicon provides **
|
|
33
|
+
The lexicon provides **4 resources** (Job, Workflow, Default, Pipeline), **16 property types** (Image, Cache, Artifacts, Rule, Environment, Trigger, Need, Service, and more), the \`CI\` pseudo-parameter object for predefined variables, and the \`reference()\` intrinsic for YAML \`!reference\` tags. It also ships **4 lint rules** + **39 post-synth checks** (including a CI/CD supply-chain security pass, WGL029–048) and a [\`chant migrate\`](./migration) source for translating GitHub Actions workflows.
|
|
34
34
|
`;
|
|
35
35
|
|
|
36
36
|
const outputFormat = `The GitLab lexicon serializes resources into **\`.gitlab-ci.yml\` YAML**. Keys are
|
|
37
|
-
converted to \`snake_case\` and jobs use kebab-case names. Stages are
|
|
38
|
-
|
|
37
|
+
converted to \`snake_case\` and jobs use kebab-case names. Stages are collected
|
|
38
|
+
from the job definitions, after any a \`Pipeline\` lists.
|
|
39
39
|
|
|
40
40
|
## Building
|
|
41
41
|
|
|
@@ -48,7 +48,8 @@ chant build
|
|
|
48
48
|
|
|
49
49
|
The generated file includes:
|
|
50
50
|
|
|
51
|
-
-
|
|
51
|
+
- a header comment, \`include:\` and \`variables:\`, if a \`Pipeline\` resource is exported
|
|
52
|
+
- \`stages:\` list: a \`Pipeline\`'s \`stages\` in its order, then every other stage a job's \`stage\` names, in first use
|
|
52
53
|
- \`default:\` section — if a \`Default\` resource is exported
|
|
53
54
|
- \`workflow:\` section — if a \`Workflow\` resource is exported
|
|
54
55
|
- Job definitions with \`snake_case\` keys and \`kebab-case\` job names
|
|
@@ -61,6 +62,45 @@ The generated file includes:
|
|
|
61
62
|
| \`expire_in: "1 week"\` | \`expire_in: 1 week\` | Property names use spec-native snake_case |
|
|
62
63
|
| \`new Image({ name: "node:20" })\` | \`image: node:20\` | Single-property objects are collapsed |
|
|
63
64
|
|
|
65
|
+
## Top-level keys and comments
|
|
66
|
+
|
|
67
|
+
A \`Pipeline\` resource holds the top-level keys that are not a job, \`default\` or \`workflow\`: \`include\`, \`stages\` and \`variables\`. Its \`stages\` can name a stage no declared job uses, such as one an included file's jobs run in, and set the order; a stage a job names that the list leaves out is still added after it, so the file stays valid. \`variables\` holds pipeline-wide variables, which \`Default\` cannot, since GitLab's \`default:\` has none.
|
|
68
|
+
|
|
69
|
+
\`Pipeline\` and \`Job\` also take a \`comment\`, written as \`#\` lines: the pipeline's at the top of the file, a job's above its key.
|
|
70
|
+
|
|
71
|
+
\`\`\`typescript
|
|
72
|
+
export const pipeline = new Pipeline({
|
|
73
|
+
comment: "Protect the writer's variables and scope them to their environment.",
|
|
74
|
+
include: [{ local: ".gitlab/watch.yml" }],
|
|
75
|
+
stages: ["review", "apply", "ops"],
|
|
76
|
+
variables: { GIT_DEPTH: "0" },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const lint = new Job({ comment: "Blocks the merge.", stage: "review", image: "node:22", script: ["npm run lint"] });
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
\`\`\`yaml
|
|
83
|
+
# Protect the writer's variables and scope them to their environment.
|
|
84
|
+
|
|
85
|
+
include:
|
|
86
|
+
- local: .gitlab/watch.yml
|
|
87
|
+
|
|
88
|
+
stages:
|
|
89
|
+
- review
|
|
90
|
+
- apply
|
|
91
|
+
- ops
|
|
92
|
+
|
|
93
|
+
variables:
|
|
94
|
+
GIT_DEPTH: '0'
|
|
95
|
+
|
|
96
|
+
# Blocks the merge.
|
|
97
|
+
lint:
|
|
98
|
+
stage: review
|
|
99
|
+
image: node:22
|
|
100
|
+
script:
|
|
101
|
+
- npm run lint
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
64
104
|
## Validating locally
|
|
65
105
|
|
|
66
106
|
The output is standard GitLab CI YAML. Validate with the GitLab CI Lint API or locally:
|
package/src/codegen/naming.ts
CHANGED
|
@@ -17,6 +17,7 @@ const gitlabNamingConfig: NamingConfig = {
|
|
|
17
17
|
"GitLab::CI::Job": "Job",
|
|
18
18
|
"GitLab::CI::Default": "Default",
|
|
19
19
|
"GitLab::CI::Workflow": "Workflow",
|
|
20
|
+
"GitLab::CI::Pipeline": "Pipeline",
|
|
20
21
|
"GitLab::CI::Artifacts": "Artifacts",
|
|
21
22
|
"GitLab::CI::Cache": "Cache",
|
|
22
23
|
"GitLab::CI::Image": "Image",
|
|
@@ -5,19 +5,20 @@ import { loadSchemaFixture } from "../testdata/load-fixtures";
|
|
|
5
5
|
const fixture = loadSchemaFixture();
|
|
6
6
|
|
|
7
7
|
describe("parseCISchema", () => {
|
|
8
|
-
test("returns
|
|
8
|
+
test("returns 20 entities", () => {
|
|
9
9
|
const results = parseCISchema(fixture);
|
|
10
|
-
expect(results).toHaveLength(
|
|
10
|
+
expect(results).toHaveLength(20);
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
test("returns
|
|
13
|
+
test("returns 4 resource entities", () => {
|
|
14
14
|
const results = parseCISchema(fixture);
|
|
15
15
|
const resources = results.filter((r) => !r.isProperty);
|
|
16
|
-
expect(resources).toHaveLength(
|
|
16
|
+
expect(resources).toHaveLength(4);
|
|
17
17
|
const names = resources.map((r) => r.resource.typeName);
|
|
18
18
|
expect(names).toContain("GitLab::CI::Job");
|
|
19
19
|
expect(names).toContain("GitLab::CI::Default");
|
|
20
20
|
expect(names).toContain("GitLab::CI::Workflow");
|
|
21
|
+
expect(names).toContain("GitLab::CI::Pipeline");
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
test("returns 16 property entities", () => {
|
|
@@ -146,8 +147,17 @@ describe("spec conformance — type mappings", () => {
|
|
|
146
147
|
expect(findProp("Job", "release")?.tsType).toBe("Release");
|
|
147
148
|
});
|
|
148
149
|
|
|
149
|
-
test("Job.needs → Need[]", () => {
|
|
150
|
-
expect(findProp("Job", "needs")?.tsType).toBe("Need[]");
|
|
150
|
+
test("Job.needs → (Need | string)[] and Job.image → Image | string (#3669)", () => {
|
|
151
|
+
expect(findProp("Job", "needs")?.tsType).toBe("(Need | string)[]");
|
|
152
|
+
expect(findProp("Job", "image")?.tsType).toBe("Image | string");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("Pipeline holds the top-level include, stages and variables, and a comment (#3669)", () => {
|
|
156
|
+
expect(findProp("Pipeline", "include")?.tsType).toBe("Include | string | (Include | string)[]");
|
|
157
|
+
expect(findProp("Pipeline", "stages")?.tsType).toBe("string[]");
|
|
158
|
+
expect(findProp("Pipeline", "variables")).toBeDefined();
|
|
159
|
+
expect(findProp("Pipeline", "comment")?.tsType).toBe("string");
|
|
160
|
+
expect(findProp("Job", "comment")?.tsType).toBe("string");
|
|
151
161
|
});
|
|
152
162
|
|
|
153
163
|
test("Job.inherit → Inherit", () => {
|
package/src/codegen/parse.ts
CHANGED
|
@@ -104,6 +104,8 @@ const RESOURCE_ENTITIES: Array<{
|
|
|
104
104
|
/** Schema path — "root" for root object, or "#/definitions/<name>" */
|
|
105
105
|
source: string;
|
|
106
106
|
description?: string;
|
|
107
|
+
/** Only these properties of the source (the root object holds every top-level key). */
|
|
108
|
+
pick?: string[];
|
|
107
109
|
}> = [
|
|
108
110
|
{
|
|
109
111
|
typeName: "GitLab::CI::Job",
|
|
@@ -120,6 +122,16 @@ const RESOURCE_ENTITIES: Array<{
|
|
|
120
122
|
source: "root:workflow",
|
|
121
123
|
description: "Pipeline-level workflow configuration",
|
|
122
124
|
},
|
|
125
|
+
{
|
|
126
|
+
// The top-level keys that are neither a job, `default` nor `workflow`
|
|
127
|
+
// (#3669). The serializer writes them first, so an explicit `stages`
|
|
128
|
+
// list can name a stage no declared job uses (one an included file's
|
|
129
|
+
// jobs run in) and set the order.
|
|
130
|
+
typeName: "GitLab::CI::Pipeline",
|
|
131
|
+
source: "root",
|
|
132
|
+
description: "Top-level pipeline keys: include, stages and variables",
|
|
133
|
+
pick: ["include", "stages", "variables"],
|
|
134
|
+
},
|
|
123
135
|
];
|
|
124
136
|
|
|
125
137
|
/**
|
|
@@ -181,14 +193,19 @@ export function parseCISchema(data: string | Buffer): GitLabParseResult[] {
|
|
|
181
193
|
*/
|
|
182
194
|
function extractResourceEntity(
|
|
183
195
|
schema: CISchema,
|
|
184
|
-
entity: { typeName: string; source: string; description?: string },
|
|
196
|
+
entity: { typeName: string; source: string; description?: string; pick?: string[] },
|
|
185
197
|
): GitLabParseResult | null {
|
|
186
198
|
const def = resolveSource(schema, entity.source);
|
|
187
199
|
if (!def) return null;
|
|
188
200
|
|
|
189
201
|
// Find the object variant if it's a oneOf/anyOf
|
|
190
|
-
const
|
|
191
|
-
if (!
|
|
202
|
+
const found = findObjectVariant(def, schema);
|
|
203
|
+
if (!found?.properties) return null;
|
|
204
|
+
const pick = entity.pick;
|
|
205
|
+
const objectDef: CISchemaDefinition = pick
|
|
206
|
+
? { ...found, properties: Object.fromEntries(Object.entries(found.properties).filter(([k]) => pick.includes(k))), required: undefined }
|
|
207
|
+
: found;
|
|
208
|
+
if (!objectDef.properties) return null;
|
|
192
209
|
|
|
193
210
|
const requiredSet = new Set<string>(objectDef.required ?? []);
|
|
194
211
|
const properties = parseProperties(objectDef.properties, requiredSet, schema);
|
|
@@ -203,6 +220,8 @@ function extractResourceEntity(
|
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
}
|
|
223
|
+
const comment = COMMENT_PROPERTIES[entity.typeName];
|
|
224
|
+
if (comment) properties.push({ name: "comment", tsType: "string", required: false, description: comment, constraints: {} });
|
|
206
225
|
|
|
207
226
|
// Extract nested property types from definition properties
|
|
208
227
|
const { propertyTypes, enums } = extractNestedTypes(objectDef, shortName, schema);
|
|
@@ -270,12 +289,23 @@ function extractPropertyEntity(
|
|
|
270
289
|
};
|
|
271
290
|
}
|
|
272
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Resources that carry a `comment`: not a GitLab key, but `#` lines the
|
|
294
|
+
* serializer writes above the entry (#3669, as #3667 for github).
|
|
295
|
+
*/
|
|
296
|
+
const COMMENT_PROPERTIES: Record<string, string> = {
|
|
297
|
+
"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",
|
|
298
|
+
"GitLab::CI::Job": "YAML comment lines written above the job's key. Not a GitLab key; each line of the string becomes a `#` line",
|
|
299
|
+
};
|
|
300
|
+
|
|
273
301
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
274
302
|
|
|
275
303
|
/**
|
|
276
304
|
* Resolve a source path to a schema definition.
|
|
277
305
|
*/
|
|
278
306
|
function resolveSource(schema: CISchema, source: string): CISchemaDefinition | null {
|
|
307
|
+
if (source === "root") return { properties: schema.properties } as CISchemaDefinition;
|
|
308
|
+
|
|
279
309
|
if (source.startsWith("#/definitions/")) {
|
|
280
310
|
// Array item extraction: "#/definitions/foo:item" → foo.items object variant
|
|
281
311
|
if (source.includes(":item")) {
|
|
@@ -541,9 +571,18 @@ const RESOURCE_PROPERTY_OVERRIDES: Record<string, Record<string, string>> = {
|
|
|
541
571
|
environment: "Environment | string",
|
|
542
572
|
trigger: "Trigger | string",
|
|
543
573
|
release: "Release",
|
|
544
|
-
needs:
|
|
574
|
+
// GitLab takes the plain forms too: `image: node:22`, `needs: [build]`.
|
|
575
|
+
image: "Image | string",
|
|
576
|
+
needs: "(Need | string)[]",
|
|
545
577
|
inherit: "Inherit",
|
|
546
578
|
},
|
|
579
|
+
"GitLab::CI::Default": {
|
|
580
|
+
image: "Image | string",
|
|
581
|
+
},
|
|
582
|
+
"GitLab::CI::Pipeline": {
|
|
583
|
+
include: "Include | string | (Include | string)[]",
|
|
584
|
+
stages: "string[]",
|
|
585
|
+
},
|
|
547
586
|
"GitLab::CI::Workflow": {
|
|
548
587
|
rules: "WorkflowRule[]",
|
|
549
588
|
},
|
|
@@ -10,7 +10,7 @@ describe("generated lexicon-gitlab.json", () => {
|
|
|
10
10
|
test.skipIf(!hasGenerated)("is valid JSON with expected entries", () => {
|
|
11
11
|
const content = readFileSync(join(generatedDir, "lexicon-gitlab.json"), "utf-8");
|
|
12
12
|
const registry = JSON.parse(content);
|
|
13
|
-
expect(Object.keys(registry)).toHaveLength(
|
|
13
|
+
expect(Object.keys(registry)).toHaveLength(20);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test.skipIf(!hasGenerated)("contains all resource entities", () => {
|
|
@@ -454,7 +454,7 @@ describe("generateGitlabOpPipeline: a deployment environment (#2257)", () => {
|
|
|
454
454
|
*/
|
|
455
455
|
const YAML_BEFORE_2257 =
|
|
456
456
|
[
|
|
457
|
-
'# chant Ops
|
|
457
|
+
'# chant Ops — one job per Op under stage "ops", each',
|
|
458
458
|
"# selected by its own rules:. A merge_request_event or push job needs no",
|
|
459
459
|
"# setup; its rule fires on the event itself.",
|
|
460
460
|
"#",
|
|
@@ -505,7 +505,7 @@ export function generateGitlabOpPipeline(
|
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
const headerLines = [
|
|
508
|
-
`# chant Ops
|
|
508
|
+
`# chant Ops — one job per Op under stage "${stage}", each`,
|
|
509
509
|
"# selected by its own rules:. A merge_request_event or push job needs no",
|
|
510
510
|
"# setup; its rule fires on the event itself.",
|
|
511
511
|
];
|
|
@@ -95,7 +95,7 @@ export function generateGitlabOpWavesPipeline(spec: OpWavesSpec, options: OpWave
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
const header = [
|
|
98
|
-
`# chant Op waves "${spec.name}"
|
|
98
|
+
`# chant Op waves "${spec.name}": one stage per wave, each needing the one before.`,
|
|
99
99
|
"# A wave that waits at its gate exits 3 and stops the waves after it; approve the",
|
|
100
100
|
"# digest it prints, then retry the job.",
|
|
101
101
|
...(spec.resume
|
package/src/coverage.test.ts
CHANGED
|
@@ -12,8 +12,8 @@ describe("coverage analysis", () => {
|
|
|
12
12
|
const { computeCoverage } = await import("./coverage");
|
|
13
13
|
const lexiconJSON = readFileSync(lexiconPath, "utf-8");
|
|
14
14
|
const report = computeCoverage(lexiconJSON);
|
|
15
|
-
expect(report.resourceCount).toBe(
|
|
16
|
-
expect(report.resources).toHaveLength(
|
|
15
|
+
expect(report.resourceCount).toBe(4); // Job, Default, Workflow, Pipeline
|
|
16
|
+
expect(report.resources).toHaveLength(4);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
test.skipIf(!hasGenerated)("reports resource names correctly", async () => {
|
|
@@ -42,6 +42,6 @@ describe("coverage analysis", () => {
|
|
|
42
42
|
const report = computeCoverage(lexiconJSON);
|
|
43
43
|
const summary = formatSummary(report);
|
|
44
44
|
expect(summary).toContain("Coverage Report");
|
|
45
|
-
expect(summary).toContain("
|
|
45
|
+
expect(summary).toContain("4 resources");
|
|
46
46
|
});
|
|
47
47
|
});
|
package/src/generated/index.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class Default {
|
|
|
50
50
|
hooks?: Record<string, unknown>;
|
|
51
51
|
id_tokens?: Record<string, { aud: string | string[] }>;
|
|
52
52
|
identity?: "google_cloud";
|
|
53
|
-
image?: Image;
|
|
53
|
+
image?: Image | string;
|
|
54
54
|
interruptible?: boolean;
|
|
55
55
|
retry?: Retry | number;
|
|
56
56
|
services?: Service[];
|
|
@@ -123,6 +123,8 @@ export declare class Job {
|
|
|
123
123
|
artifacts?: Artifacts;
|
|
124
124
|
before_script?: string | string[];
|
|
125
125
|
cache?: Cache | Cache[];
|
|
126
|
+
/** YAML comment lines written above the job's key. Not a GitLab key; each line of the string becomes a `#` line */
|
|
127
|
+
comment?: string;
|
|
126
128
|
/** Must be a regular expression, optionally but recommended to be quoted, and must be surrounded with '/'. Example: '/Code coverage: \d+\.\d+/' */
|
|
127
129
|
coverage?: string;
|
|
128
130
|
/** Specify a list of job names from earlier stages from which artifacts should be loaded. By default, all previous artifacts are passed. Use an empty array to skip downloading artifacts. */
|
|
@@ -136,12 +138,12 @@ export declare class Job {
|
|
|
136
138
|
hooks?: Record<string, unknown>;
|
|
137
139
|
id_tokens?: Record<string, { aud: string | string[] }>;
|
|
138
140
|
identity?: "google_cloud";
|
|
139
|
-
image?: Image;
|
|
141
|
+
image?: Image | string;
|
|
140
142
|
inherit?: Inherit;
|
|
141
143
|
interruptible?: boolean;
|
|
142
144
|
manual_confirmation?: string;
|
|
143
145
|
/** The list of jobs in previous stages whose sole completion is needed to start the current job. */
|
|
144
|
-
needs?: Need[];
|
|
146
|
+
needs?: (Need | string)[];
|
|
145
147
|
/** Job will run *only* when these filtering options match. */
|
|
146
148
|
only?: any;
|
|
147
149
|
pages?: Record<string, any> | boolean;
|
|
@@ -188,6 +190,16 @@ export declare class Parallel {
|
|
|
188
190
|
});
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
export declare class Pipeline {
|
|
194
|
+
constructor(props: {
|
|
195
|
+
/** YAML comment lines written at the top of the file. Not a GitLab key; each line of the string becomes a `#` line */
|
|
196
|
+
comment?: string;
|
|
197
|
+
include?: Include | string | (Include | string)[];
|
|
198
|
+
stages?: string[];
|
|
199
|
+
variables?: Record<string, unknown>;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
191
203
|
export declare class Release {
|
|
192
204
|
constructor(props: {
|
|
193
205
|
/** Specifies the longer description of the Release. */
|
package/src/generated/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createResource, createProperty } from "./runtime";
|
|
|
3
3
|
|
|
4
4
|
export const Default = /* @__PURE__ */ createResource("GitLab::CI::Default", "gitlab", {});
|
|
5
5
|
export const Job = /* @__PURE__ */ createResource("GitLab::CI::Job", "gitlab", {});
|
|
6
|
+
export const Pipeline = /* @__PURE__ */ createResource("GitLab::CI::Pipeline", "gitlab", {});
|
|
6
7
|
export const Workflow = /* @__PURE__ */ createResource("GitLab::CI::Workflow", "gitlab", {});
|
|
7
8
|
|
|
8
9
|
export const AllowFailure = /* @__PURE__ */ createProperty("GitLab::CI::AllowFailure", "gitlab");
|
|
@@ -181,6 +181,20 @@
|
|
|
181
181
|
"kind": "property",
|
|
182
182
|
"lexicon": "gitlab"
|
|
183
183
|
},
|
|
184
|
+
"Pipeline": {
|
|
185
|
+
"resourceType": "GitLab::CI::Pipeline",
|
|
186
|
+
"kind": "resource",
|
|
187
|
+
"lexicon": "gitlab",
|
|
188
|
+
"constraints": {
|
|
189
|
+
"stages": {
|
|
190
|
+
"default": [
|
|
191
|
+
"build",
|
|
192
|
+
"test",
|
|
193
|
+
"deploy"
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
184
198
|
"Release": {
|
|
185
199
|
"resourceType": "GitLab::CI::Release",
|
|
186
200
|
"kind": "property",
|
package/src/plugin.test.ts
CHANGED
|
@@ -292,7 +292,7 @@ jobs:
|
|
|
292
292
|
const result = await catalog.handler();
|
|
293
293
|
const parsed = JSON.parse(result);
|
|
294
294
|
expect(Array.isArray(parsed)).toBe(true);
|
|
295
|
-
expect(parsed.length).toBe(
|
|
295
|
+
expect(parsed.length).toBe(20);
|
|
296
296
|
const job = parsed.find((e: { className: string }) => e.className === "Job");
|
|
297
297
|
expect(job).toBeDefined();
|
|
298
298
|
expect(job.kind).toBe("resource");
|
package/src/serializer.test.ts
CHANGED
|
@@ -447,3 +447,73 @@ describe("nested objects and arrays", () => {
|
|
|
447
447
|
expect(output).toContain("parallel: 5");
|
|
448
448
|
});
|
|
449
449
|
});
|
|
450
|
+
|
|
451
|
+
describe("Pipeline: top-level include, stages and variables, and comments (#3669)", () => {
|
|
452
|
+
const Pipeline = createResource("GitLab::CI::Pipeline", "gitlab", {});
|
|
453
|
+
const Job = createResource("GitLab::CI::Job", "gitlab", {});
|
|
454
|
+
const Include = createProperty("GitLab::CI::Include", "gitlab");
|
|
455
|
+
const Image = createProperty("GitLab::CI::Image", "gitlab");
|
|
456
|
+
|
|
457
|
+
test("renders include, an explicit stage list naming a stage no job uses, variables and comments", () => {
|
|
458
|
+
const entities = new Map<string, Declarable>([
|
|
459
|
+
["pipeline", new Pipeline({
|
|
460
|
+
comment: "Protect the writer variables.\n\nScope each to its environment.",
|
|
461
|
+
include: [new Include({ local: ".gitlab/apply.yml" }), { local: ".gitlab/watch.yml" }],
|
|
462
|
+
stages: ["review", "apply", "ops"],
|
|
463
|
+
variables: { GIT_DEPTH: "0" },
|
|
464
|
+
}) as unknown as Declarable],
|
|
465
|
+
["lint", new Job({ comment: "Blocks the merge.", stage: "review", image: "node:22", script: ["npm run lint"] }) as unknown as Declarable],
|
|
466
|
+
["apply", new Job({ stage: "apply", image: new Image({ name: "node:22" }), needs: ["lint"], script: ["npm run apply"] }) as unknown as Declarable],
|
|
467
|
+
]);
|
|
468
|
+
|
|
469
|
+
expect(gitlabSerializer.serialize(entities)).toBe(`# Protect the writer variables.
|
|
470
|
+
#
|
|
471
|
+
# Scope each to its environment.
|
|
472
|
+
|
|
473
|
+
include:
|
|
474
|
+
- local: .gitlab/apply.yml
|
|
475
|
+
- local: .gitlab/watch.yml
|
|
476
|
+
|
|
477
|
+
stages:
|
|
478
|
+
- review
|
|
479
|
+
- apply
|
|
480
|
+
- ops
|
|
481
|
+
|
|
482
|
+
variables:
|
|
483
|
+
GIT_DEPTH: '0'
|
|
484
|
+
|
|
485
|
+
# Blocks the merge.
|
|
486
|
+
lint:
|
|
487
|
+
stage: review
|
|
488
|
+
image: node:22
|
|
489
|
+
script:
|
|
490
|
+
- npm run lint
|
|
491
|
+
|
|
492
|
+
apply:
|
|
493
|
+
stage: apply
|
|
494
|
+
image:
|
|
495
|
+
name: node:22
|
|
496
|
+
needs:
|
|
497
|
+
- lint
|
|
498
|
+
script:
|
|
499
|
+
- npm run apply
|
|
500
|
+
`);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
test("a stage a job names but the list leaves out is appended, so the file stays valid", () => {
|
|
504
|
+
const entities = new Map<string, Declarable>([
|
|
505
|
+
["pipeline", new Pipeline({ stages: ["apply", "review"] }) as unknown as Declarable],
|
|
506
|
+
["lint", new Job({ stage: "lint", script: ["x"] }) as unknown as Declarable],
|
|
507
|
+
["plan", new Job({ stage: "review", script: ["x"] }) as unknown as Declarable],
|
|
508
|
+
]);
|
|
509
|
+
expect(gitlabSerializer.serialize(entities)).toContain("stages:\n - apply\n - review\n - lint\n");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("a single include string renders as a one-item list, and no Pipeline leaves the output as before", () => {
|
|
513
|
+
const one = new Map<string, Declarable>([["p", new Pipeline({ include: "templates/ci.yml" }) as unknown as Declarable]]);
|
|
514
|
+
expect(gitlabSerializer.serialize(one)).toBe("include:\n - templates/ci.yml\n");
|
|
515
|
+
|
|
516
|
+
const jobsOnly = new Map<string, Declarable>([["build", new Job({ stage: "build", script: ["make"] }) as unknown as Declarable]]);
|
|
517
|
+
expect(gitlabSerializer.serialize(jobsOnly)).toBe("stages:\n - build\n\nbuild:\n stage: build\n script:\n - make\n");
|
|
518
|
+
});
|
|
519
|
+
});
|
package/src/serializer.ts
CHANGED
|
@@ -15,6 +15,22 @@ import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-w
|
|
|
15
15
|
import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
|
|
16
16
|
import { emitYAMLEntry } from "@intentius/chant/yaml";
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* `#` lines for a comment, one per line of the string (#3669). Only the file
|
|
20
|
+
* header and job comments exist, and both sit at column 0.
|
|
21
|
+
*/
|
|
22
|
+
function commentLines(comment: string): string {
|
|
23
|
+
return comment.split("\n").map((l) => `#${l.trimEnd() ? ` ${l.trimEnd()}` : ""}`).join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** An entity's props without `comment`, and the comment if it is a non-empty string. */
|
|
27
|
+
function splitComment(entity: Declarable): { props: Record<string, unknown> | undefined; comment?: string } {
|
|
28
|
+
const props = isResourceDeclarable(entity) ? (entity.props as Record<string, unknown> | undefined) : undefined;
|
|
29
|
+
if (!props || !("comment" in props)) return { props };
|
|
30
|
+
const { comment, ...rest } = props;
|
|
31
|
+
return { props: rest, comment: typeof comment === "string" && comment !== "" ? comment : undefined };
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
/**
|
|
19
35
|
* GitLab CI visitor for the generic serializer walker.
|
|
20
36
|
*/
|
|
@@ -101,6 +117,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
101
117
|
|
|
102
118
|
// Separate entities by type
|
|
103
119
|
const jobs: Array<[string, Declarable]> = [];
|
|
120
|
+
const pipelines: Array<[string, Declarable]> = [];
|
|
104
121
|
const defaults: Array<[string, Declarable]> = [];
|
|
105
122
|
const workflows: Array<[string, Declarable]> = [];
|
|
106
123
|
const others: Array<[string, Declarable]> = [];
|
|
@@ -115,13 +132,40 @@ export const gitlabSerializer: Serializer = {
|
|
|
115
132
|
defaults.push([name, entity]);
|
|
116
133
|
} else if (entityType === "GitLab::CI::Workflow") {
|
|
117
134
|
workflows.push([name, entity]);
|
|
135
|
+
} else if (entityType === "GitLab::CI::Pipeline") {
|
|
136
|
+
pipelines.push([name, entity]);
|
|
118
137
|
} else {
|
|
119
138
|
others.push([name, entity]);
|
|
120
139
|
}
|
|
121
140
|
}
|
|
122
141
|
|
|
123
|
-
//
|
|
124
|
-
|
|
142
|
+
// The Pipeline entity's top-level keys (#3669). More than one is merged
|
|
143
|
+
// in declaration order: comments joined, includes concatenated, stages
|
|
144
|
+
// in first-listed order, later variables overriding earlier ones.
|
|
145
|
+
const headers: string[] = [];
|
|
146
|
+
const includes: unknown[] = [];
|
|
147
|
+
const declaredStages: string[] = [];
|
|
148
|
+
let variables: Record<string, unknown> | undefined;
|
|
149
|
+
for (const [, entity] of pipelines) {
|
|
150
|
+
const { props, comment } = splitComment(entity);
|
|
151
|
+
if (comment) headers.push(comment);
|
|
152
|
+
const converted = toYAMLValue(props, entityNames) as Record<string, unknown> | undefined;
|
|
153
|
+
if (!converted) continue;
|
|
154
|
+
if (converted.include !== undefined) {
|
|
155
|
+
includes.push(...(Array.isArray(converted.include) ? converted.include : [converted.include]));
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(converted.stages)) declaredStages.push(...converted.stages.map(String));
|
|
158
|
+
if (converted.variables && typeof converted.variables === "object") {
|
|
159
|
+
variables = { ...variables, ...(converted.variables as Record<string, unknown>) };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (headers.length > 0) sections.push(commentLines(headers.join("\n\n")));
|
|
163
|
+
if (includes.length > 0) sections.push(emitYAMLEntry("include", includes));
|
|
164
|
+
|
|
165
|
+
// Stages: a Pipeline's explicit list first, in its order, then any stage
|
|
166
|
+
// a job names that the list leaves out, in first use. Without a list,
|
|
167
|
+
// that is the jobs' stages alone.
|
|
168
|
+
const stages = new Set<string>(declaredStages);
|
|
125
169
|
for (const [, entity] of jobs) {
|
|
126
170
|
const props = isResourceDeclarable(entity) ? (entity.props as Record<string, unknown> | undefined) : undefined;
|
|
127
171
|
if (props?.stage && typeof props.stage === "string") {
|
|
@@ -131,6 +175,7 @@ export const gitlabSerializer: Serializer = {
|
|
|
131
175
|
if (stages.size > 0) {
|
|
132
176
|
sections.push(emitYAMLEntry("stages", [...stages]));
|
|
133
177
|
}
|
|
178
|
+
if (variables && Object.keys(variables).length > 0) sections.push(emitYAMLEntry("variables", variables));
|
|
134
179
|
|
|
135
180
|
// Emit defaults
|
|
136
181
|
for (const [, entity] of defaults) {
|
|
@@ -156,14 +201,13 @@ export const gitlabSerializer: Serializer = {
|
|
|
156
201
|
|
|
157
202
|
// Emit jobs
|
|
158
203
|
for (const [name, entity] of jobs) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
entityNames,
|
|
162
|
-
) as Record<string, unknown> | undefined;
|
|
204
|
+
const { props, comment } = splitComment(entity);
|
|
205
|
+
const converted = toYAMLValue(props, entityNames) as Record<string, unknown> | undefined;
|
|
163
206
|
if (converted) {
|
|
164
207
|
// Convert job name from camelCase to kebab-case for YAML
|
|
165
208
|
const yamlName = name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
166
|
-
|
|
209
|
+
const entry = emitYAMLEntry(yamlName, converted);
|
|
210
|
+
sections.push(comment ? `${commentLines(comment)}\n${entry}` : entry);
|
|
167
211
|
}
|
|
168
212
|
}
|
|
169
213
|
|