@intentius/chant-lexicon-github 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/action-pins.js +53 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +142 -0
- package/dist/codegen/generate-cli.js +26 -0
- package/dist/codegen/generate-lexicon.js +36 -0
- package/dist/codegen/generate-typescript.js +108 -0
- package/dist/codegen/generate.js +111 -0
- package/dist/codegen/naming.js +49 -0
- package/dist/codegen/package.js +40 -0
- package/dist/codegen/parse.js +595 -0
- package/dist/codegen/patches.js +36 -0
- package/dist/components/ci-jobs.js +84 -0
- package/dist/components/generate-op-pipeline.js +440 -0
- package/dist/components/generate-op-waves-pipeline.js +158 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +476 -0
- package/dist/composites/cache.js +17 -0
- package/dist/composites/catalog.js +859 -0
- package/dist/composites/checkout.js +25 -0
- package/dist/composites/dependabot.js +43 -0
- package/dist/composites/deploy-environment.js +38 -0
- package/dist/composites/docker-build.js +70 -0
- package/dist/composites/download-artifact.js +18 -0
- package/dist/composites/go-ci.js +62 -0
- package/dist/composites/index.js +15 -0
- package/dist/composites/node-ci.js +44 -0
- package/dist/composites/node-pipeline.js +96 -0
- package/dist/composites/pr-plan-report.js +129 -0
- package/dist/composites/python-ci.js +60 -0
- package/dist/composites/setup-go.js +18 -0
- package/dist/composites/setup-node.js +21 -0
- package/dist/composites/setup-python.js +18 -0
- package/dist/composites/upload-artifact.js +19 -0
- package/dist/coverage.js +32 -0
- package/dist/expression.js +177 -0
- package/dist/generated/index.js +26 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/graph-edges.js +174 -0
- package/dist/import/generator.js +108 -0
- package/dist/import/parser.js +69 -0
- package/dist/index.js +22 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +69 -0
- package/dist/lint/audit-lineage.js +168 -0
- package/dist/lint/post-synth/gha006.js +50 -0
- package/dist/lint/post-synth/gha009.js +35 -0
- package/dist/lint/post-synth/gha011.js +34 -0
- package/dist/lint/post-synth/gha013.js +40 -0
- package/dist/lint/post-synth/gha017.js +25 -0
- package/dist/lint/post-synth/gha018.js +33 -0
- package/dist/lint/post-synth/gha019.js +58 -0
- package/dist/lint/post-synth/gha021.js +42 -0
- package/dist/lint/post-synth/gha022.js +44 -0
- package/dist/lint/post-synth/gha023.js +37 -0
- package/dist/lint/post-synth/gha024.js +32 -0
- package/dist/lint/post-synth/gha025.js +33 -0
- package/dist/lint/post-synth/gha026.js +32 -0
- package/dist/lint/post-synth/gha027.js +46 -0
- package/dist/lint/post-synth/gha028.js +28 -0
- package/dist/lint/post-synth/gha029.js +56 -0
- package/dist/lint/post-synth/gha030.js +35 -0
- package/dist/lint/post-synth/gha031.js +75 -0
- package/dist/lint/post-synth/gha032.js +35 -0
- package/dist/lint/post-synth/gha033.js +39 -0
- package/dist/lint/post-synth/gha034.js +35 -0
- package/dist/lint/post-synth/gha035.js +50 -0
- package/dist/lint/post-synth/gha036.js +37 -0
- package/dist/lint/post-synth/gha037.js +41 -0
- package/dist/lint/post-synth/gha038.js +35 -0
- package/dist/lint/post-synth/gha039.js +34 -0
- package/dist/lint/post-synth/gha040.js +36 -0
- package/dist/lint/post-synth/gha041.js +32 -0
- package/dist/lint/post-synth/gha042.js +33 -0
- package/dist/lint/post-synth/gha043.js +35 -0
- package/dist/lint/post-synth/gha044.js +36 -0
- package/dist/lint/post-synth/gha045.js +33 -0
- package/dist/lint/post-synth/gha046.js +55 -0
- package/dist/lint/post-synth/gha047.js +44 -0
- package/dist/lint/post-synth/gha048.js +34 -0
- package/dist/lint/post-synth/gha049.js +36 -0
- package/dist/lint/post-synth/gha050.js +37 -0
- package/dist/lint/post-synth/gha051.js +38 -0
- package/dist/lint/post-synth/gha052.js +35 -0
- package/dist/lint/post-synth/gha053.js +34 -0
- package/dist/lint/post-synth/gha054.js +35 -0
- package/dist/lint/post-synth/gha055.js +44 -0
- package/dist/lint/post-synth/gha056.js +32 -0
- package/dist/lint/post-synth/gha057.js +35 -0
- package/dist/lint/post-synth/gha058.js +42 -0
- package/dist/lint/post-synth/gha059.js +119 -0
- package/dist/lint/post-synth/gha060.js +129 -0
- package/dist/lint/post-synth/gha061.js +78 -0
- package/dist/lint/post-synth/gha062.js +65 -0
- package/dist/lint/post-synth/gha063.js +56 -0
- package/dist/lint/post-synth/gha064.js +47 -0
- package/dist/lint/post-synth/gha065.js +125 -0
- package/dist/lint/post-synth/gha066.js +40 -0
- package/dist/lint/post-synth/gha067.js +75 -0
- package/dist/lint/post-synth/gha068.js +47 -0
- package/dist/lint/post-synth/gha069.js +99 -0
- package/dist/lint/post-synth/index.js +114 -0
- package/dist/lint/post-synth/yaml-helpers.js +465 -0
- package/dist/lint/rules/data/action-usage-policy.js +16 -0
- package/dist/lint/rules/data/advisory-feed.js +15 -0
- package/dist/lint/rules/data/deprecated-versions.js +14 -0
- package/dist/lint/rules/data/flagged-actions.js +18 -0
- package/dist/lint/rules/data/known-action-slugs.js +38 -0
- package/dist/lint/rules/data/known-actions.js +12 -0
- package/dist/lint/rules/data/preinstalled-tools.js +36 -0
- package/dist/lint/rules/data/recommended-inputs.js +9 -0
- package/dist/lint/rules/data/risky-features.js +22 -0
- package/dist/lint/rules/data/secret-patterns.js +30 -0
- package/dist/lint/rules/data/trusted-action-owners.js +12 -0
- package/dist/lint/rules/data/untrusted-contexts.js +44 -0
- package/dist/lint/rules/deprecated-action-version.js +44 -0
- package/dist/lint/rules/detect-secrets.js +45 -0
- package/dist/lint/rules/extract-inline-structs.js +55 -0
- package/dist/lint/rules/file-job-limit.js +43 -0
- package/dist/lint/rules/index.js +16 -0
- package/dist/lint/rules/job-timeout.js +53 -0
- package/dist/lint/rules/missing-recommended-inputs.js +51 -0
- package/dist/lint/rules/no-hardcoded-secrets.js +39 -0
- package/dist/lint/rules/no-raw-expressions.js +44 -0
- package/dist/lint/rules/suggest-cache.js +60 -0
- package/dist/lint/rules/use-condition-builders.js +37 -0
- package/dist/lint/rules/use-matrix-builder.js +36 -0
- package/dist/lint/rules/use-typed-actions.js +41 -0
- package/dist/lint/rules/validate-concurrency.js +59 -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 +152 -0
- package/dist/package-cli.js +20 -0
- package/dist/plugin.js +344 -0
- package/dist/serializer.js +572 -0
- package/dist/spec/fetch.js +46 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +38 -0
- package/dist/variables.js +41 -0
- package/package.json +6 -6
- package/src/codegen/package.ts +2 -2
- package/src/components/__fixtures__/pr-loop-build.github.golden.yml +6 -2
- package/src/components/__fixtures__/pr-loop-waves.github.golden.yml +178 -0
- package/src/components/__fixtures__/pr-loop.github.golden.yml +6 -2
- package/src/components/generate-pipeline.ts +66 -29
- package/src/components/generate-pr-pipeline.test.ts +44 -20
- package/src/components/generate-pr-waves-pipeline.test.ts +117 -0
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Actions predefined context variable references.
|
|
3
|
+
*
|
|
4
|
+
* These provide type-safe access to GitHub and Runner context values
|
|
5
|
+
* that expand to `${{ context.property }}` expressions in YAML.
|
|
6
|
+
*/
|
|
7
|
+
import { Expression } from "./expression.js";
|
|
8
|
+
export const GitHub = {
|
|
9
|
+
Ref: /* @__PURE__ */ new Expression("github.ref"),
|
|
10
|
+
RefName: /* @__PURE__ */ new Expression("github.ref_name"),
|
|
11
|
+
RefType: /* @__PURE__ */ new Expression("github.ref_type"),
|
|
12
|
+
Sha: /* @__PURE__ */ new Expression("github.sha"),
|
|
13
|
+
Actor: /* @__PURE__ */ new Expression("github.actor"),
|
|
14
|
+
TriggeringActor: /* @__PURE__ */ new Expression("github.triggering_actor"),
|
|
15
|
+
Repository: /* @__PURE__ */ new Expression("github.repository"),
|
|
16
|
+
RepositoryOwner: /* @__PURE__ */ new Expression("github.repository_owner"),
|
|
17
|
+
EventName: /* @__PURE__ */ new Expression("github.event_name"),
|
|
18
|
+
Event: /* @__PURE__ */ new Expression("github.event"),
|
|
19
|
+
RunId: /* @__PURE__ */ new Expression("github.run_id"),
|
|
20
|
+
RunNumber: /* @__PURE__ */ new Expression("github.run_number"),
|
|
21
|
+
RunAttempt: /* @__PURE__ */ new Expression("github.run_attempt"),
|
|
22
|
+
Workflow: /* @__PURE__ */ new Expression("github.workflow"),
|
|
23
|
+
WorkflowRef: /* @__PURE__ */ new Expression("github.workflow_ref"),
|
|
24
|
+
Workspace: /* @__PURE__ */ new Expression("github.workspace"),
|
|
25
|
+
Token: /* @__PURE__ */ new Expression("github.token"),
|
|
26
|
+
Job: /* @__PURE__ */ new Expression("github.job"),
|
|
27
|
+
HeadRef: /* @__PURE__ */ new Expression("github.head_ref"),
|
|
28
|
+
BaseRef: /* @__PURE__ */ new Expression("github.base_ref"),
|
|
29
|
+
ServerUrl: /* @__PURE__ */ new Expression("github.server_url"),
|
|
30
|
+
ApiUrl: /* @__PURE__ */ new Expression("github.api_url"),
|
|
31
|
+
GraphqlUrl: /* @__PURE__ */ new Expression("github.graphql_url"),
|
|
32
|
+
Action: /* @__PURE__ */ new Expression("github.action"),
|
|
33
|
+
ActionPath: /* @__PURE__ */ new Expression("github.action_path"),
|
|
34
|
+
};
|
|
35
|
+
export const Runner = {
|
|
36
|
+
Os: /* @__PURE__ */ new Expression("runner.os"),
|
|
37
|
+
Arch: /* @__PURE__ */ new Expression("runner.arch"),
|
|
38
|
+
Name: /* @__PURE__ */ new Expression("runner.name"),
|
|
39
|
+
Temp: /* @__PURE__ */ new Expression("runner.temp"),
|
|
40
|
+
ToolCache: /* @__PURE__ */ new Expression("runner.tool_cache"),
|
|
41
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.32",
|
|
4
4
|
"description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
".": {
|
|
33
33
|
"development": "./src/index.ts",
|
|
34
34
|
"types": "./dist/index.d.ts",
|
|
35
|
-
"default": "./
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
36
|
},
|
|
37
37
|
"./*": {
|
|
38
38
|
"development": "./src/*.ts",
|
|
39
39
|
"types": "./dist/*.d.ts",
|
|
40
|
-
"default": "./
|
|
40
|
+
"default": "./dist/*.js"
|
|
41
41
|
},
|
|
42
42
|
"./lint/post-synth": {
|
|
43
43
|
"development": "./src/lint/post-synth/index.ts",
|
|
44
44
|
"types": "./dist/lint/post-synth/index.d.ts",
|
|
45
|
-
"default": "./
|
|
45
|
+
"default": "./dist/lint/post-synth/index.js"
|
|
46
46
|
},
|
|
47
47
|
"./manifest": "./dist/manifest.json",
|
|
48
48
|
"./meta": "./dist/meta.json",
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
"validate": "tsx src/validate-cli.ts",
|
|
55
55
|
"docs": "tsx src/codegen/docs-cli.ts",
|
|
56
56
|
"prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
|
|
57
|
-
"build": "../../scripts/tsc7.sh -p tsconfig.build.json && tsc-alias -p tsconfig.build.json
|
|
57
|
+
"build": "../../scripts/tsc7.sh -p tsconfig.build.json --sourceMap false && tsc-alias -p tsconfig.build.json"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@intentius/chant": "*",
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@intentius/chant": "^0.124.
|
|
64
|
+
"@intentius/chant": "^0.124.32"
|
|
65
65
|
}
|
|
66
66
|
}
|
package/src/codegen/package.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
6
|
import { githubPlugin } from "../plugin";
|
|
7
7
|
import { join, dirname } from "path";
|
|
8
|
-
import {
|
|
8
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
9
9
|
import type { IntrinsicDef } from "@intentius/chant/lexicon";
|
|
10
10
|
import {
|
|
11
11
|
packagePipeline,
|
|
@@ -17,7 +17,7 @@ import { generate } from "./generate";
|
|
|
17
17
|
|
|
18
18
|
export type { PackageOptions, PackageResult };
|
|
19
19
|
|
|
20
|
-
const pkgDir = dirname(
|
|
20
|
+
const pkgDir = dirname(moduleSourceDir(import.meta.url));
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Package the GitHub Actions lexicon into a distributable BundleSpec.
|
|
@@ -34,7 +34,8 @@ jobs:
|
|
|
34
34
|
BASE_SHA: '${{ github.event.pull_request.base.sha }}'
|
|
35
35
|
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
36
36
|
GITHUB_TOKEN: '${{ github.token }}'
|
|
37
|
-
|
|
37
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
38
|
+
run: chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github
|
|
38
39
|
- name: Keep the plan report
|
|
39
40
|
if: always()
|
|
40
41
|
uses: actions/upload-artifact@v4
|
|
@@ -73,7 +74,10 @@ jobs:
|
|
|
73
74
|
env:
|
|
74
75
|
BASE_SHA: '${{ github.event.before }}'
|
|
75
76
|
GITHUB_TOKEN: '${{ github.token }}'
|
|
76
|
-
|
|
77
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
78
|
+
run: |
|
|
79
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then git fetch origin "main" && BASE_SHA="$(git merge-base FETCH_HEAD "$GITHUB_SHA")"; if [ "$BASE_SHA" = "$GITHUB_SHA" ]; then BASE_SHA="$(git rev-parse --verify --quiet "$GITHUB_SHA^" || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"; fi; fi; export BASE_SHA
|
|
80
|
+
chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge github
|
|
77
81
|
- name: Keep the record for a re-run
|
|
78
82
|
if: always()
|
|
79
83
|
uses: actions/cache/save@v4
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
name: chant-pr-prod
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
CHANT_ENV: prod
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
plan:
|
|
16
|
+
if: github.event_name == 'pull_request'
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
container: node:22
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
pull-requests: write
|
|
22
|
+
statuses: write
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
fetch-depth: 0
|
|
27
|
+
- run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
28
|
+
- name: Plan the members this pull request reaches
|
|
29
|
+
env:
|
|
30
|
+
BASE_SHA: '${{ github.event.pull_request.base.sha }}'
|
|
31
|
+
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
32
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
33
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
34
|
+
run: chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github --wave-gate release
|
|
35
|
+
- name: Keep the plan report
|
|
36
|
+
if: always()
|
|
37
|
+
uses: actions/upload-artifact@v4
|
|
38
|
+
with:
|
|
39
|
+
name: chant-pr-plan
|
|
40
|
+
path: .chant/pr
|
|
41
|
+
if-no-files-found: ignore
|
|
42
|
+
include-hidden-files: true
|
|
43
|
+
apply-wave-1:
|
|
44
|
+
if: github.event_name == 'push'
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
container: node:22
|
|
47
|
+
concurrency:
|
|
48
|
+
group: chant-apply-prod
|
|
49
|
+
cancel-in-progress: false
|
|
50
|
+
permissions:
|
|
51
|
+
contents: write
|
|
52
|
+
pull-requests: write
|
|
53
|
+
statuses: write
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v7
|
|
56
|
+
with:
|
|
57
|
+
fetch-depth: 0
|
|
58
|
+
- run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
59
|
+
- name: Restore the newest record of this commit's apply
|
|
60
|
+
uses: actions/cache/restore@v4
|
|
61
|
+
with:
|
|
62
|
+
path: .chant/pr-resume/pr-apply.json
|
|
63
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-1
|
|
64
|
+
restore-keys: chant-apply-prod-${{ github.sha }}-
|
|
65
|
+
- name: Apply wave 1, under the approval its plan has
|
|
66
|
+
env:
|
|
67
|
+
BASE_SHA: '${{ github.event.before }}'
|
|
68
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
69
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
70
|
+
run: |
|
|
71
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then git fetch origin "main" && BASE_SHA="$(git merge-base FETCH_HEAD "$GITHUB_SHA")"; if [ "$BASE_SHA" = "$GITHUB_SHA" ]; then BASE_SHA="$(git rev-parse --verify --quiet "$GITHUB_SHA^" || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"; fi; fi; export BASE_SHA
|
|
72
|
+
chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge github --wave-gate release --wave 1
|
|
73
|
+
- name: Keep the record for the next wave and a re-run
|
|
74
|
+
if: always()
|
|
75
|
+
uses: actions/cache/save@v4
|
|
76
|
+
with:
|
|
77
|
+
path: .chant/pr-resume/pr-apply.json
|
|
78
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-1
|
|
79
|
+
- name: Keep the wave 1 apply report
|
|
80
|
+
if: always()
|
|
81
|
+
uses: actions/upload-artifact@v4
|
|
82
|
+
with:
|
|
83
|
+
name: chant-pr-apply-wave-1
|
|
84
|
+
path: .chant/pr
|
|
85
|
+
if-no-files-found: ignore
|
|
86
|
+
include-hidden-files: true
|
|
87
|
+
apply-wave-2:
|
|
88
|
+
if: github.event_name == 'push'
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
needs:
|
|
91
|
+
- apply-wave-1
|
|
92
|
+
container: node:22
|
|
93
|
+
concurrency:
|
|
94
|
+
group: chant-apply-prod
|
|
95
|
+
cancel-in-progress: false
|
|
96
|
+
permissions:
|
|
97
|
+
contents: write
|
|
98
|
+
pull-requests: write
|
|
99
|
+
statuses: write
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@v7
|
|
102
|
+
with:
|
|
103
|
+
fetch-depth: 0
|
|
104
|
+
- run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
105
|
+
- name: Restore the newest record of this commit's apply
|
|
106
|
+
uses: actions/cache/restore@v4
|
|
107
|
+
with:
|
|
108
|
+
path: .chant/pr-resume/pr-apply.json
|
|
109
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-2
|
|
110
|
+
restore-keys: chant-apply-prod-${{ github.sha }}-
|
|
111
|
+
- name: Apply wave 2, under the approval its plan has
|
|
112
|
+
env:
|
|
113
|
+
BASE_SHA: '${{ github.event.before }}'
|
|
114
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
115
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
116
|
+
run: |
|
|
117
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then git fetch origin "main" && BASE_SHA="$(git merge-base FETCH_HEAD "$GITHUB_SHA")"; if [ "$BASE_SHA" = "$GITHUB_SHA" ]; then BASE_SHA="$(git rev-parse --verify --quiet "$GITHUB_SHA^" || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"; fi; fi; export BASE_SHA
|
|
118
|
+
chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge github --wave-gate release --wave 2
|
|
119
|
+
- name: Keep the record for the next wave and a re-run
|
|
120
|
+
if: always()
|
|
121
|
+
uses: actions/cache/save@v4
|
|
122
|
+
with:
|
|
123
|
+
path: .chant/pr-resume/pr-apply.json
|
|
124
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-2
|
|
125
|
+
- name: Keep the wave 2 apply report
|
|
126
|
+
if: always()
|
|
127
|
+
uses: actions/upload-artifact@v4
|
|
128
|
+
with:
|
|
129
|
+
name: chant-pr-apply-wave-2
|
|
130
|
+
path: .chant/pr
|
|
131
|
+
if-no-files-found: ignore
|
|
132
|
+
include-hidden-files: true
|
|
133
|
+
apply-wave-3:
|
|
134
|
+
if: github.event_name == 'push'
|
|
135
|
+
runs-on: ubuntu-latest
|
|
136
|
+
needs:
|
|
137
|
+
- apply-wave-2
|
|
138
|
+
container: node:22
|
|
139
|
+
concurrency:
|
|
140
|
+
group: chant-apply-prod
|
|
141
|
+
cancel-in-progress: false
|
|
142
|
+
permissions:
|
|
143
|
+
contents: write
|
|
144
|
+
pull-requests: write
|
|
145
|
+
statuses: write
|
|
146
|
+
steps:
|
|
147
|
+
- uses: actions/checkout@v7
|
|
148
|
+
with:
|
|
149
|
+
fetch-depth: 0
|
|
150
|
+
- run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
|
|
151
|
+
- name: Restore the newest record of this commit's apply
|
|
152
|
+
uses: actions/cache/restore@v4
|
|
153
|
+
with:
|
|
154
|
+
path: .chant/pr-resume/pr-apply.json
|
|
155
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-3
|
|
156
|
+
restore-keys: chant-apply-prod-${{ github.sha }}-
|
|
157
|
+
- name: Apply wave 3, under the approval its plan has
|
|
158
|
+
env:
|
|
159
|
+
BASE_SHA: '${{ github.event.before }}'
|
|
160
|
+
GITHUB_TOKEN: '${{ github.token }}'
|
|
161
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
162
|
+
run: |
|
|
163
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then git fetch origin "main" && BASE_SHA="$(git merge-base FETCH_HEAD "$GITHUB_SHA")"; if [ "$BASE_SHA" = "$GITHUB_SHA" ]; then BASE_SHA="$(git rev-parse --verify --quiet "$GITHUB_SHA^" || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"; fi; fi; export BASE_SHA
|
|
164
|
+
chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge github --wave-gate release --wave 3
|
|
165
|
+
- name: Keep the record for the next wave and a re-run
|
|
166
|
+
if: always()
|
|
167
|
+
uses: actions/cache/save@v4
|
|
168
|
+
with:
|
|
169
|
+
path: .chant/pr-resume/pr-apply.json
|
|
170
|
+
key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-3
|
|
171
|
+
- name: Keep the wave 3 apply report
|
|
172
|
+
if: always()
|
|
173
|
+
uses: actions/upload-artifact@v4
|
|
174
|
+
with:
|
|
175
|
+
name: chant-pr-apply-wave-3
|
|
176
|
+
path: .chant/pr
|
|
177
|
+
if-no-files-found: ignore
|
|
178
|
+
include-hidden-files: true
|
|
@@ -30,7 +30,8 @@ jobs:
|
|
|
30
30
|
BASE_SHA: '${{ github.event.pull_request.base.sha }}'
|
|
31
31
|
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
|
32
32
|
GITHUB_TOKEN: '${{ github.token }}'
|
|
33
|
-
|
|
33
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
34
|
+
run: chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github
|
|
34
35
|
- name: Keep the plan report
|
|
35
36
|
if: always()
|
|
36
37
|
uses: actions/upload-artifact@v4
|
|
@@ -65,7 +66,10 @@ jobs:
|
|
|
65
66
|
env:
|
|
66
67
|
BASE_SHA: '${{ github.event.before }}'
|
|
67
68
|
GITHUB_TOKEN: '${{ github.token }}'
|
|
68
|
-
|
|
69
|
+
CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
|
|
70
|
+
run: |
|
|
71
|
+
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then git fetch origin "main" && BASE_SHA="$(git merge-base FETCH_HEAD "$GITHUB_SHA")"; if [ "$BASE_SHA" = "$GITHUB_SHA" ]; then BASE_SHA="$(git rev-parse --verify --quiet "$GITHUB_SHA^" || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904)"; fi; fi; export BASE_SHA
|
|
72
|
+
chant ci run apply --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge github
|
|
69
73
|
- name: Keep the record for a re-run
|
|
70
74
|
if: always()
|
|
71
75
|
uses: actions/cache/save@v4
|
|
@@ -60,7 +60,17 @@ import { resolveComponentGraph, type DriverComponent } from "@intentius/chant/co
|
|
|
60
60
|
import { hasPublishStep, promoteArchivePaths } from "@intentius/chant/components/promote";
|
|
61
61
|
import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/gated-wave-pipeline";
|
|
62
62
|
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
63
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
PR_APPLY_RECORD,
|
|
65
|
+
PR_LOOP_REPORT_DIR,
|
|
66
|
+
prApplyGroup,
|
|
67
|
+
prApplyRecordKey,
|
|
68
|
+
prApplyWaveJobName,
|
|
69
|
+
prLoopJobs,
|
|
70
|
+
prLoopSetup,
|
|
71
|
+
prLoopWaveJobs,
|
|
72
|
+
prLoopWithWaves,
|
|
73
|
+
} from "@intentius/chant/components/pr-pipeline";
|
|
64
74
|
import { memberRepoPath } from "@intentius/chant/lexicon";
|
|
65
75
|
import type {
|
|
66
76
|
ComponentPipelineJob as GeneratedJob,
|
|
@@ -175,8 +185,17 @@ function gatedWaveGithubDoc(
|
|
|
175
185
|
* path filter, since a change outside the member can reach it; the plan
|
|
176
186
|
* selects from the whole change and passes `--member`, which keeps the
|
|
177
187
|
* member's gate, note and statuses apart from the other members'.
|
|
188
|
+
*
|
|
189
|
+
* With a gate per wave (`waveGate`, #4003) the one apply job becomes
|
|
190
|
+
* `apply-wave-1` to `apply-wave-<n>`, one per wave of the component graph,
|
|
191
|
+
* each needing the one before. Each saves the record under its own cache key
|
|
192
|
+
* (the run, the attempt and the wave), and each restores the newest record
|
|
193
|
+
* for the pushed commit, so wave k reads what wave k-1 left, and a re-run of
|
|
194
|
+
* a wave that stopped at its gate reads what it left itself. Each keeps its
|
|
195
|
+
* report as `chant-pr-apply-wave-<k>`.
|
|
178
196
|
*/
|
|
179
197
|
function prLoopGithubDoc(
|
|
198
|
+
components: DriverComponent[],
|
|
180
199
|
env: string,
|
|
181
200
|
image: string,
|
|
182
201
|
beforeScript: string[],
|
|
@@ -194,34 +213,53 @@ function prLoopGithubDoc(
|
|
|
194
213
|
// its run and attempt, and a re-run restores the newest for the commit.
|
|
195
214
|
const recordPath = member ? memberRepoPath(member, PR_APPLY_RECORD) : PR_APPLY_RECORD;
|
|
196
215
|
const recordPrefix = `${prApplyRecordKey(env, "${{ github.sha }}", member?.name)}-`;
|
|
197
|
-
const recordKey = `${recordPrefix}\${{ github.run_id }}-\${{ github.run_attempt }}`;
|
|
198
|
-
const restoreRecord = {
|
|
199
|
-
name: "Restore the record of an earlier attempt at this commit",
|
|
216
|
+
const recordKey = (wave?: number) => `${recordPrefix}\${{ github.run_id }}-\${{ github.run_attempt }}${wave ? `-wave-${wave}` : ""}`;
|
|
217
|
+
const restoreRecord = (wave?: number) => ({
|
|
218
|
+
name: wave ? "Restore the newest record of this commit's apply" : "Restore the record of an earlier attempt at this commit",
|
|
200
219
|
uses: "actions/cache/restore@v4",
|
|
201
|
-
with: { path: recordPath, key: recordKey, "restore-keys": recordPrefix },
|
|
202
|
-
};
|
|
203
|
-
const saveRecord = {
|
|
204
|
-
name: "Keep the record for a re-run",
|
|
220
|
+
with: { path: recordPath, key: recordKey(wave), "restore-keys": recordPrefix },
|
|
221
|
+
});
|
|
222
|
+
const saveRecord = (wave?: number) => ({
|
|
223
|
+
name: wave ? "Keep the record for the next wave and a re-run" : "Keep the record for a re-run",
|
|
205
224
|
if: "always()",
|
|
206
225
|
uses: "actions/cache/save@v4",
|
|
207
|
-
with: { path: recordPath, key: recordKey },
|
|
208
|
-
};
|
|
226
|
+
with: { path: recordPath, key: recordKey(wave) },
|
|
227
|
+
});
|
|
228
|
+
const reportName = (job: typeof plan) => `chant-pr-${job.wave ? prApplyWaveJobName(job.wave) : job.jobName}`;
|
|
209
229
|
const steps = (job: typeof plan, name: string): Array<Record<string, unknown>> => [
|
|
210
230
|
checkoutStep(true),
|
|
211
231
|
...runSteps(prLoopSetup(image)),
|
|
212
232
|
...runSteps(beforeScript),
|
|
213
233
|
...(job.build ? [{ name: "Build the templates", run: job.build[0] }, { name: "Lint the project", run: job.build[1] }] : []),
|
|
214
|
-
...(job.jobName === "apply" ? [restoreRecord] : []),
|
|
215
|
-
|
|
234
|
+
...(job.jobName === "apply" ? [restoreRecord(job.wave)] : []),
|
|
235
|
+
// The job's setup lines (#4004) run in the command's shell, so a value they settle reaches it.
|
|
236
|
+
// The command runs through chant ci run (#4156), which writes the job's run record, signed when the CHANT_RUNNER_KEY secret is set.
|
|
237
|
+
{ name, env: job.run.env, run: [...(job.setup ?? []), job.run.command].join("\n") },
|
|
216
238
|
...runSteps(extraScript),
|
|
217
|
-
...(job.jobName === "apply" ? [saveRecord] : []),
|
|
239
|
+
...(job.jobName === "apply" ? [saveRecord(job.wave)] : []),
|
|
218
240
|
{
|
|
219
|
-
name: `Keep the ${job.jobName} report`,
|
|
241
|
+
name: `Keep the ${job.wave ? `wave ${job.wave} ` : ""}${job.jobName} report`,
|
|
220
242
|
if: "always()",
|
|
221
243
|
uses: UPLOAD_ARTIFACT,
|
|
222
|
-
with: { name:
|
|
244
|
+
with: { name: reportName(job), path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true },
|
|
223
245
|
},
|
|
224
246
|
];
|
|
247
|
+
const applyJob = (job: typeof plan, needs: string[], name: string) => ({
|
|
248
|
+
if: "github.event_name == 'push'",
|
|
249
|
+
"runs-on": GITHUB_RUNNER,
|
|
250
|
+
...(needs.length > 0 ? { needs } : {}),
|
|
251
|
+
container: image,
|
|
252
|
+
concurrency: concurrency(prApplyGroup(env, member?.name)),
|
|
253
|
+
permissions: { contents: "write", "pull-requests": "write", statuses: "write" },
|
|
254
|
+
steps: steps(job, name),
|
|
255
|
+
});
|
|
256
|
+
const waveJobs = loop.waveGate ? prLoopWaveJobs(components, apply) : [];
|
|
257
|
+
const applyJobs: Array<[string, Record<string, unknown>]> = loop.waveGate
|
|
258
|
+
? waveJobs.map((job) => [
|
|
259
|
+
prApplyWaveJobName(job.wave!),
|
|
260
|
+
applyJob(job, job.wave! > 1 ? [prApplyWaveJobName(job.wave! - 1)] : [], `Apply wave ${job.wave}, under the approval its plan has`),
|
|
261
|
+
])
|
|
262
|
+
: [["apply", applyJob(apply, [], "Apply the plan a reviewer approved")]];
|
|
225
263
|
const jobsDoc: Record<string, unknown> = {
|
|
226
264
|
plan: {
|
|
227
265
|
if: "github.event_name == 'pull_request'",
|
|
@@ -230,15 +268,9 @@ function prLoopGithubDoc(
|
|
|
230
268
|
permissions: { contents: "read", "pull-requests": "write", statuses: "write" },
|
|
231
269
|
steps: steps(plan, "Plan the members this pull request reaches"),
|
|
232
270
|
},
|
|
233
|
-
|
|
234
|
-
if: "github.event_name == 'push'",
|
|
235
|
-
"runs-on": GITHUB_RUNNER,
|
|
236
|
-
container: image,
|
|
237
|
-
concurrency: concurrency(prApplyGroup(env, member?.name)),
|
|
238
|
-
permissions: { contents: "write", "pull-requests": "write", statuses: "write" },
|
|
239
|
-
steps: steps(apply, "Apply the plan a reviewer approved"),
|
|
240
|
-
},
|
|
271
|
+
...Object.fromEntries(applyJobs),
|
|
241
272
|
};
|
|
273
|
+
const applyNames = applyJobs.map(([name]) => name);
|
|
242
274
|
return {
|
|
243
275
|
name: member ? `chant-pr.${member.name}.${env}` : `chant-pr-${env}`,
|
|
244
276
|
environment: env,
|
|
@@ -246,10 +278,15 @@ function prLoopGithubDoc(
|
|
|
246
278
|
...(rooted ? {} : { defaults: { run: { "working-directory": member!.dir } } }),
|
|
247
279
|
env: { ...options.variables, CHANT_ENV: env },
|
|
248
280
|
jobsDoc,
|
|
249
|
-
stages: ["plan",
|
|
281
|
+
stages: ["plan", ...applyNames],
|
|
250
282
|
jobs: [
|
|
251
283
|
{ jobName: "plan", component: "pull request plan", stage: "plan", needs: [] },
|
|
252
|
-
|
|
284
|
+
...applyNames.map((name, i) => ({
|
|
285
|
+
jobName: name,
|
|
286
|
+
component: loop.waveGate ? `pull request apply, wave ${i + 1}` : "pull request apply",
|
|
287
|
+
stage: name,
|
|
288
|
+
needs: i > 0 ? [applyNames[i - 1]] : [],
|
|
289
|
+
})),
|
|
253
290
|
],
|
|
254
291
|
};
|
|
255
292
|
}
|
|
@@ -315,10 +352,10 @@ export function buildGithubPipelineDoc(
|
|
|
315
352
|
const byName = new Map(components.map((c) => [c.name, c]));
|
|
316
353
|
|
|
317
354
|
if (options.prLoop) {
|
|
318
|
-
if (options.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return prLoopGithubDoc(env, image, beforeScript, extraScript, options);
|
|
355
|
+
if (options.promoteTo !== undefined) throw new Error("a pull-request pipeline has no promote job; drop --promote-to or --pr-loop");
|
|
356
|
+
// #4003: --wave-gate beside --pr-loop gives the apply a gate per wave.
|
|
357
|
+
const prLoop = prLoopWithWaves(options.prLoop, options.gatedWaves);
|
|
358
|
+
return prLoopGithubDoc(components, env, image, beforeScript, extraScript, { ...options, prLoop });
|
|
322
359
|
}
|
|
323
360
|
|
|
324
361
|
if (options.gatedWaves) {
|
|
@@ -11,7 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
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
|
-
import { prApplyGroup, prApplyRecordKey } from "@intentius/chant/components/pr-pipeline";
|
|
14
|
+
import { githubBaseFallback, prApplyGroup, prApplyRecordKey, prLoopJobs } from "@intentius/chant/components/pr-pipeline";
|
|
15
15
|
import { generateGithubPipeline } from "./generate-pipeline";
|
|
16
16
|
|
|
17
17
|
const ESTATE: DriverComponent[] = [
|
|
@@ -63,28 +63,49 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
test("the plan job measures from the pull request's base and cannot push; the apply job can, one at a time", () => {
|
|
66
|
-
const plan = doc.jobs.plan.steps.find((s) => s.run?.includes("
|
|
67
|
-
|
|
66
|
+
const plan = doc.jobs.plan.steps.find((s) => s.run?.includes("ci run plan"))!;
|
|
67
|
+
// #4156: the job runs pr-plan through chant ci run, in its own checkout, and pushes no record from the pull request's code.
|
|
68
|
+
expect(plan.run).toBe('chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github');
|
|
68
69
|
expect(plan.env).toMatchObject({ BASE_SHA: "${{ github.event.pull_request.base.sha }}", PR_NUMBER: "${{ github.event.pull_request.number }}" });
|
|
70
|
+
// The runner key is an optional secret: unset, the expression is empty and the record is unsigned.
|
|
71
|
+
expect(plan.env?.CHANT_RUNNER_KEY).toBe("${{ secrets.CHANT_RUNNER_KEY }}");
|
|
69
72
|
expect(doc.jobs.plan.permissions).toEqual({ contents: "read", "pull-requests": "write", statuses: "write" });
|
|
70
73
|
|
|
71
|
-
const apply = doc.jobs.apply.steps.find((s) => s.run?.includes("
|
|
72
|
-
|
|
73
|
-
expect(apply.
|
|
74
|
+
const apply = doc.jobs.apply.steps.find((s) => s.run?.includes("ci run apply"))!;
|
|
75
|
+
// ci run builds pr-apply with --require-review unless told --no-require-review.
|
|
76
|
+
expect(apply.run).not.toContain("--no-require-review");
|
|
77
|
+
expect(apply.run).not.toContain("--no-push");
|
|
78
|
+
expect(apply.env).toMatchObject({ BASE_SHA: "${{ github.event.before }}", CHANT_RUNNER_KEY: "${{ secrets.CHANT_RUNNER_KEY }}" });
|
|
74
79
|
expect(doc.jobs.apply.permissions.contents).toBe("write");
|
|
75
80
|
expect(doc.jobs.apply.concurrency).toEqual({ group: "chant-apply-prod", "cancel-in-progress": false });
|
|
76
81
|
for (const job of Object.values(doc.jobs)) expect(job.steps[0].with).toEqual({ "fetch-depth": 0 });
|
|
77
82
|
});
|
|
78
83
|
|
|
84
|
+
test("a first push's all-zero github.event.before is settled in the apply's own shell, before pr-apply reads it (#4004)", () => {
|
|
85
|
+
const apply = doc.jobs.apply.steps.find((s) => s.run?.includes("ci run apply"))!;
|
|
86
|
+
const lines = apply.run!.trimEnd().split("\n");
|
|
87
|
+
expect(lines).toHaveLength(2);
|
|
88
|
+
expect(lines[0]).toBe(githubBaseFallback());
|
|
89
|
+
expect(lines[0]).toContain('[ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]');
|
|
90
|
+
expect(lines[0]).toContain('git merge-base FETCH_HEAD "$GITHUB_SHA"');
|
|
91
|
+
expect(lines[1]).toMatch(/^chant ci run apply --in-place --base "\$BASE_SHA"/);
|
|
92
|
+
const trunk = parseYAML(generateGithubPipeline(ESTATE, { env: "prod", prLoop: { branch: "trunk" } }).yaml) as { jobs: Record<string, Job> };
|
|
93
|
+
expect(trunk.jobs.apply.steps.find((s) => s.run?.includes("ci run apply"))!.run).toContain('git fetch origin "trunk"');
|
|
94
|
+
const plan = doc.jobs.plan.steps.find((s) => s.run?.includes("ci run plan"))!;
|
|
95
|
+
expect(plan.run).not.toContain("0000000000000000000000000000000000000000");
|
|
96
|
+
});
|
|
97
|
+
|
|
79
98
|
test("the apply resumes from a record the cache keeps for the pushed commit across a re-run (#3543)", () => {
|
|
80
99
|
const steps = doc.jobs.apply.steps;
|
|
81
100
|
const restore = steps.findIndex((s) => s.uses === "actions/cache/restore@v4");
|
|
82
|
-
const run = steps.findIndex((s) => s.run?.includes("
|
|
101
|
+
const run = steps.findIndex((s) => s.run?.includes("ci run apply"));
|
|
83
102
|
const save = steps.findIndex((s) => s.uses === "actions/cache/save@v4");
|
|
84
103
|
expect(restore).toBeGreaterThan(-1);
|
|
85
104
|
expect(restore).toBeLessThan(run);
|
|
86
105
|
expect(save).toBeGreaterThan(run);
|
|
87
|
-
|
|
106
|
+
// ci run runs the inner pr-apply in the checkout, where the cache restored the record.
|
|
107
|
+
expect(steps[run].run).toContain("--in-place");
|
|
108
|
+
expect(prLoopJobs("github", "prod")[1]!.command).toContain("--resume .chant/pr-resume/pr-apply.json");
|
|
88
109
|
const key = "chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}";
|
|
89
110
|
expect(steps[restore].with).toEqual({ path: ".chant/pr-resume/pr-apply.json", key, "restore-keys": "chant-apply-prod-${{ github.sha }}-" });
|
|
90
111
|
// Saved even when the apply failed, which is when a re-run needs it.
|
|
@@ -92,7 +113,7 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
92
113
|
expect(steps[save].with).toEqual({ path: ".chant/pr-resume/pr-apply.json", key });
|
|
93
114
|
// The plan job runs the pull request's code: it neither reads nor writes the record.
|
|
94
115
|
expect(doc.jobs.plan.steps.some((s) => s.uses?.startsWith("actions/cache"))).toBe(false);
|
|
95
|
-
expect(doc.jobs.plan.steps.find((s) => s.run?.includes("
|
|
116
|
+
expect(doc.jobs.plan.steps.find((s) => s.run?.includes("ci run plan"))!.run).not.toContain("--resume");
|
|
96
117
|
// The report artifact does not carry the record, which holds the members' outputs.
|
|
97
118
|
const keep = steps.find((s) => s.uses?.startsWith("actions/upload-artifact"))!;
|
|
98
119
|
expect(keep.with?.path).toBe(".chant/pr");
|
|
@@ -109,18 +130,20 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
109
130
|
const yaml = generateGithubPipeline(ESTATE, { env: "prod", prLoop: { gate: "infra-apply", branch: "trunk", requireReview: false } }).yaml;
|
|
110
131
|
expect(yaml).toContain("--gate infra-apply");
|
|
111
132
|
expect(yaml).toContain("- trunk");
|
|
112
|
-
expect(yaml).
|
|
133
|
+
expect(yaml).toContain("--no-require-review");
|
|
134
|
+
expect(yaml).toContain('--branch "trunk"');
|
|
113
135
|
});
|
|
114
136
|
|
|
115
|
-
test("refuses a
|
|
116
|
-
expect(() => generateGithubPipeline(ESTATE, { prLoop: {},
|
|
117
|
-
expect(() => generateGithubPipeline(ESTATE, { prLoop: {},
|
|
137
|
+
test("refuses a promote job beside it, and a canary or a fixed base beside --wave-gate (#4003)", () => {
|
|
138
|
+
expect(() => generateGithubPipeline(ESTATE, { prLoop: {}, promoteTo: "prod" })).toThrow(/pull-request pipeline has no promote job/);
|
|
139
|
+
expect(() => generateGithubPipeline(ESTATE, { prLoop: {}, gatedWaves: { gate: "g", canary: ["net"] } })).toThrow(/takes no --canary/);
|
|
140
|
+
expect(() => generateGithubPipeline(ESTATE, { prLoop: {}, gatedWaves: { gate: "g", base: "HEAD~1" } })).toThrow(/takes no --base/);
|
|
118
141
|
});
|
|
119
142
|
|
|
120
143
|
test("events and reportUrl stream the run's events into the report artifact and copy the reports out (#4007)", () => {
|
|
121
144
|
const loud = parseYAML(generateGithubPipeline(ESTATE, { env: "prod", prLoop: { events: true, reportUrl: "s3://chant-reports/acme" } }).yaml) as { jobs: Record<string, Job> };
|
|
122
145
|
for (const job of ["plan", "apply"]) {
|
|
123
|
-
const run = loud.jobs[job].steps.find((s) => s.run?.
|
|
146
|
+
const run = loud.jobs[job].steps.find((s) => s.run?.includes("chant ci run"))!;
|
|
124
147
|
expect(run.run).toContain('--events-json --report-url "s3://chant-reports/acme"');
|
|
125
148
|
expect(run.env?.CHANT_RUN_EVENTS_DIR).toBe(".chant/pr/run-events");
|
|
126
149
|
}
|
|
@@ -142,13 +165,14 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
142
165
|
// No path filter: a change outside the member can reach it.
|
|
143
166
|
expect(mdoc.on).toEqual({ pull_request: { branches: ["main"] }, push: { branches: ["main"] } });
|
|
144
167
|
expect(mdoc.defaults).toEqual({ run: { "working-directory": "infra/network" } });
|
|
145
|
-
const plan = mdoc.jobs.plan.steps.find((s) => s.run?.includes("
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
expect(
|
|
168
|
+
const plan = mdoc.jobs.plan.steps.find((s) => s.run?.includes("ci run plan"))!;
|
|
169
|
+
// ci run finds the member from the directory it runs in and passes --member to pr-plan and pr-apply.
|
|
170
|
+
expect(plan.run).toBe('chant ci run plan --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge github');
|
|
171
|
+
expect(prLoopJobs("github", "prod", {}, "network")[0]!.command).toContain("--member network");
|
|
172
|
+
const apply = mdoc.jobs.apply.steps.find((s) => s.run?.includes("ci run apply"))!;
|
|
149
173
|
expect(mdoc.jobs.apply.concurrency).toEqual({ group: "chant-apply.network.prod", "cancel-in-progress": false });
|
|
150
174
|
// The record path is relative to the member for --resume and to the repository for the cache.
|
|
151
|
-
expect(apply.run).toContain("--
|
|
175
|
+
expect(apply.run).toContain("--in-place");
|
|
152
176
|
const restore = mdoc.jobs.apply.steps.find((s) => s.uses === "actions/cache/restore@v4")!;
|
|
153
177
|
expect(restore.with).toMatchObject({ path: "infra/network/.chant/pr-resume/pr-apply.json", "restore-keys": "chant-apply.network.prod-${{ github.sha }}-" });
|
|
154
178
|
for (const job of Object.values(mdoc.jobs)) {
|
|
@@ -172,7 +196,7 @@ describe("the GitHub pull-request workflow", () => {
|
|
|
172
196
|
const mdoc = parseYAML(yaml) as { name: string; defaults?: unknown; jobs: Record<string, Job> };
|
|
173
197
|
expect(mdoc.name).toBe("chant-pr.root.prod");
|
|
174
198
|
expect(mdoc.defaults).toBeUndefined();
|
|
175
|
-
expect(yaml).toContain("--
|
|
199
|
+
expect(yaml).toContain("chant ci run plan --in-place");
|
|
176
200
|
expect(yaml).toContain("path: .chant/pr");
|
|
177
201
|
});
|
|
178
202
|
|