@intentius/chant-lexicon-gitlab 0.46.0 → 0.50.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/components/generate-op-pipeline.d.ts +37 -0
- package/dist/components/generate-op-pipeline.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +8 -5
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl019.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl049.d.ts +14 -0
- package/dist/lint/post-synth/wgl049.d.ts.map +1 -0
- package/dist/lint/post-synth/wgl050.d.ts +13 -0
- package/dist/lint/post-synth/wgl050.d.ts.map +1 -0
- package/dist/lint/rules/import-source.d.ts +28 -0
- package/dist/lint/rules/import-source.d.ts.map +1 -0
- package/dist/lint/rules/missing-script.d.ts.map +1 -1
- package/dist/lint/rules/missing-stage.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +2 -0
- package/dist/okf/rules/WGL049.md +15 -0
- package/dist/okf/rules/WGL050.md +15 -0
- package/dist/okf/types/Job.md +2 -0
- package/dist/op/builders.d.ts +29 -0
- package/dist/op/builders.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/import-source.ts +58 -0
- package/dist/rules/missing-script.ts +13 -0
- package/dist/rules/missing-stage.ts +8 -0
- package/dist/rules/wgl019.ts +6 -1
- package/dist/rules/wgl049.ts +57 -0
- package/dist/rules/wgl050.ts +49 -0
- package/package.json +3 -3
- package/src/components/generate-op-pipeline.test.ts +94 -0
- package/src/components/generate-op-pipeline.ts +111 -0
- package/src/index.ts +8 -0
- package/src/lint/audit-catalog.ts +5 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/wgl019.test.ts +8 -0
- package/src/lint/post-synth/wgl019.ts +6 -1
- package/src/lint/post-synth/wgl049.test.ts +71 -0
- package/src/lint/post-synth/wgl049.ts +57 -0
- package/src/lint/post-synth/wgl050.test.ts +57 -0
- package/src/lint/post-synth/wgl050.ts +49 -0
- package/src/lint/rules/import-source.ts +58 -0
- package/src/lint/rules/missing-script.ts +13 -0
- package/src/lint/rules/missing-stage.ts +8 -0
- package/src/lint/rules/rules.test.ts +60 -0
- package/src/op/builders.test.ts +43 -0
- package/src/op/builders.ts +39 -0
- package/src/plugin.test.ts +1 -1
- package/src/plugin.ts +3 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate mode — scheduled Op → GitLab CI YAML (#927).
|
|
3
|
+
*
|
|
4
|
+
* The Op counterpart to `./generate-pipeline.ts` (#563): that module
|
|
5
|
+
* synthesizes a deploy-time component graph as one `.gitlab-ci.yml`; this one
|
|
6
|
+
* synthesizes a cron-triggered job per stateless Op — the CI-native
|
|
7
|
+
* alternative to a Temporal `TemporalSchedule` for downstream projects that
|
|
8
|
+
* don't run Temporal (`WorkflowAuditOp`/`PipelineAuditOp`/`ReconcileOp` all
|
|
9
|
+
* accept an optional `schedule` precisely for this).
|
|
10
|
+
*
|
|
11
|
+
* Unlike GitHub Actions' per-workflow `on.schedule`, GitLab has no in-file
|
|
12
|
+
* cron at all — a schedule is a project-level object (Settings → CI/CD →
|
|
13
|
+
* Schedules) that runs the project's *existing* `.gitlab-ci.yml` with a
|
|
14
|
+
* chosen cron and CI/CD variables. So every scheduled Op here becomes one
|
|
15
|
+
* job in a single generated file, gated to run only under its own Pipeline
|
|
16
|
+
* Schedule (`$CI_PIPELINE_SOURCE == "schedule"` plus a per-op selector
|
|
17
|
+
* variable) — the cron itself is configured on the Pipeline Schedule, not in
|
|
18
|
+
* this YAML, and the generated file's header comment states what to set up.
|
|
19
|
+
* Each job runs exactly one invocation, `chant run <name>` by default — never
|
|
20
|
+
* inlined audit/reconcile logic. The finding-mode itself is already baked
|
|
21
|
+
* into the Op's own activity args at build time by the composite that
|
|
22
|
+
* created it; GitLab has no per-job `permissions:` concept (unlike GitHub
|
|
23
|
+
* Actions), so a non-`report` mode's write access comes from whatever
|
|
24
|
+
* `GITLAB_TOKEN`/CI-CD-variable configuration the project already has —
|
|
25
|
+
* this generator documents the requirement rather than fabricating a
|
|
26
|
+
* variable nothing reads.
|
|
27
|
+
*/
|
|
28
|
+
import type { ComponentPipelineOptions as GenerateGitlabOpOptions, OpPipelineResult as GenerateGitlabOpResult, ScheduledOpSpec } from "@intentius/chant/lexicon";
|
|
29
|
+
export type { GenerateGitlabOpOptions, GenerateGitlabOpResult };
|
|
30
|
+
/**
|
|
31
|
+
* Synthesize one `.gitlab-ci.yml` job per scheduled Op, all in a single file
|
|
32
|
+
* (GitLab has no per-file cron — see the module doc). Wired into core's Op
|
|
33
|
+
* generate mode via the gitlab lexicon plugin's `generateOpPipeline`
|
|
34
|
+
* (../plugin.ts).
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateGitlabOpPipeline(ops: ScheduledOpSpec[], options?: GenerateGitlabOpOptions): GenerateGitlabOpResult;
|
|
37
|
+
//# sourceMappingURL=generate-op-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAGnD,gBAAgB,IAAI,sBAAsB,EAC1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAmBhE;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CA6CxB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { gitlabSerializer } from "./serializer.js";
|
|
2
2
|
export { gitlabPlugin } from "./plugin.js";
|
|
3
|
+
export { gitlabPipeline } from "./op/builders.js";
|
|
3
4
|
export { reference, ReferenceIntrinsic } from "./intrinsics.js";
|
|
4
5
|
export { CI } from "./variables.js";
|
|
5
6
|
export * from "./generated/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAQxC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG7D,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAIjC,cAAc,mBAAmB,CAAC;AAGlC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACnI,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGtI,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGzH,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "b1cc9643fe6dbb20872dbccf6f0ac296fd7b2ab57a3d92b8a45915b1eeaf7743",
|
|
5
5
|
"meta.json": "931fc3246a55645b1493080bbeb160e5d205e42349e8bdca96ca243adb5f0da3",
|
|
6
6
|
"types/index.d.ts": "5cd2e99f135a929b72bdd822d00d780d39b1cd407ba0cfb3d511c7ac9d667b58",
|
|
7
7
|
"rules/artifact-no-expiry.ts": "3f3cabf9792cbf8207e53a25f506715466b19ec25e9c3b4d0d77fed6b2eb4542",
|
|
8
8
|
"rules/deprecated-only-except.ts": "2341b95c0aaf3013c375fb404969858e23451e3430f3d1a63b128fcb21b90156",
|
|
9
|
-
"rules/
|
|
10
|
-
"rules/missing-
|
|
9
|
+
"rules/import-source.ts": "b5b2e79ef4c18a21711a380dce60e8bbab840223eda568b18039879d3a0a549f",
|
|
10
|
+
"rules/missing-script.ts": "c9efd57e2d5cc0a022c6e7858f7ffa98d2fbb055e6ea915d2d54cf2b2d130e47",
|
|
11
|
+
"rules/missing-stage.ts": "b0f4b7e0b70e496ddc9d239d78a26410da4c18fcf9c518813523bb2459ffa5f7",
|
|
11
12
|
"rules/wgl010.ts": "e9a14d5f9e4c49b1524ba5ee3746be739603125ec3c2dc4eb4b7c0bfc32581b0",
|
|
12
13
|
"rules/wgl011.ts": "2fbb164669204ac8c3662b9c1ae156b59589913e59dd596fb38f289a0f218aaf",
|
|
13
14
|
"rules/wgl012.ts": "ec4bd3fe93d30824c74223bb4e5929e493a06561a7bd3265b68dc1ba5d734da4",
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
"rules/wgl016.ts": "d8bf9a371b22f37a767f92e3361d5f8e064e6dd6ba879ff1a5fc90b4b09fd304",
|
|
18
19
|
"rules/wgl017.ts": "2d212de62eee4f7b086b49c92975b09d988c1ff05c2fb20cffccdc500a14e668",
|
|
19
20
|
"rules/wgl018.ts": "dba1e1357becb1bbea9b435d9ce88b12138a53d8fc934483bc0ec8f22e4b18f5",
|
|
20
|
-
"rules/wgl019.ts": "
|
|
21
|
+
"rules/wgl019.ts": "01d703b2734385c3a9c6a132e1988f07d24636aa5e8fc22d8e06cc7b4aae5d97",
|
|
21
22
|
"rules/wgl020.ts": "81a84b49308f36ab2b01342bd0e262b59b2a8395d233760ac52a679c2a4ae0e6",
|
|
22
23
|
"rules/wgl021.ts": "59a75e0b0123779bcd4c7f66eb068c4302860df8333a4bc7b41c68e3983505da",
|
|
23
24
|
"rules/wgl022.ts": "96b87f8f410f5ffa6358823f4c8cb08a59166a7575166ede24e8c41b50f3be65",
|
|
@@ -47,10 +48,12 @@
|
|
|
47
48
|
"rules/wgl046.ts": "bbd8f5075aa390db546f5e787ce542f31a9d5eb031578780480e770cd86f9b5b",
|
|
48
49
|
"rules/wgl047.ts": "e6dba6a0c6c042e90a200859e52a0da28c56ce52705e147760eca0a0a37c3255",
|
|
49
50
|
"rules/wgl048.ts": "810590da4e7eaf70a6332ae247dbd34e000e2af57c89d3bd5a19bb2a923fcbb1",
|
|
51
|
+
"rules/wgl049.ts": "875ba2203d66a2a444edf17b43c2ae438a3a23a7f579b81a448efc3d5dd4336e",
|
|
52
|
+
"rules/wgl050.ts": "c96ef683ecf6e55699ea62d669ba6359616d723dd1bc24880823da0ff6c86503",
|
|
50
53
|
"rules/yaml-helpers.ts": "5835982d5d088eb4788ebad8160b181bfdb6fc94d546eabee90a4c278a913988",
|
|
51
54
|
"skills/chant-gitlab.md": "1e26a0c50ea891423479bd7fec3bc133f9185727c4a3eaadd525d7e7436056b6",
|
|
52
55
|
"skills/chant-gitlab-migrate.md": "4853d04980560b379e5b0f7267dcb0a1e5d5879cb9e9931681ce76b2a5fe69d8",
|
|
53
56
|
"skills/chant-gitlab-patterns.md": "6d9a44e9e8de4c3820be9d65381b0d2ede4cb3626aaaf0011e21838a9fbf556e"
|
|
54
57
|
},
|
|
55
|
-
"composite": "
|
|
58
|
+
"composite": "ea16ce1e577f7c99329bab5b18295456acec8809608d0fb006f6715479863b74"
|
|
56
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-catalog.d.ts","sourceRoot":"","sources":["../../src/lint/audit-catalog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAA0E,KAAK,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAEvI,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"audit-catalog.d.ts","sourceRoot":"","sources":["../../src/lint/audit-catalog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAA0E,KAAK,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAEvI,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA6CvD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AA2CvE,eAAO,MAAM,eAAe,EAAE,cAAc,EA0C3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wgl019.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl019.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"wgl019.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl019.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAoCpB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGL049: Dependency Install Without a Cache
|
|
3
|
+
*
|
|
4
|
+
* Flags a job whose `script:` runs a package-manager install command (`npm
|
|
5
|
+
* ci`, `pip install`, `bundle install`, etc.) with no `cache:` in scope —
|
|
6
|
+
* neither on the job itself, nor at the pipeline's `default:`/top level.
|
|
7
|
+
* Every run re-fetches the same dependencies from a cold cache. A job that
|
|
8
|
+
* `extends:` another config is left alone — the cache may be inherited and
|
|
9
|
+
* this check has no way to confirm that. Efficiency (#444), not a
|
|
10
|
+
* correctness or security issue.
|
|
11
|
+
*/
|
|
12
|
+
import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
13
|
+
export declare const wgl049: PostSynthCheck;
|
|
14
|
+
//# sourceMappingURL=wgl049.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wgl049.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl049.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAY9G,eAAO,MAAM,MAAM,EAAE,cAgCpB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGL050: Merge-Request Job Missing interruptible
|
|
3
|
+
*
|
|
4
|
+
* Flags a job reachable from merge-request pipelines that doesn't set
|
|
5
|
+
* `interruptible: true`. Without it, GitLab's auto-cancel-redundant-pipelines
|
|
6
|
+
* setting can't cancel the job when a new push supersedes it, so the runner
|
|
7
|
+
* keeps spending capacity on a pipeline nobody wants anymore. Deploy jobs are
|
|
8
|
+
* left alone — cancelling mid-deploy is its own hazard, not an efficiency
|
|
9
|
+
* win. Efficiency (#444), not a correctness or security issue.
|
|
10
|
+
*/
|
|
11
|
+
import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
12
|
+
export declare const wgl050: PostSynthCheck;
|
|
13
|
+
//# sourceMappingURL=wgl050.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wgl050.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/wgl050.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAgCpB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for gitlab lint rules that match a bare TypeScript
|
|
3
|
+
* identifier (`Job`, ...) and need to know whether it actually came from
|
|
4
|
+
* this lexicon before flagging it (chant #1544 — cross-lexicon rule bleed:
|
|
5
|
+
* `new Job(...)` matches ANY lexicon's `Job` class by name alone, so a
|
|
6
|
+
* multi-lexicon project got gitlab-only rules applied to github/forgejo
|
|
7
|
+
* jobs that were never missing anything).
|
|
8
|
+
*/
|
|
9
|
+
import * as ts from "typescript";
|
|
10
|
+
/**
|
|
11
|
+
* The module specifier a top-level import bound `name` to, whether via a
|
|
12
|
+
* named import (`import { Job } from "..."`, matched on its local —
|
|
13
|
+
* possibly aliased — binding) or a namespace import (`import * as gl from
|
|
14
|
+
* "..."`, matched on the namespace's own name). Undefined when `name` isn't
|
|
15
|
+
* bound by any top-level import in this file (no import at all, a
|
|
16
|
+
* re-export chain, a dynamic `require`, …) — the caller should treat that
|
|
17
|
+
* as "can't tell", not "not gitlab", to stay conservative for the common
|
|
18
|
+
* single-lexicon-project case (and every pre-#1544 unit test fixture, which
|
|
19
|
+
* has no import statements at all).
|
|
20
|
+
*/
|
|
21
|
+
export declare function importSourceFor(sourceFile: ts.SourceFile, name: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* True when `expression` (a `new`-expression callee) resolves — via a
|
|
24
|
+
* traced import — to a lexicon OTHER than gitlab. False for an unresolved
|
|
25
|
+
* import (conservative: still checked) or one that does resolve to gitlab.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isJobFromAnotherLexicon(sourceFile: ts.SourceFile, expression: ts.LeftHandSideExpression): boolean;
|
|
28
|
+
//# sourceMappingURL=import-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-source.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/import-source.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiB3F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,CAAC,sBAAsB,GAAG,OAAO,CAWjH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"missing-script.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"missing-script.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;AAMxF,eAAO,MAAM,iBAAiB,EAAE,QAgE/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"missing-stage.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-stage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"missing-stage.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/missing-stage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,4BAA4B,CAAC;AAIxF,eAAO,MAAM,gBAAgB,EAAE,QA0D9B,CAAC"}
|
package/dist/manifest.json
CHANGED
package/dist/okf/index.md
CHANGED
|
@@ -53,3 +53,5 @@ okf_version: '0.2'
|
|
|
53
53
|
* [WGL046](/rules/WGL046.md) - Cache populated in a merge-request pipeline (poisoning risk)
|
|
54
54
|
* [WGL047](/rules/WGL047.md) - Software fetched and piped to a shell without verification
|
|
55
55
|
* [WGL048](/rules/WGL048.md) - Pipeline defines workflow: but no workflow:name
|
|
56
|
+
* [WGL049](/rules/WGL049.md) - Job installs dependencies with no cache: in scope
|
|
57
|
+
* [WGL050](/rules/WGL050.md) - Merge-request-reachable job is not interruptible
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: post-synth-check
|
|
3
|
+
title: WGL049
|
|
4
|
+
description: 'Job installs dependencies with no cache: in scope'
|
|
5
|
+
id: WGL049
|
|
6
|
+
severity: error
|
|
7
|
+
category: post-synth
|
|
8
|
+
lexicon: gitlab
|
|
9
|
+
docs: https://intentius.io/chant/lexicons/gitlab/rules/
|
|
10
|
+
---
|
|
11
|
+
Job installs dependencies with no cache: in scope
|
|
12
|
+
|
|
13
|
+
## Applies to
|
|
14
|
+
|
|
15
|
+
- [Job](/types/Job.md)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: post-synth-check
|
|
3
|
+
title: WGL050
|
|
4
|
+
description: Merge-request-reachable job is not interruptible
|
|
5
|
+
id: WGL050
|
|
6
|
+
severity: error
|
|
7
|
+
category: post-synth
|
|
8
|
+
lexicon: gitlab
|
|
9
|
+
docs: https://intentius.io/chant/lexicons/gitlab/rules/
|
|
10
|
+
---
|
|
11
|
+
Merge-request-reachable job is not interruptible
|
|
12
|
+
|
|
13
|
+
## Applies to
|
|
14
|
+
|
|
15
|
+
- [Job](/types/Job.md)
|
package/dist/okf/types/Job.md
CHANGED
|
@@ -84,3 +84,5 @@ resource_type: GitLab::CI::Job
|
|
|
84
84
|
- [WGL045](/rules/WGL045.md): Artifact path that may capture a credential file
|
|
85
85
|
- [WGL046](/rules/WGL046.md): Cache populated in a merge-request pipeline (poisoning risk)
|
|
86
86
|
- [WGL047](/rules/WGL047.md): Software fetched and piped to a shell without verification
|
|
87
|
+
- [WGL049](/rules/WGL049.md): Job installs dependencies with no cache: in scope
|
|
88
|
+
- [WGL050](/rules/WGL050.md): Merge-request-reachable job is not interruptible
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed step-builder wrapper for this lexicon's `gitlabPipeline` activity
|
|
3
|
+
* (chant #1288 Stage 2 — "regenerate the step builders as fully typed
|
|
4
|
+
* wrappers"). Core cannot import a lexicon's `*Args` types (lexicons depend
|
|
5
|
+
* on core, not the other way around), so a fully typed `gitlabPipeline` has
|
|
6
|
+
* to live beside {@link GitlabPipelineArgs} — here, next to
|
|
7
|
+
* `./activities/gitlab.ts`. `opts`'s type below IS `GitlabPipelineArgs`
|
|
8
|
+
* itself (via `Omit`/`WithStepRefs`) — never a hand-restated mirror.
|
|
9
|
+
*
|
|
10
|
+
* `core`'s own `gitlabPipeline` (in `@intentius/chant/op`, re-exported from
|
|
11
|
+
* `@intentius/chant-lexicon-temporal`) is UNCHANGED and produces a
|
|
12
|
+
* byte-identical `ActivityStep` for the same inputs — purely additive, and
|
|
13
|
+
* deliberately not swapped into the temporal barrel (see
|
|
14
|
+
* `lexicons/k8s/src/op/builders.ts`'s module doc for why: that would make
|
|
15
|
+
* temporal depend on this package at runtime). An author who wants the typed
|
|
16
|
+
* surface imports it from here — `@intentius/chant-lexicon-gitlab`.
|
|
17
|
+
*/
|
|
18
|
+
import { type ActivityStep, type NamedActivityStep, type WithStepRefs } from "@intentius/chant/op";
|
|
19
|
+
import type { GitlabPipelineArgs } from "./activities/gitlab.js";
|
|
20
|
+
/**
|
|
21
|
+
* Trigger and wait for a GitLab CI pipeline to complete — the fully typed
|
|
22
|
+
* twin of core's `gitlabPipeline`. `opts` is {@link GitlabPipelineArgs}
|
|
23
|
+
* itself, minus the positional `name`. Defaults to the `longInfra` profile.
|
|
24
|
+
*/
|
|
25
|
+
export declare const gitlabPipeline: (name: string, opts?: WithStepRefs<Omit<GitlabPipelineArgs, "name">> & {
|
|
26
|
+
profile?: ActivityStep["profile"];
|
|
27
|
+
id?: string;
|
|
28
|
+
}) => NamedActivityStep;
|
|
29
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/op/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,OAAO,YAAY,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,KACzG,iBAGF,CAAC"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,0BAA0B,CAAC;AAoB7F,eAAO,MAAM,YAAY,EAAE,aA8gB1B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for gitlab lint rules that match a bare TypeScript
|
|
3
|
+
* identifier (`Job`, ...) and need to know whether it actually came from
|
|
4
|
+
* this lexicon before flagging it (chant #1544 — cross-lexicon rule bleed:
|
|
5
|
+
* `new Job(...)` matches ANY lexicon's `Job` class by name alone, so a
|
|
6
|
+
* multi-lexicon project got gitlab-only rules applied to github/forgejo
|
|
7
|
+
* jobs that were never missing anything).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The module specifier a top-level import bound `name` to, whether via a
|
|
14
|
+
* named import (`import { Job } from "..."`, matched on its local —
|
|
15
|
+
* possibly aliased — binding) or a namespace import (`import * as gl from
|
|
16
|
+
* "..."`, matched on the namespace's own name). Undefined when `name` isn't
|
|
17
|
+
* bound by any top-level import in this file (no import at all, a
|
|
18
|
+
* re-export chain, a dynamic `require`, …) — the caller should treat that
|
|
19
|
+
* as "can't tell", not "not gitlab", to stay conservative for the common
|
|
20
|
+
* single-lexicon-project case (and every pre-#1544 unit test fixture, which
|
|
21
|
+
* has no import statements at all).
|
|
22
|
+
*/
|
|
23
|
+
export function importSourceFor(sourceFile: ts.SourceFile, name: string): string | undefined {
|
|
24
|
+
for (const stmt of sourceFile.statements) {
|
|
25
|
+
if (!ts.isImportDeclaration(stmt)) continue;
|
|
26
|
+
const moduleSpecifier = stmt.moduleSpecifier;
|
|
27
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
28
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
29
|
+
if (!namedBindings) continue;
|
|
30
|
+
|
|
31
|
+
if (ts.isNamespaceImport(namedBindings)) {
|
|
32
|
+
if (namedBindings.name.text === name) return moduleSpecifier.text;
|
|
33
|
+
} else if (ts.isNamedImports(namedBindings)) {
|
|
34
|
+
for (const element of namedBindings.elements) {
|
|
35
|
+
if (element.name.text === name) return moduleSpecifier.text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when `expression` (a `new`-expression callee) resolves — via a
|
|
44
|
+
* traced import — to a lexicon OTHER than gitlab. False for an unresolved
|
|
45
|
+
* import (conservative: still checked) or one that does resolve to gitlab.
|
|
46
|
+
*/
|
|
47
|
+
export function isJobFromAnotherLexicon(sourceFile: ts.SourceFile, expression: ts.LeftHandSideExpression): boolean {
|
|
48
|
+
let bindingName: string | undefined;
|
|
49
|
+
if (ts.isIdentifier(expression)) {
|
|
50
|
+
bindingName = expression.text;
|
|
51
|
+
} else if (ts.isPropertyAccessExpression(expression)) {
|
|
52
|
+
bindingName = ts.isIdentifier(expression.expression) ? expression.expression.text : undefined;
|
|
53
|
+
}
|
|
54
|
+
if (!bindingName) return false;
|
|
55
|
+
|
|
56
|
+
const source = importSourceFor(sourceFile, bindingName);
|
|
57
|
+
return source !== undefined && !source.includes("chant-lexicon-gitlab");
|
|
58
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
const VALID_EXECUTION_PROPS = new Set(["script", "trigger", "run"]);
|
|
12
13
|
|
|
@@ -31,6 +32,18 @@ export const missingScriptRule: LintRule = {
|
|
|
31
32
|
isJob = true;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// chant #1544 — cross-lexicon rule bleed: `new Job(...)` matches
|
|
36
|
+
// ANY lexicon's `Job` class by bare name alone (github's, forgejo's,
|
|
37
|
+
// ...), so a multi-lexicon project got WGL002 (gitlab-only) applied
|
|
38
|
+
// to github/forgejo jobs that were never missing anything — they
|
|
39
|
+
// just don't use `script`/`trigger`/`run`. Skip when the import
|
|
40
|
+
// resolves to a lexicon other than gitlab; an unresolved import
|
|
41
|
+
// (no `import` statement — most unit-test fixtures, a re-export) is
|
|
42
|
+
// "can't tell", so it keeps the previous, conservative behavior.
|
|
43
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
44
|
+
isJob = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
35
48
|
const props = node.arguments[0];
|
|
36
49
|
if (ts.isObjectLiteralExpression(props)) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
9
9
|
import * as ts from "typescript";
|
|
10
|
+
import { isJobFromAnotherLexicon } from "./import-source";
|
|
10
11
|
|
|
11
12
|
export const missingStageRule: LintRule = {
|
|
12
13
|
id: "WGL003",
|
|
@@ -28,6 +29,13 @@ export const missingStageRule: LintRule = {
|
|
|
28
29
|
isJob = true;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// chant #1544 — same cross-lexicon rule bleed as WGL002 (see
|
|
33
|
+
// missing-script.ts): a github/forgejo `Job` must not be judged
|
|
34
|
+
// against gitlab's `stage` convention.
|
|
35
|
+
if (isJob && isJobFromAnotherLexicon(sourceFile, expression)) {
|
|
36
|
+
isJob = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
if (isJob && node.arguments && node.arguments.length > 0) {
|
|
32
40
|
const props = node.arguments[0];
|
|
33
41
|
if (ts.isObjectLiteralExpression(props)) {
|
package/dist/rules/wgl019.ts
CHANGED
|
@@ -28,7 +28,12 @@ export const wgl019: PostSynthCheck = {
|
|
|
28
28
|
const stage = props.stage as string | undefined;
|
|
29
29
|
if (!stage || !DEPLOY_STAGES.has(stage.toLowerCase())) continue;
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// chant #1544 — `!props.retry` treated an explicit `retry: 0` the same
|
|
32
|
+
// as no `retry` at all, warning on a job that stated its policy
|
|
33
|
+
// ("retrying a deploy without a human is deliberate") as if it hadn't.
|
|
34
|
+
// `retry: 0` is falsy but present — only an actually-absent key means
|
|
35
|
+
// "no retry strategy".
|
|
36
|
+
if (props.retry === undefined) {
|
|
32
37
|
diagnostics.push({
|
|
33
38
|
checkId: "WGL019",
|
|
34
39
|
severity: "info",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGL049: Dependency Install Without a Cache
|
|
3
|
+
*
|
|
4
|
+
* Flags a job whose `script:` runs a package-manager install command (`npm
|
|
5
|
+
* ci`, `pip install`, `bundle install`, etc.) with no `cache:` in scope —
|
|
6
|
+
* neither on the job itself, nor at the pipeline's `default:`/top level.
|
|
7
|
+
* Every run re-fetches the same dependencies from a cold cache. A job that
|
|
8
|
+
* `extends:` another config is left alone — the cache may be inherited and
|
|
9
|
+
* this check has no way to confirm that. Efficiency (#444), not a
|
|
10
|
+
* correctness or security issue.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
14
|
+
import { getPrimaryOutput, extractJobs, extractJobSection } from "./yaml-helpers";
|
|
15
|
+
|
|
16
|
+
const DEP_INSTALL_RE = /\b(npm (install|ci)|yarn install|pnpm install|pip install|pip3 install|bundle install|composer install|go mod download|mvn (install|dependency:resolve))\b/i;
|
|
17
|
+
|
|
18
|
+
function hasPipelineWideCache(yaml: string): boolean {
|
|
19
|
+
const sections = yaml.split("\n\n");
|
|
20
|
+
if (sections.some((s) => /^cache:/.test(s))) return true;
|
|
21
|
+
const defaultSection = sections.find((s) => /^default:/.test(s));
|
|
22
|
+
return !!defaultSection && /\n\s+cache:/.test(defaultSection);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const wgl049: PostSynthCheck = {
|
|
26
|
+
id: "WGL049",
|
|
27
|
+
description: "Job installs dependencies with no cache: in scope",
|
|
28
|
+
|
|
29
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
30
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
31
|
+
|
|
32
|
+
for (const [, output] of ctx.outputs) {
|
|
33
|
+
const yaml = getPrimaryOutput(output);
|
|
34
|
+
if (hasPipelineWideCache(yaml)) continue;
|
|
35
|
+
|
|
36
|
+
for (const [jobName, job] of extractJobs(yaml)) {
|
|
37
|
+
if (jobName.startsWith(".")) continue; // hidden/template job, not run directly
|
|
38
|
+
if (job.extends && job.extends.length > 0) continue; // cache may be inherited; can't confirm
|
|
39
|
+
|
|
40
|
+
const section = extractJobSection(yaml, jobName);
|
|
41
|
+
if (!section) continue;
|
|
42
|
+
if (/^\s+cache:/m.test(section)) continue; // job-level cache present
|
|
43
|
+
if (!DEP_INSTALL_RE.test(section)) continue;
|
|
44
|
+
|
|
45
|
+
diagnostics.push({
|
|
46
|
+
checkId: "WGL049",
|
|
47
|
+
severity: "info",
|
|
48
|
+
message: `Job "${jobName}" installs dependencies with no cache: in scope — every run re-fetches from the registry. Add a cache: covering the dependency directory.`,
|
|
49
|
+
entity: jobName,
|
|
50
|
+
lexicon: "gitlab",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return diagnostics;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGL050: Merge-Request Job Missing interruptible
|
|
3
|
+
*
|
|
4
|
+
* Flags a job reachable from merge-request pipelines that doesn't set
|
|
5
|
+
* `interruptible: true`. Without it, GitLab's auto-cancel-redundant-pipelines
|
|
6
|
+
* setting can't cancel the job when a new push supersedes it, so the runner
|
|
7
|
+
* keeps spending capacity on a pipeline nobody wants anymore. Deploy jobs are
|
|
8
|
+
* left alone — cancelling mid-deploy is its own hazard, not an efficiency
|
|
9
|
+
* win. Efficiency (#444), not a correctness or security issue.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
13
|
+
import { getPrimaryOutput, isMergeRequestReachable } from "./yaml-helpers";
|
|
14
|
+
|
|
15
|
+
const RESERVED_TOP_LEVEL_KEYS = new Set(["stages", "default", "workflow", "variables", "include", "cache"]);
|
|
16
|
+
|
|
17
|
+
export const wgl050: PostSynthCheck = {
|
|
18
|
+
id: "WGL050",
|
|
19
|
+
description: "Merge-request-reachable job is not interruptible",
|
|
20
|
+
|
|
21
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
22
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
23
|
+
|
|
24
|
+
for (const [, output] of ctx.outputs) {
|
|
25
|
+
const yaml = getPrimaryOutput(output);
|
|
26
|
+
|
|
27
|
+
for (const section of yaml.split("\n\n")) {
|
|
28
|
+
const top = section.split("\n")[0]?.match(/^(\.?[a-z][a-z0-9_.-]*):/i);
|
|
29
|
+
if (!top) continue;
|
|
30
|
+
const jobName = top[1];
|
|
31
|
+
if (jobName.startsWith(".") || RESERVED_TOP_LEVEL_KEYS.has(jobName)) continue;
|
|
32
|
+
if (/deploy/i.test(jobName)) continue; // cancelling mid-deploy is a hazard, not a win
|
|
33
|
+
|
|
34
|
+
if (!isMergeRequestReachable(section)) continue;
|
|
35
|
+
if (/^\s+interruptible:\s*true\s*$/m.test(section)) continue;
|
|
36
|
+
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
checkId: "WGL050",
|
|
39
|
+
severity: "info",
|
|
40
|
+
message: `Job "${jobName}" runs on merge-request pipelines but is not interruptible: true — when a new commit supersedes this pipeline, this job keeps running instead of being cancelled. Add interruptible: true.`,
|
|
41
|
+
entity: jobName,
|
|
42
|
+
lexicon: "gitlab",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-gitlab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.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.50.0",
|
|
71
|
+
"@intentius/chant-lexicon-github": "^0.50.0",
|
|
72
72
|
"typescript": "^5.9.3"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|