@intentius/chant-lexicon-gitlab 0.124.31 → 0.124.32

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 (133) hide show
  1. package/dist/codegen/docs-cli.js +2 -0
  2. package/dist/codegen/docs.js +157 -0
  3. package/dist/codegen/fetch.js +93 -0
  4. package/dist/codegen/generate-cli.js +30 -0
  5. package/dist/codegen/generate-lexicon.js +36 -0
  6. package/dist/codegen/generate-typescript.js +105 -0
  7. package/dist/codegen/generate.js +125 -0
  8. package/dist/codegen/naming.js +48 -0
  9. package/dist/codegen/package.js +41 -0
  10. package/dist/codegen/parse.js +539 -0
  11. package/dist/codegen/patches.js +84 -0
  12. package/dist/codegen/typecheck.js +4 -0
  13. package/dist/components/ci-jobs.js +16 -0
  14. package/dist/components/generate-op-pipeline.js +288 -0
  15. package/dist/components/generate-op-waves-pipeline.js +125 -0
  16. package/dist/components/generate-pipeline.d.ts.map +1 -1
  17. package/dist/components/generate-pipeline.js +369 -0
  18. package/dist/composites/catalog.js +418 -0
  19. package/dist/composites/docker-build.js +38 -0
  20. package/dist/composites/index.js +5 -0
  21. package/dist/composites/mr-plan-report.js +42 -0
  22. package/dist/composites/node-pipeline.js +67 -0
  23. package/dist/composites/python-pipeline.js +45 -0
  24. package/dist/composites/review-app.js +32 -0
  25. package/dist/coverage.js +35 -0
  26. package/dist/generated/index.js +25 -0
  27. package/dist/generated/runtime.js +4 -0
  28. package/dist/graph-edges.js +202 -0
  29. package/dist/import/generator.js +196 -0
  30. package/dist/import/parser.js +111 -0
  31. package/dist/index.js +26 -0
  32. package/dist/integrity.json +3 -3
  33. package/dist/intrinsics.js +33 -0
  34. package/dist/lint/audit-catalog.js +53 -0
  35. package/dist/lint/audit-lineage.js +64 -0
  36. package/dist/lint/post-synth/index.js +84 -0
  37. package/dist/lint/post-synth/wgl010.js +34 -0
  38. package/dist/lint/post-synth/wgl011.js +48 -0
  39. package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
  40. package/dist/lint/post-synth/wgl012.js +68 -0
  41. package/dist/lint/post-synth/wgl013.js +57 -0
  42. package/dist/lint/post-synth/wgl014.js +43 -0
  43. package/dist/lint/post-synth/wgl015.js +70 -0
  44. package/dist/lint/post-synth/wgl016.js +70 -0
  45. package/dist/lint/post-synth/wgl017.js +38 -0
  46. package/dist/lint/post-synth/wgl018.js +34 -0
  47. package/dist/lint/post-synth/wgl019.js +43 -0
  48. package/dist/lint/post-synth/wgl020.js +47 -0
  49. package/dist/lint/post-synth/wgl021.js +52 -0
  50. package/dist/lint/post-synth/wgl022.js +38 -0
  51. package/dist/lint/post-synth/wgl023.js +44 -0
  52. package/dist/lint/post-synth/wgl024.js +41 -0
  53. package/dist/lint/post-synth/wgl025.js +42 -0
  54. package/dist/lint/post-synth/wgl026.js +58 -0
  55. package/dist/lint/post-synth/wgl027.js +49 -0
  56. package/dist/lint/post-synth/wgl028.js +58 -0
  57. package/dist/lint/post-synth/wgl029.js +55 -0
  58. package/dist/lint/post-synth/wgl030.js +43 -0
  59. package/dist/lint/post-synth/wgl031.js +33 -0
  60. package/dist/lint/post-synth/wgl032.js +64 -0
  61. package/dist/lint/post-synth/wgl033.js +32 -0
  62. package/dist/lint/post-synth/wgl034.js +37 -0
  63. package/dist/lint/post-synth/wgl035.js +40 -0
  64. package/dist/lint/post-synth/wgl036.js +38 -0
  65. package/dist/lint/post-synth/wgl037.js +41 -0
  66. package/dist/lint/post-synth/wgl038.js +43 -0
  67. package/dist/lint/post-synth/wgl039.js +34 -0
  68. package/dist/lint/post-synth/wgl040.js +35 -0
  69. package/dist/lint/post-synth/wgl041.js +53 -0
  70. package/dist/lint/post-synth/wgl042.js +41 -0
  71. package/dist/lint/post-synth/wgl043.js +39 -0
  72. package/dist/lint/post-synth/wgl044.js +35 -0
  73. package/dist/lint/post-synth/wgl045.js +54 -0
  74. package/dist/lint/post-synth/wgl046.js +40 -0
  75. package/dist/lint/post-synth/wgl047.js +34 -0
  76. package/dist/lint/post-synth/wgl048.js +32 -0
  77. package/dist/lint/post-synth/wgl049.js +53 -0
  78. package/dist/lint/post-synth/wgl050.js +42 -0
  79. package/dist/lint/post-synth/yaml-helpers.js +432 -0
  80. package/dist/lint/rules/artifact-no-expiry.js +53 -0
  81. package/dist/lint/rules/data/known-include-sources.js +44 -0
  82. package/dist/lint/rules/data/untrusted-variables.js +30 -0
  83. package/dist/lint/rules/deprecated-only-except.js +46 -0
  84. package/dist/lint/rules/import-source.js +61 -0
  85. package/dist/lint/rules/index.js +7 -0
  86. package/dist/lint/rules/missing-script.js +67 -0
  87. package/dist/lint/rules/missing-stage.js +60 -0
  88. package/dist/lsp/completions.d.ts.map +1 -1
  89. package/dist/lsp/completions.js +18 -0
  90. package/dist/lsp/hover.d.ts.map +1 -1
  91. package/dist/lsp/hover.js +33 -0
  92. package/dist/manifest.json +1 -1
  93. package/dist/mcp/context-tools.js +262 -0
  94. package/dist/migrate/from-github/actions/index.js +17 -0
  95. package/dist/migrate/from-github/actions/registry.js +61 -0
  96. package/dist/migrate/from-github/actions/tier-1.js +298 -0
  97. package/dist/migrate/from-github/actions/tier-2.js +241 -0
  98. package/dist/migrate/from-github/actions/tier-3.js +96 -0
  99. package/dist/migrate/from-github/composites/patterns.js +151 -0
  100. package/dist/migrate/from-github/composites/rewriter.js +22 -0
  101. package/dist/migrate/from-github/diagnostics.js +53 -0
  102. package/dist/migrate/from-github/emit-yaml.js +121 -0
  103. package/dist/migrate/from-github/expressions.js +275 -0
  104. package/dist/migrate/from-github/index.js +96 -0
  105. package/dist/migrate/from-github/provenance.js +35 -0
  106. package/dist/migrate/from-github/rules.js +74 -0
  107. package/dist/migrate/from-github/security.js +189 -0
  108. package/dist/migrate/from-github/stages.js +156 -0
  109. package/dist/migrate/from-github/transformer.js +633 -0
  110. package/dist/op/activities/gitlab.js +38 -0
  111. package/dist/op/activities/index.js +10 -0
  112. package/dist/op/builders.js +26 -0
  113. package/dist/package-cli.js +21 -0
  114. package/dist/plugin.js +536 -0
  115. package/dist/rules/wgl012.ts +2 -2
  116. package/dist/serializer.js +197 -0
  117. package/dist/testdata/create-fixture.js +40 -0
  118. package/dist/testdata/load-fixtures.js +20 -0
  119. package/dist/validate-cli.js +16 -0
  120. package/dist/validate.js +29 -0
  121. package/dist/variables.js +30 -0
  122. package/package.json +8 -8
  123. package/src/codegen/fetch.ts +2 -2
  124. package/src/codegen/package.ts +2 -2
  125. package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
  126. package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
  127. package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
  128. package/src/components/generate-pipeline.ts +60 -27
  129. package/src/components/generate-pr-pipeline.test.ts +14 -9
  130. package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
  131. package/src/lint/post-synth/wgl012.ts +2 -2
  132. package/src/lsp/completions.ts +2 -1
  133. package/src/lsp/hover.ts +2 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Coverage analysis for GitLab CI lexicon.
3
+ *
4
+ * Measures how many of the CI schema's definitions are covered
5
+ * by the generated lexicon entities.
6
+ */
7
+ import { readFileSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ import { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds, } from "@intentius/chant/codegen/coverage";
11
+ export { computeCoverage, overallPct, formatSummary, formatVerbose, checkThresholds };
12
+ /**
13
+ * Run coverage analysis for the GitLab lexicon.
14
+ */
15
+ export async function analyzeGitLabCoverage(opts) {
16
+ const basePath = opts?.basePath ?? dirname(dirname(fileURLToPath(import.meta.url)));
17
+ const lexiconPath = join(basePath, "src", "generated", "lexicon-gitlab.json");
18
+ const content = readFileSync(lexiconPath, "utf-8");
19
+ const report = computeCoverage(content);
20
+ if (opts?.verbose) {
21
+ console.error(formatVerbose(report));
22
+ }
23
+ else {
24
+ console.error(formatSummary(report));
25
+ }
26
+ if (typeof opts?.minOverall === "number") {
27
+ const result = checkThresholds(report, { minOverallPct: opts.minOverall });
28
+ if (!result.ok) {
29
+ for (const v of result.violations)
30
+ console.error(` FAIL: ${v}`);
31
+ throw new Error("Coverage below threshold");
32
+ }
33
+ }
34
+ return report;
35
+ }
@@ -0,0 +1,25 @@
1
+ // Code generated by chant generate. DO NOT EDIT.
2
+ import { createResource, createProperty } from "./runtime.js";
3
+ export const Default = /* @__PURE__ */ createResource("GitLab::CI::Default", "gitlab", {});
4
+ export const Job = /* @__PURE__ */ createResource("GitLab::CI::Job", "gitlab", {});
5
+ export const Pipeline = /* @__PURE__ */ createResource("GitLab::CI::Pipeline", "gitlab", {});
6
+ export const Workflow = /* @__PURE__ */ createResource("GitLab::CI::Workflow", "gitlab", {});
7
+ export const AllowFailure = /* @__PURE__ */ createProperty("GitLab::CI::AllowFailure", "gitlab");
8
+ export const Artifacts = /* @__PURE__ */ createProperty("GitLab::CI::Artifacts", "gitlab");
9
+ export const AutoCancel = /* @__PURE__ */ createProperty("GitLab::CI::AutoCancel", "gitlab");
10
+ export const Cache = /* @__PURE__ */ createProperty("GitLab::CI::Cache", "gitlab");
11
+ export const Environment = /* @__PURE__ */ createProperty("GitLab::CI::Environment", "gitlab");
12
+ export const Image = /* @__PURE__ */ createProperty("GitLab::CI::Image", "gitlab");
13
+ export const Include = /* @__PURE__ */ createProperty("GitLab::CI::Include", "gitlab");
14
+ export const Inherit = /* @__PURE__ */ createProperty("GitLab::CI::Inherit", "gitlab");
15
+ export const Need = /* @__PURE__ */ createProperty("GitLab::CI::Need", "gitlab");
16
+ export const Parallel = /* @__PURE__ */ createProperty("GitLab::CI::Parallel", "gitlab");
17
+ export const Release = /* @__PURE__ */ createProperty("GitLab::CI::Release", "gitlab");
18
+ export const Retry = /* @__PURE__ */ createProperty("GitLab::CI::Retry", "gitlab");
19
+ export const Rule = /* @__PURE__ */ createProperty("GitLab::CI::Rule", "gitlab");
20
+ export const Service = /* @__PURE__ */ createProperty("GitLab::CI::Service", "gitlab");
21
+ export const Trigger = /* @__PURE__ */ createProperty("GitLab::CI::Trigger", "gitlab");
22
+ export const WorkflowRule = /* @__PURE__ */ createProperty("GitLab::CI::WorkflowRule", "gitlab");
23
+ // Re-exports for convenience
24
+ export { reference } from "../intrinsics.js";
25
+ export { CI } from "../variables.js";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Runtime factory constructors — re-exported from core.
3
+ */
4
+ export { createResource, createProperty } from "@intentius/chant/runtime";
@@ -0,0 +1,202 @@
1
+ /**
2
+ * GitLab CI dependencies as graph edges (#3910).
3
+ *
4
+ * A GitLab job names everything it depends on as a string: the jobs it
5
+ * `needs`, the job it `extends`, the pipeline it `trigger`s, and the pipeline
6
+ * names the files it `include`s. `chant graph` cannot read those, so this
7
+ * module resolves them against the declared entities and reports each one
8
+ * through the plugin's `graphEdges` hook:
9
+ *
10
+ * - `needs` job -> job, from `needs:` (a name, `{ job }`, or the Job
11
+ * itself); a job without `needs:` waits for the stages before
12
+ * its own, reported as `needs` via `stage`
13
+ * - `extends` job -> the job it inherits from
14
+ * - `triggers` job -> the child or multi-project pipeline it starts
15
+ * - `includes` pipeline -> each file its `include:` pulls in
16
+ *
17
+ * Jobs are named in the YAML by their kebab-cased export name, as
18
+ * ./serializer.ts writes them. Cross-pipeline references (`trigger:`,
19
+ * `include:`, `needs: { project }` and `needs: { pipeline }`) name a pipeline
20
+ * this project does not generate, so they are reported as dangling and
21
+ * marked `crossPipeline`. So is any job name nothing declares: a hidden
22
+ * template from an included file, a typo.
23
+ */
24
+ import { isPropertyDeclarable } from "@intentius/chant/declarable";
25
+ const JOB = "GitLab::CI::Job";
26
+ const PIPELINE = "GitLab::CI::Pipeline";
27
+ const PIPELINE_LEVEL = new Set([PIPELINE, "GitLab::CI::Default", "GitLab::CI::Workflow"]);
28
+ /** The YAML name ./serializer.ts gives a job. */
29
+ function yamlName(name) {
30
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
31
+ }
32
+ function entityType(value) {
33
+ if (!value || typeof value !== "object")
34
+ return undefined;
35
+ const t = value.entityType;
36
+ return typeof t === "string" ? t : undefined;
37
+ }
38
+ /** A Declarable's props bag, or the plain object itself. */
39
+ function propsOf(value) {
40
+ if (!value || typeof value !== "object" || Array.isArray(value))
41
+ return undefined;
42
+ if (entityType(value) !== undefined) {
43
+ const props = value.props;
44
+ return props && typeof props === "object" ? props : undefined;
45
+ }
46
+ return value;
47
+ }
48
+ const asList = (value) => (Array.isArray(value) ? value : value === undefined || value === null ? [] : [value]);
49
+ /** GitLab's default stages, used when nothing declares a `stages:` list. */
50
+ const DEFAULT_STAGES = ["build", "test", "deploy"];
51
+ /**
52
+ * The stage order GitLab runs, as ./serializer.ts emits it: a Pipeline's
53
+ * `stages:` first, then every stage a job names that the list leaves out, in
54
+ * first use. With neither, GitLab's defaults. `.pre` and `.post` always
55
+ * bracket the list.
56
+ */
57
+ function stageOrder(pipelines, jobs) {
58
+ const stages = new Set();
59
+ for (const p of pipelines)
60
+ for (const s of asList(p.stages))
61
+ if (typeof s === "string")
62
+ stages.add(s);
63
+ for (const j of jobs)
64
+ if (typeof j.props.stage === "string")
65
+ stages.add(j.props.stage);
66
+ const list = stages.size > 0 ? [...stages] : DEFAULT_STAGES;
67
+ return [".pre", ...list.filter((s) => s !== ".pre" && s !== ".post"), ".post"];
68
+ }
69
+ /** A file an `include:` or `trigger: { include }` entry names, as written. */
70
+ function includeTarget(entry) {
71
+ if (typeof entry === "string")
72
+ return entry;
73
+ const p = propsOf(entry);
74
+ if (!p)
75
+ return undefined;
76
+ for (const key of ["local", "remote", "template", "component", "artifact"]) {
77
+ if (typeof p[key] === "string")
78
+ return p[key];
79
+ }
80
+ if (typeof p.project === "string") {
81
+ const files = asList(p.file).filter((f) => typeof f === "string");
82
+ const ref = typeof p.ref === "string" ? `@${p.ref}` : "";
83
+ return `${p.project}${ref}:${files.join(",")}`;
84
+ }
85
+ return undefined;
86
+ }
87
+ /** The graph edges and pipeline membership a GitLab CI project declares. */
88
+ export function gitlabGraphEdges(entities) {
89
+ const names = new Map();
90
+ const jobs = [];
91
+ const pipelineNodes = [];
92
+ const pipelineEntities = [];
93
+ for (const [name, entity] of entities) {
94
+ names.set(entity, name);
95
+ if (isPropertyDeclarable(entity))
96
+ continue;
97
+ const type = entityType(entity);
98
+ if (type === JOB)
99
+ jobs.push({ node: name, key: yamlName(name), props: propsOf(entity) ?? {} });
100
+ else if (type && PIPELINE_LEVEL.has(type)) {
101
+ pipelineNodes.push(name);
102
+ if (type === PIPELINE)
103
+ pipelineEntities.push({ node: name, props: propsOf(entity) ?? {} });
104
+ }
105
+ }
106
+ if (jobs.length === 0 && pipelineEntities.length === 0)
107
+ return undefined;
108
+ const byKey = new Map(jobs.map((j) => [j.key, j.node]));
109
+ const references = [];
110
+ // A job name as written, or a Job object held directly.
111
+ const jobKey = (value) => {
112
+ if (typeof value === "string")
113
+ return value;
114
+ const name = names.get(value);
115
+ return name !== undefined && entityType(value) === JOB ? yamlName(name) : undefined;
116
+ };
117
+ // Stage order, for the jobs that say nothing about `needs:`.
118
+ const stages = stageOrder(pipelineEntities.map((p) => p.props), jobs);
119
+ const stageOf = (j) => stages.indexOf(typeof j.props.stage === "string" ? j.props.stage : "test");
120
+ const atStage = new Map();
121
+ for (const j of jobs) {
122
+ const s = stageOf(j);
123
+ if (s < 0)
124
+ continue;
125
+ const arr = atStage.get(s) ?? [];
126
+ arr.push(j);
127
+ atStage.set(s, arr);
128
+ }
129
+ for (const job of jobs) {
130
+ const from = job.node;
131
+ if (job.props.needs === undefined) {
132
+ // Stage order: wait for every job in the nearest earlier stage that has
133
+ // any. Those jobs wait for the stage before them in turn, unless one of
134
+ // them uses `needs:` and starts early, in which case this job also
135
+ // waits for the stage before, and so on back.
136
+ for (let s = stageOf(job) - 1; s >= 0; s--) {
137
+ const earlier = atStage.get(s);
138
+ if (!earlier)
139
+ continue;
140
+ for (const e of earlier)
141
+ references.push({ from, to: e.node, kind: "needs", viaAttr: "stage" });
142
+ if (earlier.every((e) => e.props.needs === undefined))
143
+ break;
144
+ }
145
+ }
146
+ for (const need of asList(job.props.needs)) {
147
+ const key = jobKey(need);
148
+ if (key !== undefined) {
149
+ const to = byKey.get(key);
150
+ references.push(to ? { from, to, kind: "needs", viaAttr: "needs" } : { from, to: key, kind: "needs", viaAttr: "needs", dangling: true });
151
+ continue;
152
+ }
153
+ const p = propsOf(need);
154
+ if (!p || typeof p.job !== "string")
155
+ continue;
156
+ if (typeof p.project === "string") {
157
+ const ref = typeof p.ref === "string" ? `@${p.ref}` : "";
158
+ references.push({ from, to: `${p.project}${ref}:${p.job}`, kind: "needs", viaAttr: "needs.project", crossPipeline: true, dangling: true });
159
+ }
160
+ else if (typeof p.pipeline === "string") {
161
+ references.push({ from, to: `${p.pipeline}:${p.job}`, kind: "needs", viaAttr: "needs.pipeline", crossPipeline: true, dangling: true });
162
+ }
163
+ else {
164
+ const to = byKey.get(p.job);
165
+ references.push(to ? { from, to, kind: "needs", viaAttr: "needs" } : { from, to: p.job, kind: "needs", viaAttr: "needs", dangling: true });
166
+ }
167
+ }
168
+ for (const parent of asList(job.props.extends)) {
169
+ const key = jobKey(parent);
170
+ if (key === undefined)
171
+ continue;
172
+ const to = byKey.get(key);
173
+ references.push(to ? { from, to, kind: "extends", viaAttr: "extends" } : { from, to: key, kind: "extends", viaAttr: "extends", dangling: true });
174
+ }
175
+ const trigger = job.props.trigger;
176
+ if (typeof trigger === "string") {
177
+ references.push({ from, to: trigger, kind: "triggers", viaAttr: "trigger", crossPipeline: true, dangling: true });
178
+ }
179
+ else {
180
+ const t = propsOf(trigger);
181
+ if (t && typeof t.project === "string") {
182
+ const branch = typeof t.branch === "string" ? `@${t.branch}` : "";
183
+ references.push({ from, to: `${t.project}${branch}`, kind: "triggers", viaAttr: "trigger.project", crossPipeline: true, dangling: true });
184
+ }
185
+ for (const entry of asList(t?.include)) {
186
+ const file = includeTarget(entry);
187
+ if (file)
188
+ references.push({ from, to: file, kind: "triggers", viaAttr: "trigger.include", crossPipeline: true, dangling: true });
189
+ }
190
+ }
191
+ }
192
+ for (const p of pipelineEntities) {
193
+ for (const entry of asList(p.props.include)) {
194
+ const file = includeTarget(entry);
195
+ if (file)
196
+ references.push({ from: p.node, to: file, kind: "includes", viaAttr: "include", crossPipeline: true, dangling: true });
197
+ }
198
+ }
199
+ // One project, one `.gitlab-ci.yml`: every job and pipeline-level entity is in it.
200
+ const members = [...pipelineNodes, ...jobs.map((j) => j.node)].sort();
201
+ return { references, pipelines: { ".gitlab-ci.yml": members } };
202
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * TypeScript code generator for GitLab CI import.
3
+ *
4
+ * Converts a TemplateIR (from parsed .gitlab-ci.yml) into TypeScript
5
+ * source code using the @intentius/chant-lexicon-gitlab constructors.
6
+ */
7
+ /**
8
+ * Map GitLab CI entity types to their constructor names.
9
+ */
10
+ const TYPE_TO_CLASS = {
11
+ "GitLab::CI::Job": "Job",
12
+ "GitLab::CI::Default": "Default",
13
+ "GitLab::CI::Workflow": "Workflow",
14
+ "GitLab::CI::Pipeline": "Pipeline",
15
+ };
16
+ /**
17
+ * Composite IR types emit as `FunctionName({...})` factory calls rather
18
+ * than `new ClassName({...})` constructors. The `chant migrate --use-
19
+ * composites` rewriter rewrites recognised IR shapes into these sentinel
20
+ * types (see `lexicons/gitlab/src/migrate/from-github/composites/`).
21
+ */
22
+ const COMPOSITE_TYPE_TO_FN = {
23
+ "GitLab::Composite::NodePipeline": "NodePipeline",
24
+ "GitLab::Composite::NodeCI": "NodeCI",
25
+ };
26
+ /**
27
+ * Properties that reference known property entities.
28
+ */
29
+ const PROPERTY_CONSTRUCTORS = {
30
+ artifacts: "Artifacts",
31
+ cache: "Cache",
32
+ image: "Image",
33
+ retry: "Retry",
34
+ allow_failure: "AllowFailure",
35
+ parallel: "Parallel",
36
+ environment: "Environment",
37
+ trigger: "Trigger",
38
+ auto_cancel: "AutoCancel",
39
+ inherit: "Inherit",
40
+ };
41
+ /** An object key as TypeScript accepts it: bare when it is an identifier, quoted otherwise. */
42
+ const tsKey = (key) => (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key));
43
+ /**
44
+ * Generate TypeScript source code from a GitLab CI IR.
45
+ */
46
+ export class GitLabGenerator {
47
+ generate(ir) {
48
+ const lines = [];
49
+ // Collect which constructors are needed
50
+ const usedConstructors = new Set();
51
+ for (const resource of ir.resources) {
52
+ const cls = TYPE_TO_CLASS[resource.type];
53
+ if (cls)
54
+ usedConstructors.add(cls);
55
+ const fn = COMPOSITE_TYPE_TO_FN[resource.type];
56
+ if (fn)
57
+ usedConstructors.add(fn);
58
+ // Check properties for nested constructors
59
+ this.collectNestedConstructors(resource.properties, usedConstructors);
60
+ }
61
+ // Import statement
62
+ const imports = [...usedConstructors].sort().join(", ");
63
+ lines.push(`import { ${imports} } from "@intentius/chant-lexicon-gitlab";`);
64
+ lines.push("");
65
+ // A Pipeline resource declares the stages and includes (#3742); without
66
+ // one, the metadata is written as comments.
67
+ const hasPipeline = ir.resources.some((r) => r.type === "GitLab::CI::Pipeline");
68
+ // Emit stages if present in metadata
69
+ if (!hasPipeline && ir.metadata?.stages && Array.isArray(ir.metadata.stages)) {
70
+ lines.push(`// Pipeline stages: ${ir.metadata.stages.join(", ")}`);
71
+ lines.push("");
72
+ }
73
+ // Emit includes as comments
74
+ if (!hasPipeline && ir.metadata?.include) {
75
+ lines.push("// Imported includes (not converted):");
76
+ const includes = Array.isArray(ir.metadata.include) ? ir.metadata.include : [ir.metadata.include];
77
+ for (const inc of includes) {
78
+ if (typeof inc === "string") {
79
+ lines.push(`// - ${inc}`);
80
+ }
81
+ else if (typeof inc === "object" && inc !== null) {
82
+ lines.push(`// - ${JSON.stringify(inc)}`);
83
+ }
84
+ }
85
+ lines.push("");
86
+ }
87
+ // Emit resources
88
+ for (const resource of ir.resources) {
89
+ const cls = TYPE_TO_CLASS[resource.type];
90
+ const fn = COMPOSITE_TYPE_TO_FN[resource.type];
91
+ if (!cls && !fn)
92
+ continue;
93
+ const varName = resource.logicalId;
94
+ const propsStr = this.emitProps(resource.properties, 1);
95
+ if (fn) {
96
+ lines.push(`export const ${varName} = ${fn}(${propsStr});`);
97
+ }
98
+ else {
99
+ lines.push(`export const ${varName} = new ${cls}(${propsStr});`);
100
+ }
101
+ lines.push("");
102
+ }
103
+ return [{ path: "main.ts", content: lines.join("\n") }];
104
+ }
105
+ collectNestedConstructors(props, used) {
106
+ for (const [key, value] of Object.entries(props)) {
107
+ const constructor = PROPERTY_CONSTRUCTORS[key];
108
+ if (constructor && typeof value === "object" && value !== null && !Array.isArray(value)) {
109
+ used.add(constructor);
110
+ }
111
+ if (key === "rules" && Array.isArray(value)) {
112
+ used.add("Rule");
113
+ }
114
+ if (key === "include" && (Array.isArray(value) ? value : [value]).some((i) => typeof i === "object" && i !== null)) {
115
+ used.add("Include");
116
+ }
117
+ }
118
+ }
119
+ emitProps(props, depth) {
120
+ const indent = " ".repeat(depth);
121
+ const innerIndent = " ".repeat(depth + 1);
122
+ const entries = [];
123
+ for (const [key, value] of Object.entries(props)) {
124
+ if (value === undefined || value === null)
125
+ continue;
126
+ const emitted = this.emitValue(key, value, depth + 1);
127
+ entries.push(`${innerIndent}${tsKey(key)}: ${emitted},`);
128
+ }
129
+ if (entries.length === 0)
130
+ return "{}";
131
+ return `{\n${entries.join("\n")}\n${indent}}`;
132
+ }
133
+ emitValue(key, value, depth) {
134
+ if (value === null || value === undefined)
135
+ return "undefined";
136
+ // Check if this key maps to a property constructor
137
+ const constructor = PROPERTY_CONSTRUCTORS[key];
138
+ if (constructor && typeof value === "object" && !Array.isArray(value)) {
139
+ const propsStr = this.emitProps(value, depth);
140
+ return `new ${constructor}(${propsStr})`;
141
+ }
142
+ // A Pipeline's include: each object is an Include, a string stays a string.
143
+ if (key === "include") {
144
+ const item = (i, d) => typeof i === "object" && i !== null && !Array.isArray(i) ? `new Include(${this.emitProps(i, d)})` : this.emitLiteral(i, d);
145
+ if (!Array.isArray(value))
146
+ return item(value, depth);
147
+ const indent = " ".repeat(depth);
148
+ const innerIndent = " ".repeat(depth + 1);
149
+ return `[\n${value.map((i) => `${innerIndent}${item(i, depth + 1)},`).join("\n")}\n${indent}]`;
150
+ }
151
+ // Rules array — wrap each item in Rule constructor
152
+ if (key === "rules" && Array.isArray(value)) {
153
+ const items = value.map((item) => {
154
+ if (typeof item === "object" && item !== null) {
155
+ const propsStr = this.emitProps(item, depth + 1);
156
+ return `new Rule(${propsStr})`;
157
+ }
158
+ return JSON.stringify(item);
159
+ });
160
+ const indent = " ".repeat(depth);
161
+ const innerIndent = " ".repeat(depth + 1);
162
+ return `[\n${items.map((i) => `${innerIndent}${i},`).join("\n")}\n${indent}]`;
163
+ }
164
+ return this.emitLiteral(value, depth);
165
+ }
166
+ emitLiteral(value, depth) {
167
+ if (value === null || value === undefined)
168
+ return "undefined";
169
+ if (typeof value === "string")
170
+ return JSON.stringify(value);
171
+ if (typeof value === "number" || typeof value === "boolean")
172
+ return String(value);
173
+ if (Array.isArray(value)) {
174
+ if (value.length === 0)
175
+ return "[]";
176
+ const items = value.map((item) => this.emitLiteral(item, depth + 1));
177
+ // Short arrays on one line
178
+ const oneLine = `[${items.join(", ")}]`;
179
+ if (oneLine.length < 80)
180
+ return oneLine;
181
+ const indent = " ".repeat(depth);
182
+ const innerIndent = " ".repeat(depth + 1);
183
+ return `[\n${items.map((i) => `${innerIndent}${i},`).join("\n")}\n${indent}]`;
184
+ }
185
+ if (typeof value === "object") {
186
+ const entries = Object.entries(value);
187
+ if (entries.length === 0)
188
+ return "{}";
189
+ const indent = " ".repeat(depth);
190
+ const innerIndent = " ".repeat(depth + 1);
191
+ const items = entries.map(([k, v]) => `${innerIndent}${tsKey(k)}: ${this.emitLiteral(v, depth + 1)},`);
192
+ return `{\n${items.join("\n")}\n${indent}}`;
193
+ }
194
+ return String(value);
195
+ }
196
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * GitLab CI YAML parser for `chant import`.
3
+ *
4
+ * Parses an existing .gitlab-ci.yml file into the core TemplateIR format,
5
+ * mapping GitLab CI constructs to resources and property types.
6
+ */
7
+ import { parseYAML } from "@intentius/chant/yaml";
8
+ /**
9
+ * Reserved top-level keys in .gitlab-ci.yml that are NOT job definitions.
10
+ */
11
+ /**
12
+ * Convert snake_case to camelCase — used only for TS variable names (logicalId),
13
+ * NOT for spec property names.
14
+ */
15
+ function snakeToCamelCase(name) {
16
+ return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
17
+ }
18
+ const RESERVED_KEYS = new Set([
19
+ "stages",
20
+ "variables",
21
+ "default",
22
+ "workflow",
23
+ "include",
24
+ "image",
25
+ "services",
26
+ "before_script",
27
+ "after_script",
28
+ "cache",
29
+ "pages",
30
+ ]);
31
+ /**
32
+ * GitLab CI YAML parser implementation.
33
+ */
34
+ export class GitLabParser {
35
+ parse(content) {
36
+ const doc = parseYAML(content);
37
+ const resources = [];
38
+ // Extract default
39
+ if (doc.default && typeof doc.default === "object") {
40
+ resources.push({
41
+ logicalId: "defaults",
42
+ type: "GitLab::CI::Default",
43
+ properties: doc.default,
44
+ });
45
+ }
46
+ // Extract workflow
47
+ if (doc.workflow && typeof doc.workflow === "object") {
48
+ resources.push({
49
+ logicalId: "workflow",
50
+ type: "GitLab::CI::Workflow",
51
+ properties: doc.workflow,
52
+ });
53
+ }
54
+ // Extract jobs — any top-level key not in RESERVED_KEYS
55
+ for (const [key, value] of Object.entries(doc)) {
56
+ if (RESERVED_KEYS.has(key))
57
+ continue;
58
+ if (typeof value !== "object" || value === null)
59
+ continue;
60
+ // Check if it looks like a job (has script, stage, trigger, extends, etc.)
61
+ const obj = value;
62
+ if (obj.script !== undefined ||
63
+ obj.stage !== undefined ||
64
+ obj.trigger !== undefined ||
65
+ obj.extends !== undefined ||
66
+ obj.rules !== undefined ||
67
+ obj.needs !== undefined) {
68
+ resources.push({
69
+ logicalId: snakeToCamelCase(key.replace(/-/g, "_")),
70
+ type: "GitLab::CI::Job",
71
+ properties: obj,
72
+ metadata: {
73
+ originalName: key,
74
+ stage: typeof obj.stage === "string" ? obj.stage : undefined,
75
+ },
76
+ });
77
+ }
78
+ }
79
+ // The file's top-level include, stages and variables are a Pipeline
80
+ // (#3742), so a rebuild writes them back: stages as written, a stage no
81
+ // job uses and the declared order included.
82
+ const pipeline = {};
83
+ if (doc.include !== undefined && doc.include !== null)
84
+ pipeline.include = doc.include;
85
+ if (Array.isArray(doc.stages))
86
+ pipeline.stages = doc.stages;
87
+ if (doc.variables && typeof doc.variables === "object")
88
+ pipeline.variables = doc.variables;
89
+ if (Object.keys(pipeline).length > 0) {
90
+ const taken = new Set(resources.map((r) => r.logicalId));
91
+ resources.unshift({
92
+ logicalId: taken.has("pipeline") ? "pipelineConfig" : "pipeline",
93
+ type: "GitLab::CI::Pipeline",
94
+ properties: pipeline,
95
+ });
96
+ }
97
+ // Also kept as metadata, for callers that read the IR without the Pipeline.
98
+ const metadata = {};
99
+ if (doc.stages)
100
+ metadata.stages = doc.stages;
101
+ if (doc.include)
102
+ metadata.include = doc.include;
103
+ if (doc.variables)
104
+ metadata.variables = doc.variables;
105
+ return {
106
+ resources,
107
+ parameters: [], // GitLab CI doesn't have parameters like CFN
108
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
109
+ };
110
+ }
111
+ }
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ // Serializer
2
+ export { gitlabSerializer } from "./serializer.js";
3
+ export { gitlabGraphEdges } from "./graph-edges.js";
4
+ // Plugin
5
+ export { gitlabPlugin } from "./plugin.js";
6
+ // Typed Op step-builder wrapper (chant #1288 Stage 2) — gitlabPipeline with
7
+ // authoring-time types derived from this lexicon's own GitlabPipelineArgs
8
+ // (see lexicons/k8s/src/op/builders.ts's module doc for why this lives here
9
+ // rather than in core). Opt-in: `@intentius/chant/op`'s same-named export is
10
+ // core's original untyped builder, unchanged, for cloud-agnostic authoring.
11
+ export { gitlabPipeline } from "./op/builders.js";
12
+ // Intrinsics
13
+ export { reference, ReferenceIntrinsic } from "./intrinsics.js";
14
+ // CI/CD Variables
15
+ export { CI } from "./variables.js";
16
+ // Generated entities — export everything from generated index
17
+ // After running `chant generate`, this re-exports all CI entity classes
18
+ export * from "./generated/index.js";
19
+ // Composites
20
+ export { DockerBuild, NodePipeline, BunPipeline, PnpmPipeline, PythonPipeline, ReviewApp, MrPlanReport } from "./composites/index.js";
21
+ // Spec utilities (for tooling)
22
+ export { fetchCISchema, fetchSchemas, GITLAB_SCHEMA_VERSION } from "./codegen/fetch.js";
23
+ export { parseCISchema, gitlabShortName, gitlabServiceName } from "./codegen/parse.js";
24
+ // Code generation pipeline
25
+ export { generate, writeGeneratedFiles } from "./codegen/generate.js";
26
+ export { packageLexicon } from "./codegen/package.js";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "63c7379cd49d05603a54529fa139575f02ceb7e3241a931bb15b9e35c8ffecb7",
4
+ "manifest.json": "be91342ce1ba95512e87deebcd929d0afcadc076160491773630b2e7b62097de",
5
5
  "meta.json": "db082fbf90c5941c498b2bee0f106103449c1a4c257716ca2220f0dd490450b2",
6
6
  "types/index.d.ts": "a048a66ebe20da0cb5fc889054cac69044d9bf882e2c56b5c5bf113d1f3ba57c",
7
7
  "rules/artifact-no-expiry.ts": "0ca5bb972d4a542126e8a0229d43b55658394cb7dd9b9923b2c590bec1c2ad7d",
@@ -11,7 +11,7 @@
11
11
  "rules/missing-stage.ts": "1270d4e00e270e60f948880ade373a91b02e33a26b9c9552a874b772998e3640",
12
12
  "rules/wgl010.ts": "e9a14d5f9e4c49b1524ba5ee3746be739603125ec3c2dc4eb4b7c0bfc32581b0",
13
13
  "rules/wgl011.ts": "2fbb164669204ac8c3662b9c1ae156b59589913e59dd596fb38f289a0f218aaf",
14
- "rules/wgl012.ts": "ec4bd3fe93d30824c74223bb4e5929e493a06561a7bd3265b68dc1ba5d734da4",
14
+ "rules/wgl012.ts": "789d1c6932e532ee5eb9eddd4191c49cc9d4094425339f93a36c960e82e90273",
15
15
  "rules/wgl013.ts": "afde3fb528dc9d8a245ec35f0da2ffb8460224956eb986412bef7e9e3891e378",
16
16
  "rules/wgl014.ts": "acbbeec029ec7f23e74cebcd4965dae12ea991a5a29e754040edd4a0c3fd8f0c",
17
17
  "rules/wgl015.ts": "040da46b2fe8126ba18c8d44957b402a710537eca0dc05d885498022ccec006a",
@@ -55,5 +55,5 @@
55
55
  "skills/chant-gitlab-migrate.md": "4853d04980560b379e5b0f7267dcb0a1e5d5879cb9e9931681ce76b2a5fe69d8",
56
56
  "skills/chant-gitlab-patterns.md": "6d9a44e9e8de4c3820be9d65381b0d2ede4cb3626aaaf0011e21838a9fbf556e"
57
57
  },
58
- "composite": "fa8097c773e5d6b8d94a7ab984b8e02f66fd29ec7bc817974632acf927b7103f"
58
+ "composite": "b0fefd5cf9a9c881b5a84df608913c43c5995c4b319d2ee76a89555a635de276"
59
59
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * GitLab CI/CD intrinsic functions.
3
+ *
4
+ * GitLab CI has a single intrinsic: the !reference tag.
5
+ * CI/CD variables ($CI_*) are just strings, not intrinsic functions.
6
+ */
7
+ import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
8
+ /**
9
+ * !reference tag intrinsic.
10
+ * References another job's properties: !reference [job_name, key]
11
+ */
12
+ export class ReferenceIntrinsic {
13
+ constructor(...path) {
14
+ this[INTRINSIC_MARKER] = true;
15
+ this.path = path;
16
+ }
17
+ toJSON() {
18
+ return this.path;
19
+ }
20
+ /**
21
+ * YAML representation uses the !reference tag.
22
+ */
23
+ toYAML() {
24
+ return { tag: "!reference", value: this.path };
25
+ }
26
+ }
27
+ /**
28
+ * Create a !reference intrinsic.
29
+ * Usage: reference("job_name", "script") → !reference [job_name, script]
30
+ */
31
+ export function reference(...path) {
32
+ return new ReferenceIntrinsic(...path);
33
+ }