@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.
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +157 -0
- package/dist/codegen/fetch.js +93 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +36 -0
- package/dist/codegen/generate-typescript.js +105 -0
- package/dist/codegen/generate.js +125 -0
- package/dist/codegen/naming.js +48 -0
- package/dist/codegen/package.js +41 -0
- package/dist/codegen/parse.js +539 -0
- package/dist/codegen/patches.js +84 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/components/ci-jobs.js +16 -0
- package/dist/components/generate-op-pipeline.js +288 -0
- package/dist/components/generate-op-waves-pipeline.js +125 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +369 -0
- package/dist/composites/catalog.js +418 -0
- package/dist/composites/docker-build.js +38 -0
- package/dist/composites/index.js +5 -0
- package/dist/composites/mr-plan-report.js +42 -0
- package/dist/composites/node-pipeline.js +67 -0
- package/dist/composites/python-pipeline.js +45 -0
- package/dist/composites/review-app.js +32 -0
- package/dist/coverage.js +35 -0
- package/dist/generated/index.js +25 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/graph-edges.js +202 -0
- package/dist/import/generator.js +196 -0
- package/dist/import/parser.js +111 -0
- package/dist/index.js +26 -0
- package/dist/integrity.json +3 -3
- package/dist/intrinsics.js +33 -0
- package/dist/lint/audit-catalog.js +53 -0
- package/dist/lint/audit-lineage.js +64 -0
- package/dist/lint/post-synth/index.js +84 -0
- package/dist/lint/post-synth/wgl010.js +34 -0
- package/dist/lint/post-synth/wgl011.js +48 -0
- package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl012.js +68 -0
- package/dist/lint/post-synth/wgl013.js +57 -0
- package/dist/lint/post-synth/wgl014.js +43 -0
- package/dist/lint/post-synth/wgl015.js +70 -0
- package/dist/lint/post-synth/wgl016.js +70 -0
- package/dist/lint/post-synth/wgl017.js +38 -0
- package/dist/lint/post-synth/wgl018.js +34 -0
- package/dist/lint/post-synth/wgl019.js +43 -0
- package/dist/lint/post-synth/wgl020.js +47 -0
- package/dist/lint/post-synth/wgl021.js +52 -0
- package/dist/lint/post-synth/wgl022.js +38 -0
- package/dist/lint/post-synth/wgl023.js +44 -0
- package/dist/lint/post-synth/wgl024.js +41 -0
- package/dist/lint/post-synth/wgl025.js +42 -0
- package/dist/lint/post-synth/wgl026.js +58 -0
- package/dist/lint/post-synth/wgl027.js +49 -0
- package/dist/lint/post-synth/wgl028.js +58 -0
- package/dist/lint/post-synth/wgl029.js +55 -0
- package/dist/lint/post-synth/wgl030.js +43 -0
- package/dist/lint/post-synth/wgl031.js +33 -0
- package/dist/lint/post-synth/wgl032.js +64 -0
- package/dist/lint/post-synth/wgl033.js +32 -0
- package/dist/lint/post-synth/wgl034.js +37 -0
- package/dist/lint/post-synth/wgl035.js +40 -0
- package/dist/lint/post-synth/wgl036.js +38 -0
- package/dist/lint/post-synth/wgl037.js +41 -0
- package/dist/lint/post-synth/wgl038.js +43 -0
- package/dist/lint/post-synth/wgl039.js +34 -0
- package/dist/lint/post-synth/wgl040.js +35 -0
- package/dist/lint/post-synth/wgl041.js +53 -0
- package/dist/lint/post-synth/wgl042.js +41 -0
- package/dist/lint/post-synth/wgl043.js +39 -0
- package/dist/lint/post-synth/wgl044.js +35 -0
- package/dist/lint/post-synth/wgl045.js +54 -0
- package/dist/lint/post-synth/wgl046.js +40 -0
- package/dist/lint/post-synth/wgl047.js +34 -0
- package/dist/lint/post-synth/wgl048.js +32 -0
- package/dist/lint/post-synth/wgl049.js +53 -0
- package/dist/lint/post-synth/wgl050.js +42 -0
- package/dist/lint/post-synth/yaml-helpers.js +432 -0
- package/dist/lint/rules/artifact-no-expiry.js +53 -0
- package/dist/lint/rules/data/known-include-sources.js +44 -0
- package/dist/lint/rules/data/untrusted-variables.js +30 -0
- package/dist/lint/rules/deprecated-only-except.js +46 -0
- package/dist/lint/rules/import-source.js +61 -0
- package/dist/lint/rules/index.js +7 -0
- package/dist/lint/rules/missing-script.js +67 -0
- package/dist/lint/rules/missing-stage.js +60 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +33 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +262 -0
- package/dist/migrate/from-github/actions/index.js +17 -0
- package/dist/migrate/from-github/actions/registry.js +61 -0
- package/dist/migrate/from-github/actions/tier-1.js +298 -0
- package/dist/migrate/from-github/actions/tier-2.js +241 -0
- package/dist/migrate/from-github/actions/tier-3.js +96 -0
- package/dist/migrate/from-github/composites/patterns.js +151 -0
- package/dist/migrate/from-github/composites/rewriter.js +22 -0
- package/dist/migrate/from-github/diagnostics.js +53 -0
- package/dist/migrate/from-github/emit-yaml.js +121 -0
- package/dist/migrate/from-github/expressions.js +275 -0
- package/dist/migrate/from-github/index.js +96 -0
- package/dist/migrate/from-github/provenance.js +35 -0
- package/dist/migrate/from-github/rules.js +74 -0
- package/dist/migrate/from-github/security.js +189 -0
- package/dist/migrate/from-github/stages.js +156 -0
- package/dist/migrate/from-github/transformer.js +633 -0
- package/dist/op/activities/gitlab.js +38 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +26 -0
- package/dist/package-cli.js +21 -0
- package/dist/plugin.js +536 -0
- package/dist/rules/wgl012.ts +2 -2
- package/dist/serializer.js +197 -0
- package/dist/testdata/create-fixture.js +40 -0
- package/dist/testdata/load-fixtures.js +20 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +29 -0
- package/dist/variables.js +30 -0
- package/package.json +8 -8
- package/src/codegen/fetch.ts +2 -2
- package/src/codegen/package.ts +2 -2
- package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
- package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
- package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
- package/src/components/generate-pipeline.ts +60 -27
- package/src/components/generate-pr-pipeline.test.ts +14 -9
- package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
- package/src/lint/post-synth/wgl012.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
|
@@ -44,7 +44,17 @@ import {
|
|
|
44
44
|
import { resolveComponentGraph, type DriverComponent } from "@intentius/chant/components/driver";
|
|
45
45
|
import { hasPublishStep, promoteArchivePaths } from "@intentius/chant/components/promote";
|
|
46
46
|
import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/gated-wave-pipeline";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
PR_APPLY_RECORD,
|
|
49
|
+
PR_LOOP_REPORT_DIR,
|
|
50
|
+
prApplyGroup,
|
|
51
|
+
prApplyRecordKey,
|
|
52
|
+
prApplyWaveJobName,
|
|
53
|
+
prLoopJobs,
|
|
54
|
+
prLoopSetup,
|
|
55
|
+
prLoopWaveJobs,
|
|
56
|
+
prLoopWithWaves,
|
|
57
|
+
} from "@intentius/chant/components/pr-pipeline";
|
|
48
58
|
import { memberGitlabChanges, memberRepoPath, memberShellDir } from "@intentius/chant/lexicon";
|
|
49
59
|
import type {
|
|
50
60
|
ComponentPipelineJob as GeneratedJob,
|
|
@@ -79,10 +89,10 @@ export function generateGitlabPipeline(
|
|
|
79
89
|
const byName = new Map(components.map((c) => [c.name, c]));
|
|
80
90
|
|
|
81
91
|
if (options.prLoop) {
|
|
82
|
-
if (options.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return prLoopGitlabPipeline(env, image, beforeScript, extraScript, options);
|
|
92
|
+
if (options.promoteTo !== undefined) throw new Error("a pull-request pipeline has no promote job; drop --promote-to or --pr-loop");
|
|
93
|
+
// #4003: --wave-gate beside --pr-loop gives the apply a gate per wave.
|
|
94
|
+
const prLoop = prLoopWithWaves(options.prLoop, options.gatedWaves);
|
|
95
|
+
return prLoopGitlabPipeline(components, env, image, beforeScript, extraScript, { ...options, prLoop });
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
if (options.gatedWaves) {
|
|
@@ -282,8 +292,15 @@ function gatedWaveGitlabPipeline(
|
|
|
282
292
|
* apply passes it to `pr-apply --resume` (#3543). GitLab keeps protected
|
|
283
293
|
* branches' caches apart from other branches' by default, so a merge request
|
|
284
294
|
* pipeline cannot write the record the target branch's apply reads.
|
|
295
|
+
*
|
|
296
|
+
* With a gate per wave (`waveGate`, #4003) the apply job becomes
|
|
297
|
+
* `apply-wave-1` to `apply-wave-<n>`, one stage each, each needing the one
|
|
298
|
+
* before. They share the commit's cache key, so wave k pulls the record wave
|
|
299
|
+
* k-1 pushed, and a retry of a wave that stopped at its gate pulls the one it
|
|
300
|
+
* pushed itself.
|
|
285
301
|
*/
|
|
286
302
|
function prLoopGitlabPipeline(
|
|
303
|
+
components: DriverComponent[],
|
|
287
304
|
env: string,
|
|
288
305
|
image: string,
|
|
289
306
|
beforeScript: string[],
|
|
@@ -296,14 +313,19 @@ function prLoopGitlabPipeline(
|
|
|
296
313
|
const [plan, apply] = prLoopJobs("gitlab", env, loop, member?.name);
|
|
297
314
|
const reportPath = member ? memberRepoPath(member, PR_LOOP_REPORT_DIR) : PR_LOOP_REPORT_DIR;
|
|
298
315
|
const planJob = member ? `${member.name}-plan` : "plan";
|
|
299
|
-
const
|
|
316
|
+
const named = (job: string) => (member ? `${member.name}-${job}` : job);
|
|
317
|
+
const waveJobs = loop.waveGate ? prLoopWaveJobs(components, apply) : [];
|
|
318
|
+
const applies = loop.waveGate
|
|
319
|
+
? waveJobs.map((job) => ({ job, name: named(prApplyWaveJobName(job.wave!)), stage: prApplyWaveJobName(job.wave!) }))
|
|
320
|
+
: [{ job: apply, name: named("apply"), stage: "apply" }];
|
|
300
321
|
const onMergeRequest = GITLAB_MERGE_REQUEST_SOURCE;
|
|
301
322
|
if (loop.branch !== undefined) assertGitlabBranch("The merge-request pipeline", loop.branch);
|
|
302
323
|
const onMerge = gitlabPushRule(loop.branch ? JSON.stringify(loop.branch) : "$CI_DEFAULT_BRANCH");
|
|
303
324
|
// A member's jobs start in its directory, after the image's install lines.
|
|
304
325
|
const enter = rooted ? [] : [`cd ${memberShellDir(member!)}`];
|
|
305
|
-
|
|
306
|
-
const
|
|
326
|
+
// The command runs through chant ci run (#4156), which writes the job's run record, signed when the CHANT_RUNNER_KEY variable is set.
|
|
327
|
+
const script = (job: typeof plan): string[] => [...prLoopSetup(image), ...enter, ...beforeScript, ...(job.setup ?? []), ...(job.build ?? []), job.run.command, ...extraScript];
|
|
328
|
+
const stages = ["plan", ...applies.map((a) => a.stage)];
|
|
307
329
|
const doc: Record<string, unknown> = {
|
|
308
330
|
// Included files merge their top-level keys, so a member's file carries
|
|
309
331
|
// the rules every member shares and no name: two names would collide.
|
|
@@ -314,32 +336,43 @@ function prLoopGitlabPipeline(
|
|
|
314
336
|
stage: "plan",
|
|
315
337
|
image,
|
|
316
338
|
rules: [{ if: onMergeRequest }],
|
|
317
|
-
variables: plan.env,
|
|
339
|
+
variables: plan.run.env,
|
|
318
340
|
script: script(plan),
|
|
319
341
|
artifacts: { when: "always", paths: [reportPath] },
|
|
320
342
|
},
|
|
321
|
-
|
|
322
|
-
stage
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
343
|
+
...Object.fromEntries(
|
|
344
|
+
applies.map(({ job, name, stage }, i) => [
|
|
345
|
+
name,
|
|
346
|
+
{
|
|
347
|
+
stage,
|
|
348
|
+
image,
|
|
349
|
+
...(i > 0 ? { needs: [applies[i - 1]!.name] } : {}),
|
|
350
|
+
resource_group: prApplyGroup(env, member?.name),
|
|
351
|
+
rules: [{ if: onMerge }],
|
|
352
|
+
variables: job.run.env,
|
|
353
|
+
script: script(job),
|
|
354
|
+
// The attempt record (#3543), kept under the pushed commit even when
|
|
355
|
+
// the job fails, so a retry finishes the apply under the same approval.
|
|
356
|
+
cache: {
|
|
357
|
+
key: prApplyRecordKey(env, "$CI_COMMIT_SHA", member?.name),
|
|
358
|
+
paths: [member ? memberRepoPath(member, PR_APPLY_RECORD) : PR_APPLY_RECORD],
|
|
359
|
+
when: "always",
|
|
360
|
+
},
|
|
361
|
+
artifacts: { when: "always", paths: [reportPath] },
|
|
362
|
+
},
|
|
363
|
+
]),
|
|
364
|
+
),
|
|
337
365
|
};
|
|
338
366
|
const jobs: GeneratedJob[] = [
|
|
339
367
|
{ jobName: planJob, component: "merge request plan", stage: "plan", needs: [] },
|
|
340
|
-
{
|
|
368
|
+
...applies.map(({ job, name, stage }, i) => ({
|
|
369
|
+
jobName: name,
|
|
370
|
+
component: job.wave ? `merge request apply, wave ${job.wave}` : "merge request apply",
|
|
371
|
+
stage,
|
|
372
|
+
needs: i > 0 ? [applies[i - 1]!.name] : [],
|
|
373
|
+
})),
|
|
341
374
|
];
|
|
342
|
-
const yaml = emitGitlabFile(["workflow", "stages", "variables", planJob,
|
|
375
|
+
const yaml = emitGitlabFile(["workflow", "stages", "variables", planJob, ...applies.map((a) => a.name)].map((key) => [key, doc[key]] as const));
|
|
343
376
|
return { yaml, stages, jobs, env };
|
|
344
377
|
}
|
|
345
378
|
|
|
@@ -12,7 +12,7 @@ import { describe, expect, test } from "vitest";
|
|
|
12
12
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
13
13
|
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
14
14
|
import { generateGitlabPipeline } from "./generate-pipeline";
|
|
15
|
-
import { PR_LOOP_SETUP } from "@intentius/chant/components/pr-pipeline";
|
|
15
|
+
import { PR_LOOP_SETUP, prLoopJobs } from "@intentius/chant/components/pr-pipeline";
|
|
16
16
|
|
|
17
17
|
const ESTATE: DriverComponent[] = [
|
|
18
18
|
{ name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
|
|
@@ -54,10 +54,12 @@ describe("the GitLab merge-request pipeline", () => {
|
|
|
54
54
|
expect(doc.plan.rules).toEqual([{ if: '$CI_PIPELINE_SOURCE == "merge_request_event"' }]);
|
|
55
55
|
expect(doc.plan.variables).toEqual({ BASE_SHA: "$CI_MERGE_REQUEST_DIFF_BASE_SHA", PR_NUMBER: "$CI_MERGE_REQUEST_IID" });
|
|
56
56
|
expect(doc.plan.script).toEqual([
|
|
57
|
-
...PR_LOOP_SETUP,'chant
|
|
57
|
+
...PR_LOOP_SETUP,'chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge gitlab']);
|
|
58
58
|
expect(doc.apply.rules).toEqual([{ if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' }]);
|
|
59
59
|
expect(doc.apply.variables).toEqual({ BASE_SHA: "$CI_COMMIT_BEFORE_SHA" });
|
|
60
|
-
|
|
60
|
+
// #4156: ci run builds pr-apply with --require-review, and GitLab hands CHANT_RUNNER_KEY to the job as a CI/CD variable when it has one.
|
|
61
|
+
expect(doc.apply.script.at(-1)).toBe('chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge gitlab');
|
|
62
|
+
expect(result.yaml).not.toContain("CHANT_RUNNER_KEY");
|
|
61
63
|
expect(doc.apply.resource_group).toBe("chant-apply-prod");
|
|
62
64
|
expect(doc.variables.GIT_DEPTH).toBe("0");
|
|
63
65
|
expect(doc.apply.artifacts).toEqual({ when: "always", paths: [".chant/pr"] });
|
|
@@ -95,15 +97,17 @@ describe("the GitLab merge-request pipeline", () => {
|
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
test("the apply resumes from a record the job cache keeps for the pushed commit, failed or not (#3543)", () => {
|
|
98
|
-
expect(doc.apply.script.at(-1)).toContain("--
|
|
100
|
+
expect(doc.apply.script.at(-1)).toContain("--in-place");
|
|
101
|
+
expect(prLoopJobs("gitlab", "prod")[1]!.command).toContain("--resume .chant/pr-resume/pr-apply.json");
|
|
99
102
|
expect(doc.apply.cache).toEqual({ key: "chant-apply-prod-$CI_COMMIT_SHA", paths: [".chant/pr-resume/pr-apply.json"], when: "always" });
|
|
100
103
|
// The plan runs the merge request's code: no cache, no record.
|
|
101
104
|
expect(doc.plan.cache).toBeUndefined();
|
|
102
105
|
expect(doc.plan.script.at(-1)).not.toContain("--resume");
|
|
103
106
|
});
|
|
104
107
|
|
|
105
|
-
test("refuses a
|
|
106
|
-
expect(() => generateGitlabPipeline(ESTATE, { prLoop: {},
|
|
108
|
+
test("refuses a promote job beside it, and a canary beside --wave-gate (#4003)", () => {
|
|
109
|
+
expect(() => generateGitlabPipeline(ESTATE, { prLoop: {}, promoteTo: "prod" })).toThrow(/pull-request pipeline has no promote job/);
|
|
110
|
+
expect(() => generateGitlabPipeline(ESTATE, { prLoop: {}, gatedWaves: { gate: "g", canary: ["net"] } })).toThrow(/takes no --canary/);
|
|
107
111
|
});
|
|
108
112
|
|
|
109
113
|
test("inside a workspace member: member-named jobs that cd into the member, with no changes rule (#3465)", () => {
|
|
@@ -118,9 +122,10 @@ describe("the GitLab merge-request pipeline", () => {
|
|
|
118
122
|
const plan = mdoc["network-plan"] as Job;
|
|
119
123
|
const apply = mdoc["network-apply"] as Job;
|
|
120
124
|
expect(plan.script).toContain("cd infra/network");
|
|
121
|
-
|
|
122
|
-
expect(plan.script.
|
|
123
|
-
expect(apply.script.
|
|
125
|
+
// ci run, started in the member's directory, finds the member and passes --member to pr-plan and pr-apply.
|
|
126
|
+
expect(plan.script.indexOf("cd infra/network")).toBeLessThan(plan.script.findIndex((l) => l.includes("ci run plan")));
|
|
127
|
+
expect(apply.script.indexOf("cd infra/network")).toBeLessThan(apply.script.findIndex((l) => l.includes("ci run apply")));
|
|
128
|
+
expect(prLoopJobs("gitlab", "prod", {}, "network")[0]!.command).toContain("--member network");
|
|
124
129
|
// A change outside the member can reach it, so the jobs keep only their pipeline-source rules.
|
|
125
130
|
expect(plan.rules).toEqual([{ if: '$CI_PIPELINE_SOURCE == "merge_request_event"' }]);
|
|
126
131
|
expect(apply.resource_group).toBe("chant-apply.network.prod");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GitLab merge-request pipeline with a gate per wave on merge (#4003):
|
|
3
|
+
* the plan job in each merge request pipeline, and one apply job per wave of
|
|
4
|
+
* the component graph, one stage each, sharing the commit's record in the
|
|
5
|
+
* job cache. The whole file has a golden, and it validates against the GitLab
|
|
6
|
+
* CI schema this lexicon vendors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { describe, expect, test } from "vitest";
|
|
13
|
+
import { parseYAML } from "@intentius/chant/yaml";
|
|
14
|
+
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
15
|
+
import { generateGitlabPipeline } from "./generate-pipeline";
|
|
16
|
+
|
|
17
|
+
const root = (name: string, dependsOn: string[] = []): DriverComponent => ({
|
|
18
|
+
name,
|
|
19
|
+
dependsOn,
|
|
20
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: name }] }],
|
|
21
|
+
});
|
|
22
|
+
const ESTATE: DriverComponent[] = [root("net"), root("app", ["net"]), root("web", ["app"])];
|
|
23
|
+
|
|
24
|
+
const GOLDEN = join(import.meta.dirname, "__fixtures__", "pr-loop-waves.gitlab.golden.yml");
|
|
25
|
+
const SCHEMA = join(import.meta.dirname, "..", "codegen", "vendored", "gitlab-ci-schema.json");
|
|
26
|
+
|
|
27
|
+
interface Job {
|
|
28
|
+
stage: string;
|
|
29
|
+
needs?: string[];
|
|
30
|
+
rules: Array<{ if: string }>;
|
|
31
|
+
script: string[];
|
|
32
|
+
resource_group?: string;
|
|
33
|
+
cache?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("the GitLab merge-request pipeline with a gate per wave (#4003)", () => {
|
|
37
|
+
const result = generateGitlabPipeline(ESTATE, { env: "prod", prLoop: {}, gatedWaves: { gate: "release" } });
|
|
38
|
+
const doc = parseYAML(result.yaml) as Record<string, unknown> & { stages: string[] };
|
|
39
|
+
|
|
40
|
+
test("matches its golden", () => {
|
|
41
|
+
if (process.env.UPDATE_GOLDEN) writeFileSync(GOLDEN, result.yaml);
|
|
42
|
+
expect(result.yaml).toBe(readFileSync(GOLDEN, "utf-8"));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("validates against the vendored GitLab CI schema", () => {
|
|
46
|
+
const req = createRequire(import.meta.url);
|
|
47
|
+
const mod = req("ajv") as { default?: unknown };
|
|
48
|
+
const Ajv = (mod.default ?? mod) as new (opts: object) => { compile(s: object): ((d: unknown) => boolean) & { errors?: unknown } };
|
|
49
|
+
const validate = new Ajv({ strict: false, allErrors: true }).compile(JSON.parse(readFileSync(SCHEMA, "utf-8")));
|
|
50
|
+
expect(validate(doc), JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("one apply job and stage per wave, each needing the one before, on the merge, one at a time", () => {
|
|
54
|
+
expect(doc.stages).toEqual(["plan", "apply-wave-1", "apply-wave-2", "apply-wave-3"]);
|
|
55
|
+
expect(result.jobs.map((j) => [j.jobName, j.needs])).toEqual([
|
|
56
|
+
["plan", []],
|
|
57
|
+
["apply-wave-1", []],
|
|
58
|
+
["apply-wave-2", ["apply-wave-1"]],
|
|
59
|
+
["apply-wave-3", ["apply-wave-2"]],
|
|
60
|
+
]);
|
|
61
|
+
for (const k of [1, 2, 3]) {
|
|
62
|
+
const job = doc[`apply-wave-${k}`] as Job;
|
|
63
|
+
expect(job.stage).toBe(`apply-wave-${k}`);
|
|
64
|
+
expect(job.needs).toEqual(k > 1 ? [`apply-wave-${k - 1}`] : undefined);
|
|
65
|
+
expect(job.resource_group).toBe("chant-apply-prod");
|
|
66
|
+
// #4156: through chant ci run, which runs pr-apply --require-review --wave-gate release --resume ... --wave <k>.
|
|
67
|
+
expect(job.script.at(-1)).toBe(`chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge gitlab --wave-gate release --wave ${k}`);
|
|
68
|
+
// Every wave shares the commit's record, so wave k pulls what wave k-1 pushed.
|
|
69
|
+
expect(job.cache).toEqual({ key: "chant-apply-prod-$CI_COMMIT_SHA", paths: [".chant/pr-resume/pr-apply.json"], when: "always" });
|
|
70
|
+
}
|
|
71
|
+
expect((doc.plan as Job).script.at(-1)).toContain("--forge gitlab --wave-gate release");
|
|
72
|
+
expect((doc.plan as Job).script.at(-1)).not.toContain("--wave ");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("inside a workspace member the wave jobs carry the member's name", () => {
|
|
76
|
+
const member = { name: "network", dir: "infra/network", file: ".gitlab/ci/chant-pr.network.prod.gitlab-ci.yml" };
|
|
77
|
+
const scoped = generateGitlabPipeline(ESTATE, { env: "prod", prLoop: {}, gatedWaves: { gate: "release" }, member });
|
|
78
|
+
expect(scoped.jobs.map((j) => [j.jobName, j.needs])).toEqual([
|
|
79
|
+
["network-plan", []],
|
|
80
|
+
["network-apply-wave-1", []],
|
|
81
|
+
["network-apply-wave-2", ["network-apply-wave-1"]],
|
|
82
|
+
["network-apply-wave-3", ["network-apply-wave-2"]],
|
|
83
|
+
]);
|
|
84
|
+
// ci run, started in the member's directory, passes --member network to pr-apply.
|
|
85
|
+
expect(scoped.yaml).toContain("cd infra/network");
|
|
86
|
+
expect(scoped.yaml).toContain("chant ci run apply --in-place --base \"$BASE_SHA\" --env prod --gate pr-apply --forge gitlab --wave-gate release --wave 3");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { readFileSync } from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
10
11
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
12
|
import { isPropertyDeclarable } from "@intentius/chant/declarable";
|
|
12
13
|
|
|
@@ -23,8 +24,7 @@ interface LexiconEntry {
|
|
|
23
24
|
function loadDeprecatedProperties(): Map<string, Set<string>> {
|
|
24
25
|
const map = new Map<string, Set<string>>();
|
|
25
26
|
try {
|
|
26
|
-
const
|
|
27
|
-
const lexiconPath = join(pkgDir, "src", "generated", "lexicon-gitlab.json");
|
|
27
|
+
const lexiconPath = join(moduleSourceDir(import.meta.url), "..", "..", "generated", "lexicon-gitlab.json");
|
|
28
28
|
const content = readFileSync(lexiconPath, "utf-8");
|
|
29
29
|
const data = JSON.parse(content) as Record<string, LexiconEntry>;
|
|
30
30
|
|
package/src/lsp/completions.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
2
3
|
import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
|
|
3
4
|
import { LexiconIndex, lexiconCompletions, type LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
6
|
|
|
6
7
|
let cachedIndex: LexiconIndex | null = null;
|
|
7
8
|
|
package/src/lsp/hover.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
2
3
|
import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
|
|
3
4
|
import { LexiconIndex, lexiconHover, type LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
6
|
|
|
6
7
|
let cachedIndex: LexiconIndex | null = null;
|
|
7
8
|
|