@intentius/chant-lexicon-forgejo 0.122.0 → 0.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/generate-op-pipeline.d.ts +33 -74
- package/dist/components/generate-op-pipeline.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +3 -3
- package/src/components/generate-op-pipeline.test.ts +56 -58
- package/src/components/generate-op-pipeline.ts +39 -98
- package/src/components/generate-op-waves-pipeline.test.ts +15 -0
|
@@ -29,62 +29,28 @@
|
|
|
29
29
|
* the runner can fetch it.
|
|
30
30
|
*
|
|
31
31
|
* `comment` (#2231) crosses over, since chant #2291: it posts onto the
|
|
32
|
-
* triggering pull request
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `reconcilePr`'s `postOrUpdateComment` (`packages/core/src/op/activities/reconcile.ts`)
|
|
41
|
-
* for the fix.
|
|
32
|
+
* triggering pull request against `${GITHUB_API_URL}`, and a Forgejo Actions
|
|
33
|
+
* job already sets that (and `github.token`) the same way a GitHub Actions
|
|
34
|
+
* job does. This generator used to refuse the mode by name here, on the
|
|
35
|
+
* premise that chant had no way to reach a Forgejo instance; that premise
|
|
36
|
+
* was checked against a real Forgejo 12.0.4+gitea-1.22.0 instance during
|
|
37
|
+
* INTENTIUS/choudoufu#1027 and found false. Since chant #3757 the post goes
|
|
38
|
+
* through the Forgejo client in `packages/core/src/pr-forge.ts` over `fetch`
|
|
39
|
+
* rather than through `gh`, so the job's image needs no `gh` either.
|
|
42
40
|
*
|
|
43
|
-
* `issue`
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* PATCH calls carried only `GH_TOKEN` (in fact, on the `issue` path, not even
|
|
57
|
-
* that — `reconcilePr` handed it `execAsync` with no `env` override at all,
|
|
58
|
-
* chant #2320), and `gh`'s own documented environment variables (`gh help
|
|
59
|
-
* environment`) scope `GH_TOKEN`/`GITHUB_TOKEN` to "github.com or a subdomain
|
|
60
|
-
* of ghe.com" — never a self-hosted Forgejo. A `GH_DEBUG=api` POST against
|
|
61
|
-
* the live instance, with `GH_TOKEN` and `GITHUB_API_URL` set exactly as the
|
|
62
|
-
* generated workflow sets them, sent no `Authorization` header at all and
|
|
63
|
-
* Forgejo answered `{"message":"token is required"}` (HTTP 401); adding
|
|
64
|
-
* `GH_HOST` alongside `GH_TOKEN` made no difference. The same shape was what
|
|
65
|
-
* `postOrUpdateComment`'s writes carried too, which is why #2304's "verified
|
|
66
|
-
* against a real Forgejo instance" could not have gone through the generated
|
|
67
|
-
* workflow's own credential path — either that session had a `gh auth login`
|
|
68
|
-
* already stored for the test instance, which a real Actions job's fresh
|
|
69
|
-
* checkout never has, or a different `gh` build was in play.
|
|
70
|
-
*
|
|
71
|
-
* Chant #2333 fixed that credential for both modes: `ghCredentialEnv` now
|
|
72
|
-
* forwards the resolved token as `GH_ENTERPRISE_TOKEN` beside `GH_TOKEN`,
|
|
73
|
-
* which is the variable `gh` reads for a host in neither of the two classes
|
|
74
|
-
* above. #2333's own probe against the same image also narrowed the claim
|
|
75
|
-
* made here: `GH_ENTERPRISE_TOKEN` authenticated the identical call *without*
|
|
76
|
-
* a `GH_HOST` beside it, because the full URL #2291 built already names the
|
|
77
|
-
* host. So the premise this refusal was reinstated on no longer holds, and
|
|
78
|
-
* both halves of `postOrUpdateGithubIssue` — the paginated read and the
|
|
79
|
-
* POST/PATCH write — were driven green end to end against a live
|
|
80
|
-
* 12.0.4+gitea-1.22.0 instance under #2333, from a shell with no stored
|
|
81
|
-
* `gh auth login`.
|
|
82
|
-
*
|
|
83
|
-
* The refusal below is nonetheless left standing here, because lifting it is
|
|
84
|
-
* a generator behavior change with its own YAML surface to settle and #2333
|
|
85
|
-
* was scoped to the credential. Lifting it is filed separately; this comment
|
|
86
|
-
* is corrected rather than acted on so the stated reason does not outlive the
|
|
87
|
-
* fact it rested on.
|
|
41
|
+
* `issue` crosses over too, since chant #2351. It was refused here from
|
|
42
|
+
* #2332 on, after chant #2315 drove the mode against a real Forgejo
|
|
43
|
+
* 12.0.4+gitea-1.22.0 instance: the paginated read and the `.pull_request ==
|
|
44
|
+
* null` filter behaved as on GitHub, but every POST and PATCH answered
|
|
45
|
+
* `{"message":"token is required"}` (HTTP 401), because `gh` sends
|
|
46
|
+
* `GH_TOKEN` only to github.com and `ghe.com` hosts. #2333 fixed that
|
|
47
|
+
* credential, and #3757 moved the mode off `gh` onto the Forgejo client in
|
|
48
|
+
* `packages/core/src/pr-forge.ts`, which sends `Authorization: token …`
|
|
49
|
+
* itself. The job this emits for a cron `issue` Op is github's, unchanged:
|
|
50
|
+
* the same trigger, and the same `GH_TOKEN`/`GITHUB_TOKEN` from
|
|
51
|
+
* `github.token` on the run step, which is the credential the mode reads.
|
|
52
|
+
* Only `permissions:` is dropped, as for every mode, so what that token may
|
|
53
|
+
* write on a scheduled run is the runner's configuration.
|
|
88
54
|
*
|
|
89
55
|
* A spec's `environment` (#2257) is dropped on the same terms as
|
|
90
56
|
* `permissions:`, and for a stronger reason: Forgejo Actions has no
|
|
@@ -97,25 +63,18 @@
|
|
|
97
63
|
* still holds the apply here is chant's own gate (#2119), which is a fact on
|
|
98
64
|
* the `chant/lifecycle` branch and needs nothing from the forge.
|
|
99
65
|
*
|
|
100
|
-
* The gated-apply notice job (#2243)
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* `
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* approve }` on the Op's job, and the `node -e` step that writes them to
|
|
113
|
-
* `$GITHUB_OUTPUT` — does not survive it either (#2294). Both existed only for
|
|
114
|
-
* that job to read via `needs.<job>.outputs`; with the job gone, so is every
|
|
115
|
-
* reader, and `buildGithubOpPipelineDocs` is asked for neither
|
|
116
|
-
* (`emitGatedOutputs: false` below) rather than emitting them here and
|
|
117
|
-
* stripping them back out. Should the Forgejo posting work in #2291 grow a
|
|
118
|
-
* notice job of its own, this is the one flag that brings both back.
|
|
66
|
+
* The gated-apply notice job (#2243) crosses over since #3773. It used to
|
|
67
|
+
* shell to `gh` on GitHub's hosted runner image, which a Forgejo runner
|
|
68
|
+
* neither ships nor can point at its own instance, so it was dropped here
|
|
69
|
+
* along with the apply job's `gated`/`op`/`gate`/`approve` outputs that
|
|
70
|
+
* only it read (#2294). It now runs in the Op's own image and posts through
|
|
71
|
+
* `@intentius/chant/pr-forge`, whose Forgejo client finds the pull request
|
|
72
|
+
* a commit merged with `commits/{sha}/pull` and sends `Authorization: token`
|
|
73
|
+
* itself. So the doc rebuilt below carries `gatedNoticeDoc` through the
|
|
74
|
+
* same dialect transform as the jobs, which drops its `permissions:` the
|
|
75
|
+
* way it drops every scope but `id-token`. A `push` job also still runs
|
|
76
|
+
* with `--gated-exit 0`, and `chant run` still writes the gate and the
|
|
77
|
+
* approve command to `GITHUB_STEP_SUMMARY`.
|
|
119
78
|
*
|
|
120
79
|
* A spec's own `variables` (#2290) crosses over unchanged: forgejo reuses the
|
|
121
80
|
* same job-level `env:` github's builder emits, and the dialect transform
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAOH,OAAO,KAAK,EACV,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAmCjF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,wBAA6B,EACtC,cAAc,GAAE,qBAA0B,GACzC,gBAAgB,CA6BlB"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "151167f06f66d77050d96f35856fc324fddd493067350967d26bb1dc3c995c63",
|
|
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": "
|
|
12
|
+
"composite": "14703e6af2b10d8c29a0924bae768c39d2c913ba486a3fb0af8c7b285195062f"
|
|
13
13
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-forgejo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.123.0",
|
|
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.
|
|
58
|
-
"@intentius/chant-lexicon-github": "^0.
|
|
57
|
+
"@intentius/chant": "^0.123.0",
|
|
58
|
+
"@intentius/chant-lexicon-github": "^0.123.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"generate": "tsx src/codegen/generate-cli.ts",
|
|
@@ -13,6 +13,9 @@ import { generateGithubOpPipeline } from "@intentius/chant-lexicon-github/compon
|
|
|
13
13
|
import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
|
|
14
14
|
|
|
15
15
|
interface ParsedJob {
|
|
16
|
+
needs?: string;
|
|
17
|
+
if?: string;
|
|
18
|
+
permissions?: Record<string, string>;
|
|
16
19
|
"runs-on"?: string;
|
|
17
20
|
container?: string;
|
|
18
21
|
environment?: Record<string, string>;
|
|
@@ -33,7 +36,7 @@ describe("generateForgejoOpPipeline: structure (github-shaped)", () => {
|
|
|
33
36
|
test("one file per scheduled Op, each a valid workflow with one job", () => {
|
|
34
37
|
const specs: ScheduledOpSpec[] = [
|
|
35
38
|
{ name: "actions-audit", schedule: "0 6 * * *" },
|
|
36
|
-
{ name: "prod-reconcile", schedule: "0 * * * *", findingMode: "
|
|
39
|
+
{ name: "prod-reconcile", schedule: "0 * * * *", findingMode: "issue" },
|
|
37
40
|
];
|
|
38
41
|
const result = generateForgejoOpPipeline(specs);
|
|
39
42
|
|
|
@@ -78,7 +81,7 @@ describe("generateForgejoOpPipeline: dialect applied", () => {
|
|
|
78
81
|
describe("generateForgejoOpPipeline: non-cron trigger survives the dialect transform (#2084)", () => {
|
|
79
82
|
test("a pull_request trigger round-trips through the Forgejo dialect, with permissions: still dropped", () => {
|
|
80
83
|
const specs: ScheduledOpSpec[] = [
|
|
81
|
-
{ name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "
|
|
84
|
+
{ name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "issue" },
|
|
82
85
|
];
|
|
83
86
|
const fj = generateForgejoOpPipeline(specs);
|
|
84
87
|
const gh = generateGithubOpPipeline(specs);
|
|
@@ -149,32 +152,41 @@ describe("generateForgejoOpPipeline: comment finding mode crosses over (#2291)",
|
|
|
149
152
|
});
|
|
150
153
|
});
|
|
151
154
|
|
|
152
|
-
describe("generateForgejoOpPipeline: issue finding mode
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
// reproduction.
|
|
161
|
-
test("findingMode issue is refused, naming the Op and the real-instance failure", () => {
|
|
162
|
-
const specs: ScheduledOpSpec[] = [{ name: "prod-watch", schedule: "0 6 * * *", findingMode: "issue" }];
|
|
163
|
-
expect(() => generateForgejoOpPipeline(specs)).toThrow(
|
|
164
|
-
/Scheduled Op "prod-watch".*findingMode "issue".*token is required/s,
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("github generates the same spec cleanly, so the refusal is forgejo's and not the shared builder's", () => {
|
|
155
|
+
describe("generateForgejoOpPipeline: issue finding mode crosses over (#2351)", () => {
|
|
156
|
+
// Refused here from #2332 to #2351: a real Forgejo instance answered 401 to
|
|
157
|
+
// the writes `gh` made for this mode, because `gh` never sent its token to
|
|
158
|
+
// a self-hosted host (#2315). #2333 fixed that credential, and #3757 moved
|
|
159
|
+
// the mode onto the forge client in `pr-forge.ts`, which sends the header
|
|
160
|
+
// itself. So the generator emits the mode as github does: same trigger,
|
|
161
|
+
// same job, same step env, with `permissions:` dropped by the dialect.
|
|
162
|
+
test("a cron issue Op generates cleanly", () => {
|
|
169
163
|
const specs: ScheduledOpSpec[] = [{ name: "prod-watch", schedule: "0 6 * * *", findingMode: "issue" }];
|
|
170
|
-
expect(() =>
|
|
164
|
+
expect(() => generateForgejoOpPipeline(specs)).not.toThrow();
|
|
171
165
|
});
|
|
172
166
|
|
|
173
|
-
test("
|
|
167
|
+
test("job/trigger/findingMode parity with the github generator", () => {
|
|
174
168
|
const specs: ScheduledOpSpec[] = [
|
|
169
|
+
{ name: "prod-watch", schedule: "0 6 * * *", findingMode: "issue" },
|
|
175
170
|
{ name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "issue" },
|
|
176
171
|
];
|
|
177
|
-
expect(
|
|
172
|
+
expect(generateForgejoOpPipeline(specs).jobs).toEqual(generateGithubOpPipeline(specs).jobs);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("the cron job carries github.token as GH_TOKEN and GITHUB_TOKEN, the credential the mode reads", () => {
|
|
176
|
+
const specs: ScheduledOpSpec[] = [{ name: "prod-watch", schedule: "0 6 * * *", findingMode: "issue" }];
|
|
177
|
+
const fjDoc = parseFile(generateForgejoOpPipeline(specs).files[0].yaml);
|
|
178
|
+
const ghDoc = parseFile(generateGithubOpPipeline(specs).files[0].yaml);
|
|
179
|
+
const run = fjDoc.jobs!["prod-watch"].steps.at(-1)!;
|
|
180
|
+
expect(run.run).toBe("chant run prod-watch");
|
|
181
|
+
expect((run as { env?: Record<string, string> }).env).toEqual({
|
|
182
|
+
GITHUB_TOKEN: "${{ github.token }}",
|
|
183
|
+
GH_TOKEN: "${{ github.token }}",
|
|
184
|
+
});
|
|
185
|
+
expect(fjDoc.on).toEqual(ghDoc.on);
|
|
186
|
+
expect(fjDoc.permissions).toBeUndefined();
|
|
187
|
+
// github grants the scope the mode needs; on Forgejo what the token may
|
|
188
|
+
// write is the runner's configuration.
|
|
189
|
+
expect(ghDoc.permissions).toEqual({ contents: "read", issues: "write" });
|
|
178
190
|
});
|
|
179
191
|
});
|
|
180
192
|
|
|
@@ -238,14 +250,18 @@ describe("generateForgejoOpPipeline: the gated apply on push (#2243)", () => {
|
|
|
238
250
|
expect(step?.run).toContain("chant run app-apply --gated-exit 0 --json");
|
|
239
251
|
});
|
|
240
252
|
|
|
241
|
-
test("the notice job
|
|
253
|
+
test("the notice job crosses over too, posting through pr-forge rather than gh (#3773)", () => {
|
|
242
254
|
const fj = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
expect(
|
|
248
|
-
|
|
255
|
+
expect(Object.keys(fj.jobs ?? {})).toEqual(["app-apply", "app-apply-gate-notice"]);
|
|
256
|
+
const notice = fj.jobs!["app-apply-gate-notice"];
|
|
257
|
+
expect(notice.needs).toBe("app-apply");
|
|
258
|
+
expect(notice.if).toBe("needs.app-apply.outputs.gated == 'true'");
|
|
259
|
+
expect(notice.container).toBe("node:22");
|
|
260
|
+
// The dialect drops the job's permissions like every scope but id-token.
|
|
261
|
+
expect(notice.permissions).toBeUndefined();
|
|
262
|
+
const script = notice.steps[0].run ?? "";
|
|
263
|
+
expect(script).toContain('from "@intentius/chant/pr-forge"');
|
|
264
|
+
expect(script).not.toMatch(/(^|\s)gh\s/m);
|
|
249
265
|
});
|
|
250
266
|
|
|
251
267
|
/**
|
|
@@ -262,7 +278,7 @@ describe("generateForgejoOpPipeline: the gated apply on push (#2243)", () => {
|
|
|
262
278
|
test("the exit-code capture crosses the Forgejo dialect with no shell override — its job runs in a container, whose default shell is sh", () => {
|
|
263
279
|
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
264
280
|
const job = doc.jobs!["app-apply"];
|
|
265
|
-
expect(job.container).toBe("node:22
|
|
281
|
+
expect(job.container).toBe("node:22");
|
|
266
282
|
const step = job.steps.find((s) => s.id === "chant-run");
|
|
267
283
|
expect(step?.shell).toBeUndefined();
|
|
268
284
|
expect(step?.run).not.toContain("pipefail");
|
|
@@ -318,7 +334,7 @@ describe("generateForgejoOpPipeline: a dropped deployment environment (#2257)",
|
|
|
318
334
|
"jobs:",
|
|
319
335
|
" actions-audit:",
|
|
320
336
|
" runs-on: docker",
|
|
321
|
-
" container: node:22
|
|
337
|
+
" container: node:22",
|
|
322
338
|
" steps:",
|
|
323
339
|
" - uses: https://code.forgejo.org/actions/checkout@v7",
|
|
324
340
|
" - run: chant run actions-audit",
|
|
@@ -329,7 +345,7 @@ describe("generateForgejoOpPipeline: a dropped deployment environment (#2257)",
|
|
|
329
345
|
|
|
330
346
|
test("a spec with no environment emits the bytes it emitted before the option existed", () => {
|
|
331
347
|
const yaml = generateForgejoOpPipeline([
|
|
332
|
-
{ name: "actions-audit", schedule: "0 6 * * *", findingMode: "
|
|
348
|
+
{ name: "actions-audit", schedule: "0 6 * * *", findingMode: "issue" },
|
|
333
349
|
]).files[0].yaml;
|
|
334
350
|
expect(yaml).toBe(AUDIT_YAML_BEFORE_2257);
|
|
335
351
|
});
|
|
@@ -456,41 +472,23 @@ describe("generateForgejoOpPipeline: per-Op variables land on the job, not the w
|
|
|
456
472
|
});
|
|
457
473
|
|
|
458
474
|
/**
|
|
459
|
-
* chant #2294
|
|
460
|
-
* them
|
|
461
|
-
* push job, no `node -e` line writing to `$GITHUB_OUTPUT`, while github's
|
|
462
|
-
* generator (same spec) still carries both because its notice job still reads
|
|
463
|
-
* them.
|
|
475
|
+
* chant #2294 dropped the gated-apply outputs on Forgejo while nothing read
|
|
476
|
+
* them; #3773 brings back the notice job that does, and the outputs with it.
|
|
464
477
|
*/
|
|
465
|
-
describe("generateForgejoOpPipeline:
|
|
478
|
+
describe("generateForgejoOpPipeline: the outputs the notice job reads (#2294, #3773)", () => {
|
|
466
479
|
const pushSpec: ScheduledOpSpec = { name: "live-apply", trigger: { kind: "push", branches: ["main"] } };
|
|
467
480
|
|
|
468
|
-
test("the push job
|
|
469
|
-
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
470
|
-
expect(doc.jobs!["live-apply"].outputs).toBeUndefined();
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
test("the run step carries no node -e line writing to $GITHUB_OUTPUT", () => {
|
|
481
|
+
test("the push job publishes what it stopped on, as on github", () => {
|
|
474
482
|
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
475
|
-
|
|
476
|
-
expect(step?.run).not.toContain("node -e");
|
|
477
|
-
expect(step?.run).not.toContain("GITHUB_OUTPUT");
|
|
478
|
-
// The tee'd invocation survives — it's what puts the run's own record in
|
|
479
|
-
// the log, gate-notice job or not.
|
|
480
|
-
expect(step?.run).toContain("| tee");
|
|
481
|
-
expect(step?.run).toContain("chant run live-apply --gated-exit 0 --json");
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
test("github, generated from the same spec, still carries both — the drop is forgejo's dialect, not the shared builder", () => {
|
|
485
|
-
const gh = parseFile(generateGithubOpPipeline([pushSpec]).files[0].yaml);
|
|
486
|
-
const ghStep = gh.jobs!["live-apply"].steps.find((s) => s.id === "chant-run");
|
|
487
|
-
expect(gh.jobs!["live-apply"].outputs).toEqual({
|
|
483
|
+
expect(doc.jobs!["live-apply"].outputs).toEqual({
|
|
488
484
|
gated: "${{ steps.chant-run.outputs.gated }}",
|
|
489
485
|
op: "${{ steps.chant-run.outputs.op }}",
|
|
490
486
|
gate: "${{ steps.chant-run.outputs.gate }}",
|
|
491
487
|
approve: "${{ steps.chant-run.outputs.approve }}",
|
|
492
488
|
});
|
|
493
|
-
|
|
489
|
+
const step = doc.jobs!["live-apply"].steps.find((s) => s.id === "chant-run");
|
|
490
|
+
expect(step?.run).toContain("GITHUB_OUTPUT");
|
|
491
|
+
expect(step?.run).toContain("chant run live-apply --gated-exit 0 --json");
|
|
494
492
|
});
|
|
495
493
|
|
|
496
494
|
test("a non-push (cron) job carries no outputs and no gate script either way", () => {
|
|
@@ -29,62 +29,28 @@
|
|
|
29
29
|
* the runner can fetch it.
|
|
30
30
|
*
|
|
31
31
|
* `comment` (#2231) crosses over, since chant #2291: it posts onto the
|
|
32
|
-
* triggering pull request
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `reconcilePr`'s `postOrUpdateComment` (`packages/core/src/op/activities/reconcile.ts`)
|
|
41
|
-
* for the fix.
|
|
32
|
+
* triggering pull request against `${GITHUB_API_URL}`, and a Forgejo Actions
|
|
33
|
+
* job already sets that (and `github.token`) the same way a GitHub Actions
|
|
34
|
+
* job does. This generator used to refuse the mode by name here, on the
|
|
35
|
+
* premise that chant had no way to reach a Forgejo instance; that premise
|
|
36
|
+
* was checked against a real Forgejo 12.0.4+gitea-1.22.0 instance during
|
|
37
|
+
* INTENTIUS/choudoufu#1027 and found false. Since chant #3757 the post goes
|
|
38
|
+
* through the Forgejo client in `packages/core/src/pr-forge.ts` over `fetch`
|
|
39
|
+
* rather than through `gh`, so the job's image needs no `gh` either.
|
|
42
40
|
*
|
|
43
|
-
* `issue`
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* `
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* PATCH calls carried only `GH_TOKEN` (in fact, on the `issue` path, not even
|
|
57
|
-
* that — `reconcilePr` handed it `execAsync` with no `env` override at all,
|
|
58
|
-
* chant #2320), and `gh`'s own documented environment variables (`gh help
|
|
59
|
-
* environment`) scope `GH_TOKEN`/`GITHUB_TOKEN` to "github.com or a subdomain
|
|
60
|
-
* of ghe.com" — never a self-hosted Forgejo. A `GH_DEBUG=api` POST against
|
|
61
|
-
* the live instance, with `GH_TOKEN` and `GITHUB_API_URL` set exactly as the
|
|
62
|
-
* generated workflow sets them, sent no `Authorization` header at all and
|
|
63
|
-
* Forgejo answered `{"message":"token is required"}` (HTTP 401); adding
|
|
64
|
-
* `GH_HOST` alongside `GH_TOKEN` made no difference. The same shape was what
|
|
65
|
-
* `postOrUpdateComment`'s writes carried too, which is why #2304's "verified
|
|
66
|
-
* against a real Forgejo instance" could not have gone through the generated
|
|
67
|
-
* workflow's own credential path — either that session had a `gh auth login`
|
|
68
|
-
* already stored for the test instance, which a real Actions job's fresh
|
|
69
|
-
* checkout never has, or a different `gh` build was in play.
|
|
70
|
-
*
|
|
71
|
-
* Chant #2333 fixed that credential for both modes: `ghCredentialEnv` now
|
|
72
|
-
* forwards the resolved token as `GH_ENTERPRISE_TOKEN` beside `GH_TOKEN`,
|
|
73
|
-
* which is the variable `gh` reads for a host in neither of the two classes
|
|
74
|
-
* above. #2333's own probe against the same image also narrowed the claim
|
|
75
|
-
* made here: `GH_ENTERPRISE_TOKEN` authenticated the identical call *without*
|
|
76
|
-
* a `GH_HOST` beside it, because the full URL #2291 built already names the
|
|
77
|
-
* host. So the premise this refusal was reinstated on no longer holds, and
|
|
78
|
-
* both halves of `postOrUpdateGithubIssue` — the paginated read and the
|
|
79
|
-
* POST/PATCH write — were driven green end to end against a live
|
|
80
|
-
* 12.0.4+gitea-1.22.0 instance under #2333, from a shell with no stored
|
|
81
|
-
* `gh auth login`.
|
|
82
|
-
*
|
|
83
|
-
* The refusal below is nonetheless left standing here, because lifting it is
|
|
84
|
-
* a generator behavior change with its own YAML surface to settle and #2333
|
|
85
|
-
* was scoped to the credential. Lifting it is filed separately; this comment
|
|
86
|
-
* is corrected rather than acted on so the stated reason does not outlive the
|
|
87
|
-
* fact it rested on.
|
|
41
|
+
* `issue` crosses over too, since chant #2351. It was refused here from
|
|
42
|
+
* #2332 on, after chant #2315 drove the mode against a real Forgejo
|
|
43
|
+
* 12.0.4+gitea-1.22.0 instance: the paginated read and the `.pull_request ==
|
|
44
|
+
* null` filter behaved as on GitHub, but every POST and PATCH answered
|
|
45
|
+
* `{"message":"token is required"}` (HTTP 401), because `gh` sends
|
|
46
|
+
* `GH_TOKEN` only to github.com and `ghe.com` hosts. #2333 fixed that
|
|
47
|
+
* credential, and #3757 moved the mode off `gh` onto the Forgejo client in
|
|
48
|
+
* `packages/core/src/pr-forge.ts`, which sends `Authorization: token …`
|
|
49
|
+
* itself. The job this emits for a cron `issue` Op is github's, unchanged:
|
|
50
|
+
* the same trigger, and the same `GH_TOKEN`/`GITHUB_TOKEN` from
|
|
51
|
+
* `github.token` on the run step, which is the credential the mode reads.
|
|
52
|
+
* Only `permissions:` is dropped, as for every mode, so what that token may
|
|
53
|
+
* write on a scheduled run is the runner's configuration.
|
|
88
54
|
*
|
|
89
55
|
* A spec's `environment` (#2257) is dropped on the same terms as
|
|
90
56
|
* `permissions:`, and for a stronger reason: Forgejo Actions has no
|
|
@@ -97,25 +63,18 @@
|
|
|
97
63
|
* still holds the apply here is chant's own gate (#2119), which is a fact on
|
|
98
64
|
* the `chant/lifecycle` branch and needs nothing from the forge.
|
|
99
65
|
*
|
|
100
|
-
* The gated-apply notice job (#2243)
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* `
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* approve }` on the Op's job, and the `node -e` step that writes them to
|
|
113
|
-
* `$GITHUB_OUTPUT` — does not survive it either (#2294). Both existed only for
|
|
114
|
-
* that job to read via `needs.<job>.outputs`; with the job gone, so is every
|
|
115
|
-
* reader, and `buildGithubOpPipelineDocs` is asked for neither
|
|
116
|
-
* (`emitGatedOutputs: false` below) rather than emitting them here and
|
|
117
|
-
* stripping them back out. Should the Forgejo posting work in #2291 grow a
|
|
118
|
-
* notice job of its own, this is the one flag that brings both back.
|
|
66
|
+
* The gated-apply notice job (#2243) crosses over since #3773. It used to
|
|
67
|
+
* shell to `gh` on GitHub's hosted runner image, which a Forgejo runner
|
|
68
|
+
* neither ships nor can point at its own instance, so it was dropped here
|
|
69
|
+
* along with the apply job's `gated`/`op`/`gate`/`approve` outputs that
|
|
70
|
+
* only it read (#2294). It now runs in the Op's own image and posts through
|
|
71
|
+
* `@intentius/chant/pr-forge`, whose Forgejo client finds the pull request
|
|
72
|
+
* a commit merged with `commits/{sha}/pull` and sends `Authorization: token`
|
|
73
|
+
* itself. So the doc rebuilt below carries `gatedNoticeDoc` through the
|
|
74
|
+
* same dialect transform as the jobs, which drops its `permissions:` the
|
|
75
|
+
* way it drops every scope but `id-token`. A `push` job also still runs
|
|
76
|
+
* with `--gated-exit 0`, and `chant run` still writes the gate and the
|
|
77
|
+
* approve command to `GITHUB_STEP_SUMMARY`.
|
|
119
78
|
*
|
|
120
79
|
* A spec's own `variables` (#2290) crosses over unchanged: forgejo reuses the
|
|
121
80
|
* same job-level `env:` github's builder emits, and the dialect transform
|
|
@@ -186,31 +145,11 @@ export function generateForgejoOpPipeline(
|
|
|
186
145
|
options: ComponentPipelineOptions = {},
|
|
187
146
|
dialectOptions: ForgejoDialectOptions = {},
|
|
188
147
|
): OpPipelineResult {
|
|
189
|
-
// `findingMode: "comment"` used to be refused by name here (#2231)
|
|
190
|
-
//
|
|
191
|
-
// the
|
|
192
|
-
|
|
193
|
-
// `findingMode: "issue"` is refused by name here (#2315): unlike `comment`,
|
|
194
|
-
// a real Forgejo instance did not clear it — see the module doc above for
|
|
195
|
-
// the reproduction.
|
|
196
|
-
for (const spec of ops) {
|
|
197
|
-
if (spec.findingMode === "issue") {
|
|
198
|
-
throw new Error(
|
|
199
|
-
`Scheduled Op "${spec.name}" has findingMode "issue", which opens or edits a GitHub-shaped issue by ` +
|
|
200
|
-
`shelling to \`gh\`. Checked against a real Forgejo 12.0.4+gitea-1.22.0 instance (chant #2315): the ` +
|
|
201
|
-
`search this mode does works there, but the POST/PATCH it writes with does not — \`gh\`'s own ` +
|
|
202
|
-
`GH_TOKEN/GITHUB_TOKEN only authenticate a request to github.com or a ghe.com subdomain, never a ` +
|
|
203
|
-
`self-hosted Forgejo, and neither this mode nor its caller sets GH_HOST or GH_ENTERPRISE_TOKEN, so ` +
|
|
204
|
-
`every write fails with Forgejo's "token is required" (HTTP 401). Use findingMode "comment" on a ` +
|
|
205
|
-
`pull_request trigger here, or generate this Op for github.`,
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
148
|
+
// `findingMode: "comment"` used to be refused by name here (#2231), and
|
|
149
|
+
// `findingMode: "issue"` from #2332 to #2351. Neither is now: both post
|
|
150
|
+
// through the Forgejo client — see the module doc above.
|
|
209
151
|
|
|
210
|
-
|
|
211
|
-
// (`gatedNoticeDoc` never crosses the dialect, below), so the job outputs
|
|
212
|
-
// and the `node -e` step that populate them would have no reader.
|
|
213
|
-
const { files, jobs } = buildGithubOpPipelineDocs(ops, options, { emitGatedOutputs: false });
|
|
152
|
+
const { files, jobs } = buildGithubOpPipelineDocs(ops, options);
|
|
214
153
|
|
|
215
154
|
return {
|
|
216
155
|
// One file per spec, in spec order, which is what lets the header below
|
|
@@ -227,6 +166,8 @@ export function generateForgejoOpPipeline(
|
|
|
227
166
|
concurrency: forgejoize(doc.concurrency, dialectOptions),
|
|
228
167
|
permissions: {},
|
|
229
168
|
jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialectOptions),
|
|
169
|
+
// The gate-notice job (#3773), dialect applied like the Op's own job.
|
|
170
|
+
...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialectOptions) } : {}),
|
|
230
171
|
};
|
|
231
172
|
return { name, yaml: emitOpPipelineYAML(forgejoDoc) };
|
|
232
173
|
}),
|
|
@@ -47,4 +47,19 @@ describe("generateForgejoOpWavesPipeline", () => {
|
|
|
47
47
|
expect(files[1]!.yaml).toContain("pull_request:");
|
|
48
48
|
expect(files[1]!.yaml).toContain("run: chant run wave --spec waves.json --record-plans");
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
test("a wave-level plan and apply, and stage hooks, render the same jobs: chant run wave runs them (#3762)", () => {
|
|
52
|
+
const { plan: _plan, ...rest } = spec;
|
|
53
|
+
const waveLevel: OpWavesSpec = {
|
|
54
|
+
...rest,
|
|
55
|
+
planWave: ["./bin/plan-wave", "{runs}", "{plans}"],
|
|
56
|
+
applyWave: ["./bin/apply-wave", "{runs}"],
|
|
57
|
+
hooks: { before: { apply: [{ command: ["./bin/check", "{runs}"], onFailure: "approve" }] } },
|
|
58
|
+
};
|
|
59
|
+
const plain = generateForgejoOpWavesPipeline(spec, { specFile: "waves.json" });
|
|
60
|
+
const rendered = generateForgejoOpWavesPipeline(waveLevel, { specFile: "waves.json" });
|
|
61
|
+
expect(rendered.files).toEqual(plain.files);
|
|
62
|
+
expect(rendered.jobs).toEqual(plain.jobs);
|
|
63
|
+
expect(() => generateForgejoOpWavesPipeline({ ...waveLevel, hooks: { after: { apply: [{ command: ["./bin/smoke"], onFailure: "approve" }] } } }, { specFile: "waves.json" })).toThrow(/nothing left to hold/);
|
|
64
|
+
});
|
|
50
65
|
});
|