@intentius/chant-lexicon-forgejo 0.124.57 → 0.124.58

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "4c4c5e8764f6844c5a23d70e6afa86d905c6627e01d04a2463e9a6f40d857e2c",
4
+ "manifest.json": "b777da2653c844ff4bbc29657ffe48e5e540412f1b6622e40ebe8d3b2f0cccec",
5
5
  "meta.json": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
6
6
  "types/index.d.ts": "66204549b2a864ab5489e3f02c1878e73ab4b3c9d938cfb480b66f71543a43de",
7
7
  "rules/delegate-to-github.ts": "1060cda40f4b73d6cca3ba3fa13b80b4886147d4034c88584c2d80c5510dbe5e",
@@ -9,5 +9,5 @@
9
9
  "rules/wfj011.ts": "fcb8bf6685d744af20ad804ce929488257995c74b9a3353b2eacae3768d1c83e",
10
10
  "skills/chant-forgejo.md": "f28d7fb69fb1cfd992496ef41fda0ea5ccd54a29b84ea1c501a44f6a3992c4d5"
11
11
  },
12
- "composite": "4226c270f3ec6aed2feaee4293a384febb3a87163e9c1256fd58813717a46592"
12
+ "composite": "25e5d73ecbdcf0cc5b25304bc3aa47baed7872ac3a968ba3ede47fbfac4d96d0"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.124.57",
3
+ "version": "0.124.58",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Forgejo",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-forgejo",
3
- "version": "0.124.57",
3
+ "version": "0.124.58",
4
4
  "description": "Forgejo / Codeberg / Gitea Actions lexicon for chant — a thin GitHub Actions dialect",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -54,8 +54,8 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^4.3.6",
57
- "@intentius/chant": "^0.124.57",
58
- "@intentius/chant-lexicon-github": "^0.124.57"
57
+ "@intentius/chant": "^0.124.58",
58
+ "@intentius/chant-lexicon-github": "^0.124.58"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
@@ -0,0 +1,169 @@
1
+ name: chant-pr-prod
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+ issue_comment:
11
+ types:
12
+ - created
13
+ workflow_dispatch:
14
+ inputs:
15
+ stack:
16
+ description: The stack the action runs on
17
+ required: false
18
+ type: string
19
+ default: ''
20
+ action:
21
+ description: The action to run, by its name
22
+ required: false
23
+ type: string
24
+ default: ''
25
+
26
+ env:
27
+ CHANT_ENV: prod
28
+
29
+ jobs:
30
+ plan:
31
+ if: github.event_name == 'pull_request'
32
+ runs-on: docker
33
+ container: node:22
34
+ outputs:
35
+ report_0: '${{ steps.plan.outputs.report_0 }}'
36
+ report_1: '${{ steps.plan.outputs.report_1 }}'
37
+ report_2: '${{ steps.plan.outputs.report_2 }}'
38
+ report_3: '${{ steps.plan.outputs.report_3 }}'
39
+ steps:
40
+ - name: Check out the change
41
+ env:
42
+ CHANT_CHECKOUT_TOKEN: '${{ github.token }}'
43
+ run: |
44
+ set -eu
45
+ git config --global --add safe.directory "$PWD" 2>/dev/null || true
46
+ [ -d .git ] || git init -q .
47
+ git remote get-url origin >/dev/null 2>&1 || git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
48
+ if [ -n "${CHANT_CHECKOUT_TOKEN:-}" ]; then
49
+ chant_auth="$(printf 'x-access-token:%s' "$CHANT_CHECKOUT_TOKEN" | base64 | tr -d '\n')"
50
+ export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=http.extraheader GIT_CONFIG_VALUE_0="AUTHORIZATION: basic $chant_auth"
51
+ unset chant_auth
52
+ fi
53
+ ref="$GITHUB_REF"
54
+ want="$GITHUB_SHA"
55
+ case "$ref" in
56
+ refs/heads/*) target="refs/remotes/origin/${ref#refs/heads/}" ;;
57
+ refs/tags/*) target="$ref" ;;
58
+ refs/*) target="refs/remotes/${ref#refs/}" ;;
59
+ *) target="refs/remotes/origin/chant-head" ;;
60
+ esac
61
+ if git rev-parse -q --verify HEAD >/dev/null; then git clean -ffdxq; fi
62
+ git -c protocol.version=2 fetch -q --prune --no-recurse-submodules origin '+refs/heads/*:refs/remotes/origin/*' '+refs/tags/*:refs/tags/*' "+$want:$target"
63
+ git -c protocol.version=2 fetch -q --no-tags origin '+refs/heads/chant/lifecycle:refs/heads/chant/lifecycle' 2>/dev/null || true
64
+ case "$ref" in
65
+ refs/heads/*) git checkout -q --force -B "${ref#refs/heads/}" "$target" ;;
66
+ *) git checkout -q --force --detach "$target" ;;
67
+ esac
68
+ - run: |
69
+ if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
70
+ 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)
71
+ - name: Plan the members this pull request reaches
72
+ id: plan
73
+ env:
74
+ BASE_SHA: '${{ github.event.pull_request.base.sha }}'
75
+ PR_NUMBER: '${{ github.event.pull_request.number }}'
76
+ run: |
77
+ if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
78
+ chant_report="$PWD/.chant/pr"
79
+ chant_handover() { node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const [dir,out]=process.argv.slice(1);const CHUNK=1000000,PARTS=4,NOTE=["pr-plan.json","pr-note.md"];const all={};const walk=(d,r)=>{let n=[];try{n=fs.readdirSync(d).sort()}catch{return}for(const x of n){const f=p.join(d,x),k=r?r+"/"+x:x;let s;try{s=fs.lstatSync(f)}catch{continue}if(s.isDirectory())walk(f,k);else if(s.isFile())all[k]=fs.readFileSync(f).toString("base64")}};walk(dir,"");const pack=(o)=>z.gzipSync(JSON.stringify(o)).toString("base64");let b=pack(all);if(b.length>CHUNK*PARTS){b=pack(Object.fromEntries(Object.entries(all).filter(([k])=>NOTE.includes(k))));if(b.length>CHUNK*PARTS){b=pack({});console.error("chant: the plan report is too large to hand to the note job, even cut to the report and the note; it hands on none of it")}else console.error("chant: the plan report is too large to hand to the note job whole; it hands on pr-plan.json and pr-note.md")}let o="";for(let i=0;i*CHUNK<b.length;i++)o+="report_"+i+"="+b.slice(i*CHUNK,(i+1)*CHUNK)+"\n";fs.appendFileSync(out,o);' "$chant_report" "${GITHUB_OUTPUT:-/dev/null}" || echo "chant: the plan report could not be handed to the note job" >&2; }
80
+ trap chant_handover EXIT
81
+ npx --no-install plywood ci run plan --chant "npx --no-install plywood" --in-place --no-push --base "$BASE_SHA" --pr "$PR_NUMBER" --env prod --gate pr-apply --forge forgejo
82
+ plan-note:
83
+ needs:
84
+ - plan
85
+ if: always() && github.event_name == 'pull_request' && (needs.plan.result == 'success' || needs.plan.result == 'failure')
86
+ runs-on: docker
87
+ container: node:22
88
+ steps:
89
+ - name: Check out the target branch's commit
90
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
91
+ with:
92
+ ref: '${{ github.event.pull_request.base.sha }}'
93
+ - name: Take the plan report from the plan job
94
+ run: |
95
+ set -u
96
+ node -e 'const fs=require("fs"),p=require("path"),z=require("zlib");const dir=process.argv[1];let b="";for(const l of fs.readFileSync(0,"utf8").split("\n")){if(!l.trim())continue;let s="";try{s=JSON.parse(l)}catch{}if(typeof s==="string")b+=s}if(!b){console.log("chant: the plan job handed on no report");process.exit(0)}let files;try{files=JSON.parse(z.gunzipSync(Buffer.from(b,"base64"),{maxOutputLength:268435456}).toString("utf8"))}catch(e){console.log("chant: the report the plan job handed on does not read: "+e.message);process.exit(0)}if(!files||typeof files!=="object"||Array.isArray(files)){console.log("chant: the report the plan job handed on is not a set of files");process.exit(0)}let n=0;for(const [k,v] of Object.entries(files)){const parts=k.split("/");if(typeof v!=="string"||k.length>1024||parts.some((x)=>!x||x==="."||x===".."||/[\\\x00-\x1f]/.test(x))){console.log("chant: left out "+JSON.stringify(k));continue}try{const f=p.join(dir,...parts);fs.mkdirSync(p.dirname(f),{recursive:true});fs.writeFileSync(f,Buffer.from(v,"base64"));n++}catch(e){console.log("chant: could not write "+JSON.stringify(k)+": "+e.message)}}console.log("chant: took "+n+" report files from the plan job")' .chant/pr <<'END_OF_REPORT'
97
+ ${{ toJSON(needs.plan.outputs.report_0) }}
98
+ ${{ toJSON(needs.plan.outputs.report_1) }}
99
+ ${{ toJSON(needs.plan.outputs.report_2) }}
100
+ ${{ toJSON(needs.plan.outputs.report_3) }}
101
+ END_OF_REPORT
102
+ - name: Keep the plan report
103
+ if: always()
104
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
105
+ with:
106
+ name: chant-pr-plan
107
+ path: .chant/pr
108
+ if-no-files-found: ignore
109
+ - name: Post the plan note and plywood/plan
110
+ if: always()
111
+ env:
112
+ PR_NUMBER: '${{ github.event.pull_request.number }}'
113
+ HEAD_SHA: '${{ github.sha }}'
114
+ PLAN_RESULT: '${{ needs.plan.result }}'
115
+ GITHUB_TOKEN: '${{ github.token }}'
116
+ run: npx --no-install plywood components pr-post --pr "$PR_NUMBER" --head "$HEAD_SHA" --env prod --output .chant/pr --forge forgejo --result "$PLAN_RESULT"
117
+ action:
118
+ if: (github.event_name == 'workflow_dispatch' && github.event.inputs.action != '') || (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '/plywood action '))
119
+ runs-on: docker
120
+ container: node:22
121
+ concurrency:
122
+ group: chant-action-prod
123
+ cancel-in-progress: false
124
+ steps:
125
+ - name: Check out the target branch's commit
126
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
127
+ with:
128
+ fetch-depth: 0
129
+ - 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)
130
+ - name: Run the requested action under its gated request
131
+ env:
132
+ CHANT_ACTION_COMMENT: '${{ github.event.comment.body }}'
133
+ CHANT_ACTION_MEMBER: '${{ github.event.inputs.stack }}'
134
+ CHANT_ACTION: '${{ github.event.inputs.action }}'
135
+ PR_NUMBER: '${{ github.event.issue.number }}'
136
+ GITHUB_TOKEN: '${{ github.token }}'
137
+ run: |
138
+ set +e
139
+ npx --no-install plywood action --from-event
140
+ code="$?"
141
+ if [ "$code" -eq 3 ]; then echo "The action waits for an approval."; exit 0; fi
142
+ exit "$code"
143
+ record:
144
+ if: github.event_name == 'push'
145
+ runs-on: docker
146
+ container: node:22
147
+ concurrency:
148
+ group: chant-record-prod
149
+ cancel-in-progress: false
150
+ steps:
151
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
152
+ with:
153
+ fetch-depth: 0
154
+ - 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)
155
+ - name: Record the plan on the merged commit; nothing applies
156
+ env:
157
+ BASE_SHA: '${{ github.event.before }}'
158
+ GITHUB_TOKEN: '${{ github.token }}'
159
+ CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
160
+ run: |
161
+ 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
162
+ npx --no-install plywood ci run record --chant "npx --no-install plywood" --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge forgejo
163
+ - name: Keep the record report
164
+ if: always()
165
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
166
+ with:
167
+ name: chant-pr-record
168
+ path: .chant/pr
169
+ if-no-files-found: ignore
@@ -0,0 +1,30 @@
1
+ /** The action job on Forgejo (#4654): the GitHub generator's job, reading Forgejo's payload, with no author check, and the same dispatch inputs. */
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { parseYAML } from "@intentius/chant/yaml";
5
+ import type { DriverComponent } from "@intentius/chant/components/driver";
6
+ import { generateForgejoPipeline } from "./generate-pipeline";
7
+
8
+ const ESTATE: DriverComponent[] = [
9
+ { name: "shop", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "pulumi-up", stack: "shop/prod-us" }] }] },
10
+ ];
11
+
12
+ const WORDS = { product: "plywood", noun: ["stack", "stacks"] as [string, string], commands: { preview: "plan" as const, action: "action" as const }, bin: "npx --no-install plywood" };
13
+
14
+ type Doc = { on: Record<string, { inputs?: Record<string, unknown> }>; jobs: Record<string, { if?: string; steps: Array<{ run?: string }> }> };
15
+
16
+ describe("the Forgejo workflow's action job", () => {
17
+ const yaml = generateForgejoPipeline(ESTATE, { env: "prod", prLoop: { words: WORDS, actionJob: true } }).yaml;
18
+ const doc = parseYAML(yaml) as Doc;
19
+
20
+ test("the dispatch carries the stack and action inputs, and the comment check asks no author", () => {
21
+ expect(Object.keys(doc.on.workflow_dispatch!.inputs!)).toEqual(["stack", "action"]);
22
+ expect(doc.jobs.action!.if).toContain("startsWith(github.event.comment.body, '/plywood action ')");
23
+ expect(doc.jobs.action!.if).not.toContain("author_association");
24
+ expect(doc.jobs.action!.steps.at(-1)!.run).toContain("npx --no-install plywood action --from-event");
25
+ });
26
+
27
+ test("golden", async () => {
28
+ await expect(yaml).toMatchFileSnapshot("./__golden__/pr-loop-action.forgejo.yml");
29
+ });
30
+ });