@intentius/chant-lexicon-forgejo 0.62.0 → 0.64.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.
@@ -28,7 +28,7 @@
28
28
  * no mapping in ../actions.ts passes through verbatim and resolves only if
29
29
  * the runner can fetch it.
30
30
  *
31
- * `comment` (#2231) crosses over too, since chant #2291: it posts onto the
31
+ * `comment` (#2231) crosses over, since chant #2291: it posts onto the
32
32
  * triggering pull request by shelling to `gh` against `${GITHUB_API_URL}`,
33
33
  * and a Forgejo Actions job already sets that (and `github.token`) the same
34
34
  * way a GitHub Actions job does. This generator used to refuse the mode by
@@ -38,10 +38,42 @@
38
38
  * false — the failure was `gh api` resolving a *relative* path against
39
39
  * `/api/v3`, which Forgejo does not serve, not an unreachable forge. See
40
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.
41
+ * for the fix.
42
+ *
43
+ * `issue` (`gh issue create`/`postOrUpdateGithubIssue`) does not cross over,
44
+ * and chant #2315 is why: it remained un-refused-but-unverified after #2304
45
+ * lifted `comment`'s refusal, and settling that gap the same way — against a
46
+ * real instance rather than a mock — found a real failure, so the refusal
47
+ * below is reinstated rather than lifted. Tested against the same
48
+ * 12.0.4+gitea-1.22.0 image (`codeberg.org/forgejo/forgejo:12`) with a repo,
49
+ * an issue, and a pull request created on it: the *read* half of
50
+ * `postOrUpdateGithubIssue` checks out fine on Forgejo — plain paginated
51
+ * `GET .../issues?state=open` (no `/search/issues`, confirmed absent from a
52
+ * live Forgejo's own OpenAPI spec) returns pull requests interleaved with
53
+ * issues exactly as GitHub's endpoint does, and the `.pull_request == null`
54
+ * filter and the marker `startswith` match both behave identically to
55
+ * GitHub. The *write* half does not: `postOrUpdateGithubIssue`'s POST and
56
+ * PATCH calls carry only `GH_TOKEN` (in fact, on the `issue` path, not even
57
+ * that — `reconcilePr` hands 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. Only `GH_ENTERPRISE_TOKEN`
65
+ * paired with a matching `GH_HOST` authenticated the same call. Since neither
66
+ * `postOrUpdateGithubIssue` nor its caller sets either, every write this mode
67
+ * makes on Forgejo fails — not a slow search, a 401 on every run. The same
68
+ * shape (`GH_TOKEN` only, no `GH_HOST`/`GH_ENTERPRISE_TOKEN`) is what
69
+ * `postOrUpdateComment`'s writes carry too, which means #2304's "verified
70
+ * against a real Forgejo instance" could not have gone through the generated
71
+ * workflow's own credential path — either that session had a `gh auth login`
72
+ * already stored for the test instance, which a real Actions job's fresh
73
+ * checkout never has, or a different `gh` build was in play. That is
74
+ * `comment` mode's problem to re-verify, not `issue` mode's, and out of
75
+ * scope for chant #2315; flagged here because finding it is what closes that
76
+ * issue's question of whether to lift or reinstate this refusal.
45
77
  *
46
78
  * A spec's `environment` (#2257) is dropped on the same terms as
47
79
  * `permissions:`, and for a stronger reason: Forgejo Actions has no
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoHG;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,CA4ClB"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "3f69907e1588ef81d9bbf78e57ba60a06e4657ad54692aad9cc172ac21246ce6",
4
+ "manifest.json": "b27a0caa6602113e3055867aa963f678a9711619cf73741170ae05938f562af0",
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": "c1b979e353a7bd9fbc449e1f98b765979d89d4e386ed94d8fce9ec20754ee514"
12
+ "composite": "a8a40b48d5887134deb8e9feb9ffa1ea5502fd1e4c9ce7462f48d10368e1d881"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
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.62.0",
3
+ "version": "0.64.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.62.0",
58
- "@intentius/chant-lexicon-github": "^0.62.0"
57
+ "@intentius/chant": "^0.64.0",
58
+ "@intentius/chant-lexicon-github": "^0.64.0"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
@@ -33,7 +33,7 @@ describe("generateForgejoOpPipeline: structure (github-shaped)", () => {
33
33
  test("one file per scheduled Op, each a valid workflow with one job", () => {
34
34
  const specs: ScheduledOpSpec[] = [
35
35
  { name: "actions-audit", schedule: "0 6 * * *" },
36
- { name: "prod-reconcile", schedule: "0 * * * *", findingMode: "issue" },
36
+ { name: "prod-reconcile", schedule: "0 * * * *", findingMode: "pull-request" },
37
37
  ];
38
38
  const result = generateForgejoOpPipeline(specs);
39
39
 
@@ -78,7 +78,7 @@ describe("generateForgejoOpPipeline: dialect applied", () => {
78
78
  describe("generateForgejoOpPipeline: non-cron trigger survives the dialect transform (#2084)", () => {
79
79
  test("a pull_request trigger round-trips through the Forgejo dialect, with permissions: still dropped", () => {
80
80
  const specs: ScheduledOpSpec[] = [
81
- { name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "issue" },
81
+ { name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "pull-request" },
82
82
  ];
83
83
  const fj = generateForgejoOpPipeline(specs);
84
84
  const gh = generateGithubOpPipeline(specs);
@@ -149,6 +149,35 @@ describe("generateForgejoOpPipeline: comment finding mode crosses over (#2291)",
149
149
  });
150
150
  });
151
151
 
152
+ describe("generateForgejoOpPipeline: issue finding mode is refused by name (#2315)", () => {
153
+ // `comment` (above) was checked against a real Forgejo instance and cleared
154
+ // (#2291, #2304). `issue` stayed un-refused-but-unverified after that —
155
+ // this is the same settling, and it came out the other way: a real
156
+ // instance showed the write half of `postOrUpdateGithubIssue` cannot
157
+ // authenticate (`gh`'s GH_TOKEN never reaches a self-hosted Forgejo), so
158
+ // the refusal this mode used to carry (pre-#2304, for a different reason)
159
+ // is reinstated rather than left lifted. See the module doc above for the
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", () => {
169
+ const specs: ScheduledOpSpec[] = [{ name: "prod-watch", schedule: "0 6 * * *", findingMode: "issue" }];
170
+ expect(() => generateGithubOpPipeline(specs)).not.toThrow();
171
+ });
172
+
173
+ test("a pull_request-triggered Op is refused the same way — issue mode needs no trigger to fail on", () => {
174
+ const specs: ScheduledOpSpec[] = [
175
+ { name: "tf-plan", trigger: { kind: "pull_request", branches: ["main"] }, findingMode: "issue" },
176
+ ];
177
+ expect(() => generateForgejoOpPipeline(specs)).toThrow(/findingMode "issue"/);
178
+ });
179
+ });
180
+
152
181
  /**
153
182
  * #2242 crosses the dialect asymmetrically: Forgejo runs `uses:` steps, so a
154
183
  * spec's `setup` list is emitted; it ignores `permissions:`, so an additive
@@ -225,16 +254,34 @@ describe("generateForgejoOpPipeline: the gated apply on push (#2243)", () => {
225
254
  * always carries, unconditionally) defaults to `sh`, and `sh` rejects `set
226
255
  * -o pipefail`. This generator reuses github's `buildGithubOpPipelineDocs`
227
256
  * 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:`.
257
+ * it, so the fix has to survive both: the exit-code capture is plain POSIX
258
+ * `sh` (chant #2321 — no `shell: bash` to drop or rename in the first
259
+ * place, unlike before #2321, when an unconditional `shell: bash` broke
260
+ * any consumer image without bash — see the sibling test below).
230
261
  */
231
- test("the pipefail step keeps shell: bash across the Forgejo dialect — its job runs in a container, whose default shell is sh", () => {
262
+ test("the exit-code capture crosses the Forgejo dialect with no shell override — its job runs in a container, whose default shell is sh", () => {
232
263
  const doc = parseFile(generateForgejoOpPipeline([pushSpec]).files[0].yaml);
233
264
  const job = doc.jobs!["app-apply"];
234
265
  expect(job.container).toBe("node:22-slim");
235
266
  const step = job.steps.find((s) => s.id === "chant-run");
236
- expect(step?.run).toContain("set -o pipefail");
237
- expect(step?.shell).toBe("bash");
267
+ expect(step?.shell).toBeUndefined();
268
+ expect(step?.run).not.toContain("pipefail");
269
+ expect(step?.run).toContain('[ "$code" -eq 0 ] || exit "$code"');
270
+ });
271
+
272
+ /**
273
+ * chant #2321 — same regression as the github generator's, since this one
274
+ * reuses `buildGithubOpPipelineDocs` to build the job before crossing the
275
+ * dialect: a consumer's non-bash `options.image` (alpine, distroless, …)
276
+ * must not get an unconditional `shell: bash` it cannot run.
277
+ */
278
+ test("a consumer-set non-bash image's gated job can still start on Forgejo (#2321)", () => {
279
+ const doc = parseFile(generateForgejoOpPipeline([pushSpec], { image: "alpine:3.20" }).files[0].yaml);
280
+ const job = doc.jobs!["app-apply"];
281
+ expect(job.container).toBe("alpine:3.20");
282
+ const step = job.steps.find((s) => s.id === "chant-run");
283
+ expect(step?.shell).toBeUndefined();
284
+ expect(step?.run).not.toContain("pipefail");
238
285
  });
239
286
  });
240
287
 
@@ -277,7 +324,7 @@ describe("generateForgejoOpPipeline: a dropped deployment environment (#2257)",
277
324
 
278
325
  test("a spec with no environment emits the bytes it emitted before the option existed", () => {
279
326
  const yaml = generateForgejoOpPipeline([
280
- { name: "actions-audit", schedule: "0 6 * * *", findingMode: "issue" },
327
+ { name: "actions-audit", schedule: "0 6 * * *", findingMode: "pull-request" },
281
328
  ]).files[0].yaml;
282
329
  expect(yaml).toBe(AUDIT_YAML_BEFORE_2257);
283
330
  });
@@ -28,7 +28,7 @@
28
28
  * no mapping in ../actions.ts passes through verbatim and resolves only if
29
29
  * the runner can fetch it.
30
30
  *
31
- * `comment` (#2231) crosses over too, since chant #2291: it posts onto the
31
+ * `comment` (#2231) crosses over, since chant #2291: it posts onto the
32
32
  * triggering pull request by shelling to `gh` against `${GITHUB_API_URL}`,
33
33
  * and a Forgejo Actions job already sets that (and `github.token`) the same
34
34
  * way a GitHub Actions job does. This generator used to refuse the mode by
@@ -38,10 +38,42 @@
38
38
  * false — the failure was `gh api` resolving a *relative* path against
39
39
  * `/api/v3`, which Forgejo does not serve, not an unreachable forge. See
40
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.
41
+ * for the fix.
42
+ *
43
+ * `issue` (`gh issue create`/`postOrUpdateGithubIssue`) does not cross over,
44
+ * and chant #2315 is why: it remained un-refused-but-unverified after #2304
45
+ * lifted `comment`'s refusal, and settling that gap the same way — against a
46
+ * real instance rather than a mock — found a real failure, so the refusal
47
+ * below is reinstated rather than lifted. Tested against the same
48
+ * 12.0.4+gitea-1.22.0 image (`codeberg.org/forgejo/forgejo:12`) with a repo,
49
+ * an issue, and a pull request created on it: the *read* half of
50
+ * `postOrUpdateGithubIssue` checks out fine on Forgejo — plain paginated
51
+ * `GET .../issues?state=open` (no `/search/issues`, confirmed absent from a
52
+ * live Forgejo's own OpenAPI spec) returns pull requests interleaved with
53
+ * issues exactly as GitHub's endpoint does, and the `.pull_request == null`
54
+ * filter and the marker `startswith` match both behave identically to
55
+ * GitHub. The *write* half does not: `postOrUpdateGithubIssue`'s POST and
56
+ * PATCH calls carry only `GH_TOKEN` (in fact, on the `issue` path, not even
57
+ * that — `reconcilePr` hands 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. Only `GH_ENTERPRISE_TOKEN`
65
+ * paired with a matching `GH_HOST` authenticated the same call. Since neither
66
+ * `postOrUpdateGithubIssue` nor its caller sets either, every write this mode
67
+ * makes on Forgejo fails — not a slow search, a 401 on every run. The same
68
+ * shape (`GH_TOKEN` only, no `GH_HOST`/`GH_ENTERPRISE_TOKEN`) is what
69
+ * `postOrUpdateComment`'s writes carry too, which means #2304's "verified
70
+ * against a real Forgejo instance" could not have gone through the generated
71
+ * workflow's own credential path — either that session had a `gh auth login`
72
+ * already stored for the test instance, which a real Actions job's fresh
73
+ * checkout never has, or a different `gh` build was in play. That is
74
+ * `comment` mode's problem to re-verify, not `issue` mode's, and out of
75
+ * scope for chant #2315; flagged here because finding it is what closes that
76
+ * issue's question of whether to lift or reinstate this refusal.
45
77
  *
46
78
  * A spec's `environment` (#2257) is dropped on the same terms as
47
79
  * `permissions:`, and for a stronger reason: Forgejo Actions has no
@@ -147,6 +179,23 @@ export function generateForgejoOpPipeline(
147
179
  // in #2291 once a real Forgejo instance showed the forge itself was never
148
180
  // the obstacle — see the module doc above.
149
181
 
182
+ // `findingMode: "issue"` is refused by name here (#2315): unlike `comment`,
183
+ // a real Forgejo instance did not clear it — see the module doc above for
184
+ // the reproduction.
185
+ for (const spec of ops) {
186
+ if (spec.findingMode === "issue") {
187
+ throw new Error(
188
+ `Scheduled Op "${spec.name}" has findingMode "issue", which opens or edits a GitHub-shaped issue by ` +
189
+ `shelling to \`gh\`. Checked against a real Forgejo 12.0.4+gitea-1.22.0 instance (chant #2315): the ` +
190
+ `search this mode does works there, but the POST/PATCH it writes with does not — \`gh\`'s own ` +
191
+ `GH_TOKEN/GITHUB_TOKEN only authenticate a request to github.com or a ghe.com subdomain, never a ` +
192
+ `self-hosted Forgejo, and neither this mode nor its caller sets GH_HOST or GH_ENTERPRISE_TOKEN, so ` +
193
+ `every write fails with Forgejo's "token is required" (HTTP 401). Use findingMode "comment" on a ` +
194
+ `pull_request trigger here, or generate this Op for github.`,
195
+ );
196
+ }
197
+ }
198
+
150
199
  // `emitGatedOutputs: false` (#2294): forgejo never carries a gate-notice job
151
200
  // (`gatedNoticeDoc` never crosses the dialect, below), so the job outputs
152
201
  // and the `node -e` step that populate them would have no reader.