@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.
Files changed (50) hide show
  1. package/dist/components/generate-op-pipeline.d.ts +37 -0
  2. package/dist/components/generate-op-pipeline.d.ts.map +1 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/integrity.json +8 -5
  6. package/dist/lint/audit-catalog.d.ts.map +1 -1
  7. package/dist/lint/post-synth/index.d.ts.map +1 -1
  8. package/dist/lint/post-synth/wgl019.d.ts.map +1 -1
  9. package/dist/lint/post-synth/wgl049.d.ts +14 -0
  10. package/dist/lint/post-synth/wgl049.d.ts.map +1 -0
  11. package/dist/lint/post-synth/wgl050.d.ts +13 -0
  12. package/dist/lint/post-synth/wgl050.d.ts.map +1 -0
  13. package/dist/lint/rules/import-source.d.ts +28 -0
  14. package/dist/lint/rules/import-source.d.ts.map +1 -0
  15. package/dist/lint/rules/missing-script.d.ts.map +1 -1
  16. package/dist/lint/rules/missing-stage.d.ts.map +1 -1
  17. package/dist/manifest.json +1 -1
  18. package/dist/okf/index.md +2 -0
  19. package/dist/okf/rules/WGL049.md +15 -0
  20. package/dist/okf/rules/WGL050.md +15 -0
  21. package/dist/okf/types/Job.md +2 -0
  22. package/dist/op/builders.d.ts +29 -0
  23. package/dist/op/builders.d.ts.map +1 -0
  24. package/dist/plugin.d.ts.map +1 -1
  25. package/dist/rules/import-source.ts +58 -0
  26. package/dist/rules/missing-script.ts +13 -0
  27. package/dist/rules/missing-stage.ts +8 -0
  28. package/dist/rules/wgl019.ts +6 -1
  29. package/dist/rules/wgl049.ts +57 -0
  30. package/dist/rules/wgl050.ts +49 -0
  31. package/package.json +3 -3
  32. package/src/components/generate-op-pipeline.test.ts +94 -0
  33. package/src/components/generate-op-pipeline.ts +111 -0
  34. package/src/index.ts +8 -0
  35. package/src/lint/audit-catalog.ts +5 -0
  36. package/src/lint/post-synth/index.ts +4 -0
  37. package/src/lint/post-synth/wgl019.test.ts +8 -0
  38. package/src/lint/post-synth/wgl019.ts +6 -1
  39. package/src/lint/post-synth/wgl049.test.ts +71 -0
  40. package/src/lint/post-synth/wgl049.ts +57 -0
  41. package/src/lint/post-synth/wgl050.test.ts +57 -0
  42. package/src/lint/post-synth/wgl050.ts +49 -0
  43. package/src/lint/rules/import-source.ts +58 -0
  44. package/src/lint/rules/missing-script.ts +13 -0
  45. package/src/lint/rules/missing-stage.ts +8 -0
  46. package/src/lint/rules/rules.test.ts +60 -0
  47. package/src/op/builders.test.ts +43 -0
  48. package/src/op/builders.ts +39 -0
  49. package/src/plugin.test.ts +1 -1
  50. 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";
@@ -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;AAGxC,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"}
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"}
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "c3703cd4ebf4f650408c0bd187c1a242041dfdb0461b52d67db198d2cf486c67",
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/missing-script.ts": "1dba062d77fa248fc02857fd08bc7789f90eefd3206e552ac63782a04b843888",
10
- "rules/missing-stage.ts": "ce810703cba6c8942282c4ebaeff23fe3312c4ecc362acce05baf85d0fd32ea3",
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": "d54038ed7b24ac8e8d59efba9645ef435fe090647138fff1946443dcd75ebfd1",
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": "b01961b1bd64c168d5d5d28d9d6cd160745f706b69b7451fe5b9218dff4c1d66"
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,CAwCvD,CAAC"}
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;AAyCvE,eAAO,MAAM,eAAe,EAAE,cAAc,EAwC3C,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,cA+BpB,CAAC"}
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;AAKxF,eAAO,MAAM,iBAAiB,EAAE,QAoD/B,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;AAGxF,eAAO,MAAM,gBAAgB,EAAE,QAmD9B,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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitlab",
3
- "version": "0.46.0",
3
+ "version": "0.50.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GitLab",
6
6
  "intrinsics": [
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)
@@ -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"}
@@ -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;AAmB7F,eAAO,MAAM,YAAY,EAAE,aA4gB1B,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)) {
@@ -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
- if (!props.retry) {
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.46.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.46.0",
71
- "@intentius/chant-lexicon-github": "^0.46.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": {