@intentius/chant-lexicon-forgejo 0.60.0 → 0.62.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 +32 -6
- 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 +178 -14
- package/src/components/generate-op-pipeline.ts +39 -17
- package/src/serializer.test.ts +25 -4
|
@@ -28,12 +28,20 @@
|
|
|
28
28
|
* no mapping in ../actions.ts passes through verbatim and resolves only if
|
|
29
29
|
* the runner can fetch it.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
* triggering pull request by shelling to `gh` against
|
|
33
|
-
*
|
|
34
|
-
* GitHub
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* `comment` (#2231) crosses over too, since chant #2291: it posts onto the
|
|
32
|
+
* triggering pull request by shelling to `gh` against `${GITHUB_API_URL}`,
|
|
33
|
+
* and a Forgejo Actions job already sets that (and `github.token`) the same
|
|
34
|
+
* way a GitHub Actions job does. This generator used to refuse the mode by
|
|
35
|
+
* name here, on the premise that chant had no way to point `gh` at a Forgejo
|
|
36
|
+
* instance; that premise was checked against a real Forgejo
|
|
37
|
+
* 12.0.4+gitea-1.22.0 instance during INTENTIUS/choudoufu#1027 and found
|
|
38
|
+
* false — the failure was `gh api` resolving a *relative* path against
|
|
39
|
+
* `/api/v3`, which Forgejo does not serve, not an unreachable forge. See
|
|
40
|
+
* `reconcilePr`'s `postOrUpdateComment` (`packages/core/src/op/activities/reconcile.ts`)
|
|
41
|
+
* for the fix. `issue` mode (`gh issue create`) was not part of that
|
|
42
|
+
* verification — only the comment endpoints were exercised on the real
|
|
43
|
+
* instance — so it remains un-refused-but-unverified here, exactly as it was
|
|
44
|
+
* on GitHub before this change.
|
|
37
45
|
*
|
|
38
46
|
* A spec's `environment` (#2257) is dropped on the same terms as
|
|
39
47
|
* `permissions:`, and for a stronger reason: Forgejo Actions has no
|
|
@@ -56,6 +64,24 @@
|
|
|
56
64
|
* Forgejo apply that stops at its gate is a green run rather than a red one,
|
|
57
65
|
* and `chant run` still writes the gate and the approve command to
|
|
58
66
|
* `GITHUB_STEP_SUMMARY`, which Forgejo Actions sets like GitHub does.
|
|
67
|
+
*
|
|
68
|
+
* The gate-notice job's own reason for being — `outputs: { gated, op, gate,
|
|
69
|
+
* approve }` on the Op's job, and the `node -e` step that writes them to
|
|
70
|
+
* `$GITHUB_OUTPUT` — does not survive it either (#2294). Both existed only for
|
|
71
|
+
* that job to read via `needs.<job>.outputs`; with the job gone, so is every
|
|
72
|
+
* reader, and `buildGithubOpPipelineDocs` is asked for neither
|
|
73
|
+
* (`emitGatedOutputs: false` below) rather than emitting them here and
|
|
74
|
+
* stripping them back out. Should the Forgejo posting work in #2291 grow a
|
|
75
|
+
* notice job of its own, this is the one flag that brings both back.
|
|
76
|
+
*
|
|
77
|
+
* A spec's own `variables` (#2290) crosses over unchanged: forgejo reuses the
|
|
78
|
+
* same job-level `env:` github's builder emits, and the dialect transform
|
|
79
|
+
* touches only `permissions:`/`environment:`/action refs, never a job's `env:`
|
|
80
|
+
* mapping. That is what makes a per-Op credential expressible here at all —
|
|
81
|
+
* `ComponentPipelineOptions.variables` is workflow-scoped and, on Forgejo,
|
|
82
|
+
* Actions mints no OIDC token to put in `setup` instead, so a job-level
|
|
83
|
+
* `variables` entry is the only way one Op's job can hold a credential no
|
|
84
|
+
* sibling Op's job receives.
|
|
59
85
|
*/
|
|
60
86
|
import type { ComponentPipelineOptions, OpPipelineResult, ScheduledOpSpec } from "@intentius/chant/lexicon";
|
|
61
87
|
import { type ForgejoDialectOptions } from "../dialect.js";
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;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,CA2BlB"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "3f69907e1588ef81d9bbf78e57ba60a06e4657ad54692aad9cc172ac21246ce6",
|
|
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": "a1a560429db736c187e0b34cf8dd9efc6ad771afefcbf6f802d160d3d5274257"
|
|
11
11
|
},
|
|
12
|
-
"composite": "
|
|
12
|
+
"composite": "c1b979e353a7bd9fbc449e1f98b765979d89d4e386ed94d8fce9ec20754ee514"
|
|
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.62.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.62.0",
|
|
58
|
+
"@intentius/chant-lexicon-github": "^0.62.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"generate": "tsx src/codegen/generate-cli.ts",
|
|
@@ -14,9 +14,11 @@ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
|
|
|
14
14
|
|
|
15
15
|
interface ParsedJob {
|
|
16
16
|
"runs-on"?: string;
|
|
17
|
+
container?: string;
|
|
17
18
|
environment?: Record<string, string>;
|
|
18
19
|
outputs?: Record<string, string>;
|
|
19
|
-
|
|
20
|
+
env?: Record<string, string>;
|
|
21
|
+
steps: Array<{ id?: string; uses?: string; run?: string; shell?: string }>;
|
|
20
22
|
}
|
|
21
23
|
interface ParsedDoc {
|
|
22
24
|
on?: Record<string, unknown>;
|
|
@@ -105,27 +107,45 @@ describe("generateForgejoOpPipeline: non-cron trigger survives the dialect trans
|
|
|
105
107
|
});
|
|
106
108
|
});
|
|
107
109
|
|
|
108
|
-
describe("generateForgejoOpPipeline:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
110
|
+
describe("generateForgejoOpPipeline: comment finding mode crosses over (#2291)", () => {
|
|
111
|
+
// Used to be refused by name here (#2231), on the premise that chant had no
|
|
112
|
+
// way to point `gh` at a Forgejo instance. Checked against a real Forgejo
|
|
113
|
+
// 12.0.4+gitea-1.22.0 instance during INTENTIUS/choudoufu#1027 and found
|
|
114
|
+
// false: Forgejo's own `/api/v1` takes the same GET/POST/PATCH calls
|
|
115
|
+
// `reconcilePr`'s `postOrUpdateComment` makes, once it targets the right
|
|
116
|
+
// URL. See `packages/core/src/op/activities/reconcile.test.ts` for the
|
|
117
|
+
// mocked-transport proof of that fix; this file only proves the generator
|
|
118
|
+
// no longer refuses the mode and produces the same job shape github does.
|
|
119
|
+
test("findingMode comment generates cleanly on the pull_request trigger it needs", () => {
|
|
115
120
|
const specs: ScheduledOpSpec[] = [
|
|
116
121
|
{ name: "app-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "comment" },
|
|
117
122
|
];
|
|
118
|
-
expect(() => generateForgejoOpPipeline(specs)).toThrow(
|
|
119
|
-
/Scheduled Op "app-plan".*findingMode "comment".*no Forgejo API client/s,
|
|
120
|
-
);
|
|
123
|
+
expect(() => generateForgejoOpPipeline(specs)).not.toThrow();
|
|
121
124
|
});
|
|
122
125
|
|
|
123
|
-
test("
|
|
126
|
+
test("job/trigger/findingMode parity with the github generator (same input, same job list)", () => {
|
|
124
127
|
const specs: ScheduledOpSpec[] = [
|
|
125
128
|
{ name: "app-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "comment" },
|
|
126
129
|
];
|
|
127
|
-
const
|
|
128
|
-
|
|
130
|
+
const fj = generateForgejoOpPipeline(specs);
|
|
131
|
+
const gh = generateGithubOpPipeline(specs);
|
|
132
|
+
expect(fj.jobs).toEqual(gh.jobs);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("the run step carries GH_TOKEN/GITHUB_TOKEN, same as github's — permissions: still dropped by the dialect", () => {
|
|
136
|
+
const specs: ScheduledOpSpec[] = [
|
|
137
|
+
{ name: "app-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "comment" },
|
|
138
|
+
];
|
|
139
|
+
const fjYaml = generateForgejoOpPipeline(specs).files[0].yaml;
|
|
140
|
+
const ghDoc = parseFile(generateGithubOpPipeline(specs).files[0].yaml);
|
|
141
|
+
|
|
142
|
+
expect(fjYaml).toContain("GH_TOKEN: '${{ github.token }}'");
|
|
143
|
+
expect(fjYaml).toContain("GITHUB_TOKEN: '${{ github.token }}'");
|
|
144
|
+
expect(fjYaml).not.toMatch(/^permissions:/m);
|
|
145
|
+
// github still declares the scope the mode needs — proving the omission
|
|
146
|
+
// above is the Forgejo dialect dropping `permissions:` wholesale, not a
|
|
147
|
+
// sign the mode generated with no token.
|
|
148
|
+
expect(ghDoc.permissions).toEqual({ contents: "read", "pull-requests": "write" });
|
|
129
149
|
});
|
|
130
150
|
});
|
|
131
151
|
|
|
@@ -198,6 +218,24 @@ describe("generateForgejoOpPipeline: the gated apply on push (#2243)", () => {
|
|
|
198
218
|
expect(Object.keys(fj.jobs ?? {})).toEqual(["app-apply"]);
|
|
199
219
|
expect(gh.jobs).toHaveProperty("app-apply-gate-notice");
|
|
200
220
|
});
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* chant #2299 — Forgejo Actions runs the same act_runner semantics as
|
|
224
|
+
* GitHub Actions: a job with `container:` (which this generator's Op job
|
|
225
|
+
* always carries, unconditionally) defaults to `sh`, and `sh` rejects `set
|
|
226
|
+
* -o pipefail`. This generator reuses github's `buildGithubOpPipelineDocs`
|
|
227
|
+
* to build the job, then the dialect transform in ../dialect.ts to cross
|
|
228
|
+
* it, so the fix has to survive both: neither step drops or renames
|
|
229
|
+
* `shell:`.
|
|
230
|
+
*/
|
|
231
|
+
test("the pipefail step keeps shell: bash across the Forgejo dialect — its job runs in a container, whose default shell is sh", () => {
|
|
232
|
+
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
233
|
+
const job = doc.jobs!["app-apply"];
|
|
234
|
+
expect(job.container).toBe("node:22-slim");
|
|
235
|
+
const step = job.steps.find((s) => s.id === "chant-run");
|
|
236
|
+
expect(step?.run).toContain("set -o pipefail");
|
|
237
|
+
expect(step?.shell).toBe("bash");
|
|
238
|
+
});
|
|
201
239
|
});
|
|
202
240
|
|
|
203
241
|
/**
|
|
@@ -277,3 +315,129 @@ describe("generateForgejoOpPipeline: a dropped deployment environment (#2257)",
|
|
|
277
315
|
).toThrow(/neither an absolute http\(s\) URL nor a/);
|
|
278
316
|
});
|
|
279
317
|
});
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* chant #2290 — per-Op credentials, so a pull-request job need not hold the
|
|
321
|
+
* apply credential. The consuming project's own shape: `live-check` (no
|
|
322
|
+
* `variables`) carries no credential while `live-apply` (`variables` set)
|
|
323
|
+
* carries one, both generated from forge-wide `options.variables` that name
|
|
324
|
+
* no credential at all.
|
|
325
|
+
*/
|
|
326
|
+
describe("generateForgejoOpPipeline: per-Op variables land on the job, not the workflow (#2290)", () => {
|
|
327
|
+
const FORGE_WIDE = { CHANT_FORGE: "forgejo", AWS_REGION: "${{ vars.AWS_REGION }}" };
|
|
328
|
+
const CREDENTIAL = { AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}" };
|
|
329
|
+
|
|
330
|
+
test("a job whose spec declares no variables carries none, even when the forge-wide options do", () => {
|
|
331
|
+
const doc = parseFile(
|
|
332
|
+
generateForgejoOpPipeline([{ name: "live-check", trigger: { kind: "pull_request", branches: ["main"] } }], {
|
|
333
|
+
variables: FORGE_WIDE,
|
|
334
|
+
}).files[0].yaml,
|
|
335
|
+
);
|
|
336
|
+
expect(doc.jobs!["live-check"].env).toBeUndefined();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("a job whose spec declares variables carries them as the job's own env:, beside the workflow env:", () => {
|
|
340
|
+
const doc = parseFile(
|
|
341
|
+
generateForgejoOpPipeline(
|
|
342
|
+
[{ name: "live-apply", trigger: { kind: "push", branches: ["main"] }, variables: CREDENTIAL }],
|
|
343
|
+
{ variables: FORGE_WIDE },
|
|
344
|
+
).files[0].yaml,
|
|
345
|
+
);
|
|
346
|
+
expect(doc.jobs!["live-apply"].env).toEqual(CREDENTIAL);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* The proof the issue names: a generated workflow whose `live-check` job
|
|
351
|
+
* carries no credential while its `live-apply` job does, from one forge-wide
|
|
352
|
+
* options object that itself names no credential — the property the
|
|
353
|
+
* consuming project (`examples/ci-pipelines`) needs to be able to state.
|
|
354
|
+
*/
|
|
355
|
+
test("live-check carries no credential while live-apply does, from the same forge-wide options", () => {
|
|
356
|
+
const files = generateForgejoOpPipeline(
|
|
357
|
+
[
|
|
358
|
+
{ name: "live-check", trigger: { kind: "pull_request", branches: ["main"] } },
|
|
359
|
+
{ name: "live-apply", trigger: { kind: "push", branches: ["main"] }, variables: CREDENTIAL },
|
|
360
|
+
],
|
|
361
|
+
{ variables: FORGE_WIDE },
|
|
362
|
+
).files;
|
|
363
|
+
const check = parseFile(files.find((f) => f.name === "live-check.yml")!.yaml);
|
|
364
|
+
const apply = parseFile(files.find((f) => f.name === "live-apply.yml")!.yaml);
|
|
365
|
+
|
|
366
|
+
expect(JSON.stringify(check.jobs!["live-check"])).not.toContain("AWS_ACCESS_KEY_ID");
|
|
367
|
+
expect(apply.jobs!["live-apply"].env).toEqual(CREDENTIAL);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("a per-Op key wins over a same-named forge-wide one — the job is more specific", () => {
|
|
371
|
+
const doc = parseFile(
|
|
372
|
+
generateForgejoOpPipeline(
|
|
373
|
+
[
|
|
374
|
+
{
|
|
375
|
+
name: "live-apply",
|
|
376
|
+
trigger: { kind: "push", branches: ["main"] },
|
|
377
|
+
variables: { CHANT_FORGE: "overridden" },
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
{ variables: FORGE_WIDE },
|
|
381
|
+
).files[0].yaml,
|
|
382
|
+
);
|
|
383
|
+
expect(doc.jobs!["live-apply"].env).toEqual({ CHANT_FORGE: "overridden" });
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("a spec declaring variables changes exactly the job's env: block and nothing else", () => {
|
|
387
|
+
const spec: ScheduledOpSpec = { name: "live-apply", trigger: { kind: "push", branches: ["main"] } };
|
|
388
|
+
const before = generateForgejoOpPipeline([spec]).files[0].yaml;
|
|
389
|
+
const after = generateForgejoOpPipeline([{ ...spec, variables: CREDENTIAL }]).files[0].yaml;
|
|
390
|
+
expect(after).toContain(" env:\n AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'\n");
|
|
391
|
+
expect(
|
|
392
|
+
after.replace(" env:\n AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'\n", ""),
|
|
393
|
+
).toBe(before);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* chant #2294 — the gated-apply outputs survive the notice job that consumed
|
|
399
|
+
* them. Asserted directly against forgejo's own output: no `outputs:` on the
|
|
400
|
+
* push job, no `node -e` line writing to `$GITHUB_OUTPUT`, while github's
|
|
401
|
+
* generator (same spec) still carries both because its notice job still reads
|
|
402
|
+
* them.
|
|
403
|
+
*/
|
|
404
|
+
describe("generateForgejoOpPipeline: no unconsumed outputs (#2294)", () => {
|
|
405
|
+
const pushSpec: ScheduledOpSpec = { name: "live-apply", trigger: { kind: "push", branches: ["main"] } };
|
|
406
|
+
|
|
407
|
+
test("the push job carries no outputs: block", () => {
|
|
408
|
+
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
409
|
+
expect(doc.jobs!["live-apply"].outputs).toBeUndefined();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("the run step carries no node -e line writing to $GITHUB_OUTPUT", () => {
|
|
413
|
+
const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
|
|
414
|
+
const step = doc.jobs!["live-apply"].steps.find((s) => s.id === "chant-run");
|
|
415
|
+
expect(step?.run).not.toContain("node -e");
|
|
416
|
+
expect(step?.run).not.toContain("GITHUB_OUTPUT");
|
|
417
|
+
// The tee'd invocation survives — it's what puts the run's own record in
|
|
418
|
+
// the log, gate-notice job or not.
|
|
419
|
+
expect(step?.run).toContain("| tee");
|
|
420
|
+
expect(step?.run).toContain("chant run live-apply --gated-exit 0 --json");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("github, generated from the same spec, still carries both — the drop is forgejo's dialect, not the shared builder", () => {
|
|
424
|
+
const gh = parseFile(generateGithubOpPipeline([pushSpec]).files[0].yaml);
|
|
425
|
+
const ghStep = gh.jobs!["live-apply"].steps.find((s) => s.id === "chant-run");
|
|
426
|
+
expect(gh.jobs!["live-apply"].outputs).toEqual({
|
|
427
|
+
gated: "${{ steps.chant-run.outputs.gated }}",
|
|
428
|
+
op: "${{ steps.chant-run.outputs.op }}",
|
|
429
|
+
gate: "${{ steps.chant-run.outputs.gate }}",
|
|
430
|
+
approve: "${{ steps.chant-run.outputs.approve }}",
|
|
431
|
+
});
|
|
432
|
+
expect(ghStep?.run).toContain("node -e");
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("a non-push (cron) job carries no outputs and no gate script either way", () => {
|
|
436
|
+
const doc = parseFile(
|
|
437
|
+
generateForgejoOpPipeline([{ name: "live-discover", schedule: "0 6 * * *" }]).files[0].yaml,
|
|
438
|
+
);
|
|
439
|
+
expect(doc.jobs!["live-discover"].outputs).toBeUndefined();
|
|
440
|
+
const step = doc.jobs!["live-discover"].steps.find((s) => typeof s.run === "string");
|
|
441
|
+
expect(step?.run).not.toContain("node -e");
|
|
442
|
+
});
|
|
443
|
+
});
|
|
@@ -28,12 +28,20 @@
|
|
|
28
28
|
* no mapping in ../actions.ts passes through verbatim and resolves only if
|
|
29
29
|
* the runner can fetch it.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
* triggering pull request by shelling to `gh` against
|
|
33
|
-
*
|
|
34
|
-
* GitHub
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* `comment` (#2231) crosses over too, since chant #2291: it posts onto the
|
|
32
|
+
* triggering pull request by shelling to `gh` against `${GITHUB_API_URL}`,
|
|
33
|
+
* and a Forgejo Actions job already sets that (and `github.token`) the same
|
|
34
|
+
* way a GitHub Actions job does. This generator used to refuse the mode by
|
|
35
|
+
* name here, on the premise that chant had no way to point `gh` at a Forgejo
|
|
36
|
+
* instance; that premise was checked against a real Forgejo
|
|
37
|
+
* 12.0.4+gitea-1.22.0 instance during INTENTIUS/choudoufu#1027 and found
|
|
38
|
+
* false — the failure was `gh api` resolving a *relative* path against
|
|
39
|
+
* `/api/v3`, which Forgejo does not serve, not an unreachable forge. See
|
|
40
|
+
* `reconcilePr`'s `postOrUpdateComment` (`packages/core/src/op/activities/reconcile.ts`)
|
|
41
|
+
* for the fix. `issue` mode (`gh issue create`) was not part of that
|
|
42
|
+
* verification — only the comment endpoints were exercised on the real
|
|
43
|
+
* instance — so it remains un-refused-but-unverified here, exactly as it was
|
|
44
|
+
* on GitHub before this change.
|
|
37
45
|
*
|
|
38
46
|
* A spec's `environment` (#2257) is dropped on the same terms as
|
|
39
47
|
* `permissions:`, and for a stronger reason: Forgejo Actions has no
|
|
@@ -56,6 +64,24 @@
|
|
|
56
64
|
* Forgejo apply that stops at its gate is a green run rather than a red one,
|
|
57
65
|
* and `chant run` still writes the gate and the approve command to
|
|
58
66
|
* `GITHUB_STEP_SUMMARY`, which Forgejo Actions sets like GitHub does.
|
|
67
|
+
*
|
|
68
|
+
* The gate-notice job's own reason for being — `outputs: { gated, op, gate,
|
|
69
|
+
* approve }` on the Op's job, and the `node -e` step that writes them to
|
|
70
|
+
* `$GITHUB_OUTPUT` — does not survive it either (#2294). Both existed only for
|
|
71
|
+
* that job to read via `needs.<job>.outputs`; with the job gone, so is every
|
|
72
|
+
* reader, and `buildGithubOpPipelineDocs` is asked for neither
|
|
73
|
+
* (`emitGatedOutputs: false` below) rather than emitting them here and
|
|
74
|
+
* stripping them back out. Should the Forgejo posting work in #2291 grow a
|
|
75
|
+
* notice job of its own, this is the one flag that brings both back.
|
|
76
|
+
*
|
|
77
|
+
* A spec's own `variables` (#2290) crosses over unchanged: forgejo reuses the
|
|
78
|
+
* same job-level `env:` github's builder emits, and the dialect transform
|
|
79
|
+
* touches only `permissions:`/`environment:`/action refs, never a job's `env:`
|
|
80
|
+
* mapping. That is what makes a per-Op credential expressible here at all —
|
|
81
|
+
* `ComponentPipelineOptions.variables` is workflow-scoped and, on Forgejo,
|
|
82
|
+
* Actions mints no OIDC token to put in `setup` instead, so a job-level
|
|
83
|
+
* `variables` entry is the only way one Op's job can hold a credential no
|
|
84
|
+
* sibling Op's job receives.
|
|
59
85
|
*/
|
|
60
86
|
|
|
61
87
|
import {
|
|
@@ -117,18 +143,14 @@ export function generateForgejoOpPipeline(
|
|
|
117
143
|
options: ComponentPipelineOptions = {},
|
|
118
144
|
dialectOptions: ForgejoDialectOptions = {},
|
|
119
145
|
): OpPipelineResult {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`Scheduled Op "${spec.name}" has findingMode "comment", which posts its finding on the pull request ` +
|
|
124
|
-
`that triggered the run. That activity shells to \`gh\` against the GitHub API and reads the ` +
|
|
125
|
-
`GitHub Actions event payload; chant carries no Forgejo API client to post the equivalent comment ` +
|
|
126
|
-
`(#2231). Use findingMode "issue" here, or generate this Op for github.`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
146
|
+
// `findingMode: "comment"` used to be refused by name here (#2231); lifted
|
|
147
|
+
// in #2291 once a real Forgejo instance showed the forge itself was never
|
|
148
|
+
// the obstacle — see the module doc above.
|
|
130
149
|
|
|
131
|
-
|
|
150
|
+
// `emitGatedOutputs: false` (#2294): forgejo never carries a gate-notice job
|
|
151
|
+
// (`gatedNoticeDoc` never crosses the dialect, below), so the job outputs
|
|
152
|
+
// and the `node -e` step that populate them would have no reader.
|
|
153
|
+
const { files, jobs } = buildGithubOpPipelineDocs(ops, options, { emitGatedOutputs: false });
|
|
132
154
|
|
|
133
155
|
return {
|
|
134
156
|
// One file per spec, in spec order, which is what lets the header below
|
package/src/serializer.test.ts
CHANGED
|
@@ -128,10 +128,27 @@ describe("forgejoSerializer — multi-workflow output", () => {
|
|
|
128
128
|
// "@intentius/chant-lexicon-github"`); nothing forgejo-specific is written for
|
|
129
129
|
// it. This is the functional check that inheritance actually holds, not just
|
|
130
130
|
// that the type is importable: the dialect still drops the job's
|
|
131
|
-
// `permissions` and remaps its runner label,
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
131
|
+
// `permissions` and remaps its runner label, and the sticky-comment step — a
|
|
132
|
+
// plain `gh api` script with no `uses:` at all — passes through the dialect
|
|
133
|
+
// transform untouched, because the dialect only ever rewrites job-level
|
|
134
|
+
// `permissions:`/`environment:`/action refs and never looks at step content.
|
|
135
|
+
//
|
|
136
|
+
// That passing-through is a fact about serialization, not about whether the
|
|
137
|
+
// script would work if run: chant #2291 settled, against a real Forgejo
|
|
138
|
+
// 12.0.4+gitea-1.22.0 instance, that Forgejo's `/api/v1` does accept the same
|
|
139
|
+
// GET/POST/PATCH calls this script makes — but only when they target a full
|
|
140
|
+
// URL. Until chant #2305, this script's `gh api "repos/$REPO/issues/…"` took
|
|
141
|
+
// a bare relative path, which `gh` resolves against `/api/v3` on any host but
|
|
142
|
+
// github.com, and Forgejo does not serve `/api/v3`. `reconcilePr`'s
|
|
143
|
+
// `postOrUpdateComment` got the URL fix #2291 made
|
|
144
|
+
// (`packages/core/src/op/activities/reconcile.ts`), and `PrPlanReport`'s own
|
|
145
|
+
// script (`lexicons/github/src/composites/pr-plan-report.ts`) — out of
|
|
146
|
+
// #2291's scope, being the component composite rather than the Op generator —
|
|
147
|
+
// got the equivalent fix in #2305: every call now targets `$api_base`, built
|
|
148
|
+
// from `$GITHUB_API_URL` inline in the shell script. The dialect still leaves
|
|
149
|
+
// step content untouched, so that base-URL construction crosses over exactly
|
|
150
|
+
// as written, which is what makes the untouched script below a working
|
|
151
|
+
// Forgejo comment rather than just a passed-through one.
|
|
135
152
|
describe("forgejoSerializer — inherits PrPlanReport from github (#1983)", () => {
|
|
136
153
|
test("dialect still applies to a github-lexicon composite's job", () => {
|
|
137
154
|
const workflow = new Workflow({
|
|
@@ -154,6 +171,10 @@ describe("forgejoSerializer — inherits PrPlanReport from github (#1983)", () =
|
|
|
154
171
|
// Including the `-F` that reads the body from plan.md — forgejo renders
|
|
155
172
|
// the same script, so the #2236 regression would show up here as well.
|
|
156
173
|
expect(result.primary).toContain("-F body=@plan.md");
|
|
174
|
+
// And the #2305 fix: the resolved-base construction and every `gh api`
|
|
175
|
+
// call built from it cross the dialect untouched too.
|
|
176
|
+
expect(result.primary).toContain('api_base="${GITHUB_API_URL%/}"');
|
|
177
|
+
expect(result.primary).not.toMatch(/gh api[^\n]*"repos\//);
|
|
157
178
|
expect(result.primary).not.toContain("-f body=@");
|
|
158
179
|
});
|
|
159
180
|
});
|