@intentius/chant-lexicon-forgejo 0.124.70 → 0.124.72

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.
@@ -17,6 +17,13 @@ export declare function forgejoize(value: Record<string, unknown>, dialect: Forg
17
17
  * dialect transforms as it is.
18
18
  */
19
19
  export declare function withoutEnvironment(jobsDoc: Record<string, unknown>): Record<string, unknown>;
20
+ /**
21
+ * Forgejo 15 and later serves a job an OIDC token only when the job sets
22
+ * `enable-openid-connect: true` (#4753); it does not read `id-token: write`.
23
+ * Each job that asks for the token, by its own `permissions` or, with none of
24
+ * its own, by the workflow's, gets the key.
25
+ */
26
+ export declare function withOpenIdConnect(jobsDoc: Record<string, unknown>, workflowPermissions: unknown): Record<string, unknown>;
20
27
  /** The header of an Op's workflow whose environment gate did not survive the crossing. */
21
28
  export declare function droppedEnvironmentHeader(environmentName: string): string[];
22
29
  /** The header of an Op waves workflow, naming each wave whose environment was dropped as `wave -> environment`. */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iEAAiE,CAAC;AAC3G,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEjF,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5F;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1E;AAED,mHAAmH;AACnH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAOlF;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;IAAK,iBAAiB;CAAqC,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAcnI"}
1
+ {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iEAAiE,CAAC;AAC3G,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEjF,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzH;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1E;AAED,mHAAmH;AACnH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAOlF;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;IAAK,iBAAiB;CAAqC,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAenI"}
@@ -26,6 +26,22 @@ export function withoutEnvironment(jobsDoc) {
26
26
  return [jobName, rest];
27
27
  }));
28
28
  }
29
+ /**
30
+ * Forgejo 15 and later serves a job an OIDC token only when the job sets
31
+ * `enable-openid-connect: true` (#4753); it does not read `id-token: write`.
32
+ * Each job that asks for the token, by its own `permissions` or, with none of
33
+ * its own, by the workflow's, gets the key.
34
+ */
35
+ export function withOpenIdConnect(jobsDoc, workflowPermissions) {
36
+ const wants = (p) => typeof p === "object" && p !== null && p["id-token"] === "write";
37
+ const workflow = wants(workflowPermissions);
38
+ return Object.fromEntries(Object.entries(jobsDoc).map(([jobName, job]) => {
39
+ if (typeof job !== "object" || job === null)
40
+ return [jobName, job];
41
+ const own = job.permissions;
42
+ return wants(own) || (own === undefined && workflow) ? [jobName, { ...job, "enable-openid-connect": true }] : [jobName, job];
43
+ }));
44
+ }
29
45
  /** The header of an Op's workflow whose environment gate did not survive the crossing. */
30
46
  export function droppedEnvironmentHeader(environmentName) {
31
47
  return [
@@ -65,9 +81,10 @@ export function forgejoWorkflowDoc(doc, dialect, header) {
65
81
  ...(doc.env ? { env: forgejoize(doc.env, dialect) } : {}),
66
82
  ...(doc.concurrency ? { concurrency: forgejoize(doc.concurrency, dialect) } : {}),
67
83
  permissions: {},
68
- jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
84
+ jobsDoc: withOpenIdConnect(forgejoize(withoutEnvironment(doc.jobsDoc), dialect), doc.permissions),
69
85
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
70
86
  ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
71
87
  ...(doc.agentDoc ? { agentDoc: forgejoize(doc.agentDoc, dialect) } : {}),
88
+ ...(doc.commitDoc ? { commitDoc: forgejoize(doc.commitDoc, dialect) } : {}),
72
89
  };
73
90
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "41aee4ba1fe4330d30fab53fc1d593863b4cf4f4c6f0ac9960f6b20de7f8ef82",
4
+ "manifest.json": "a12f035cc5e61cfe98b0c5eb91628d0e6383bb868ce59b605925f2b6089e2c97",
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": "eba0a5d9ab481245353bcdff350575f6a19421e1c38c3c0b5bc72bc52424a3f6"
12
+ "composite": "f05bc732b6f3251c2a60aa97343513b8f23baf293b3edfa594d365b0009540df"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.124.70",
3
+ "version": "0.124.72",
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.70",
3
+ "version": "0.124.72",
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.70",
58
- "@intentius/chant-lexicon-github": "^0.124.70"
57
+ "@intentius/chant": "^0.124.72",
58
+ "@intentius/chant-lexicon-github": "^0.124.72"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
@@ -107,6 +107,7 @@ jobs:
107
107
  name: migrations-wave-2-prod-us-outcome
108
108
  path: .chant/op-waves/migrations/outcomes/outcome-wave-2-group-us.json
109
109
  if-no-files-found: ignore
110
+ enable-openid-connect: true
110
111
  wave-2-prod-eu:
111
112
  runs-on: eu-apply
112
113
  needs:
@@ -32,6 +32,24 @@ export function withoutEnvironment(jobsDoc: Record<string, unknown>): Record<str
32
32
  );
33
33
  }
34
34
 
35
+ /**
36
+ * Forgejo 15 and later serves a job an OIDC token only when the job sets
37
+ * `enable-openid-connect: true` (#4753); it does not read `id-token: write`.
38
+ * Each job that asks for the token, by its own `permissions` or, with none of
39
+ * its own, by the workflow's, gets the key.
40
+ */
41
+ export function withOpenIdConnect(jobsDoc: Record<string, unknown>, workflowPermissions: unknown): Record<string, unknown> {
42
+ const wants = (p: unknown): boolean => typeof p === "object" && p !== null && (p as Record<string, unknown>)["id-token"] === "write";
43
+ const workflow = wants(workflowPermissions);
44
+ return Object.fromEntries(
45
+ Object.entries(jobsDoc).map(([jobName, job]) => {
46
+ if (typeof job !== "object" || job === null) return [jobName, job];
47
+ const own = (job as Record<string, unknown>).permissions;
48
+ return wants(own) || (own === undefined && workflow) ? [jobName, { ...(job as Record<string, unknown>), "enable-openid-connect": true }] : [jobName, job];
49
+ }),
50
+ );
51
+ }
52
+
35
53
  /** The header of an Op's workflow whose environment gate did not survive the crossing. */
36
54
  export function droppedEnvironmentHeader(environmentName: string): string[] {
37
55
  return [
@@ -74,9 +92,10 @@ export function forgejoWorkflowDoc(doc: GithubOpPipelineDoc, dialect: ForgejoDia
74
92
  ...(doc.env ? { env: forgejoize(doc.env, dialect) } : {}),
75
93
  ...(doc.concurrency ? { concurrency: forgejoize(doc.concurrency, dialect) } : {}),
76
94
  permissions: {},
77
- jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
95
+ jobsDoc: withOpenIdConnect(forgejoize(withoutEnvironment(doc.jobsDoc), dialect), doc.permissions),
78
96
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
79
97
  ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
80
98
  ...(doc.agentDoc ? { agentDoc: forgejoize(doc.agentDoc, dialect) } : {}),
99
+ ...(doc.commitDoc ? { commitDoc: forgejoize(doc.commitDoc, dialect) } : {}),
81
100
  };
82
101
  }
@@ -0,0 +1,18 @@
1
+ /** #4749 on Forgejo: GitHub's commit jobs under the Forgejo dialect, with the job's own token. */
2
+ import { describe, expect, test } from "vitest";
3
+ import { parseYAML } from "@intentius/chant/yaml";
4
+ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
5
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
6
+
7
+ type Job = { steps: Array<{ name?: string; env?: Record<string, string>; run?: string }> };
8
+
9
+ describe("commit jobs with jobToken on Forgejo (#4749)", () => {
10
+ test("push with the job's token through the Forgejo client, then run after", () => {
11
+ const spec: ScheduledOpSpec = { name: "chant-fmt", commit: { on: "comment", comment: "/chant fmt", command: ["tofu", "fmt"], allowed: ["infra/"], jobToken: true, after: ["echo pushed"] } };
12
+ const doc = parseYAML(generateForgejoOpPipeline([spec]).files[0]!.yaml) as { jobs: Record<string, Job> };
13
+ const steps = doc.jobs["chant-fmt-push"]!.steps;
14
+ const land = steps.find((s) => s.name === "Push the change to the pull request")!;
15
+ expect(land.env).toMatchObject({ CHANT_FORGEJO_TOKEN: "${{ github.token }}" });
16
+ expect(steps.at(-1)!.run).toContain("echo pushed");
17
+ });
18
+ });
@@ -0,0 +1,22 @@
1
+ /** #4622: a scheduled Op's job keeps named artifacts after its run, with the upload action Forgejo runs. */
2
+ import { describe, expect, test } from "vitest";
3
+ import { parseYAML } from "@intentius/chant/yaml";
4
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
5
+ import { ACTION_PINS } from "@intentius/chant/action-pins";
6
+ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
7
+
8
+ type Step = { if?: string; uses?: string; run?: string; with?: Record<string, unknown> };
9
+
10
+ describe("scheduled Op artifacts on Forgejo (#4622)", () => {
11
+ test("each artifact is an upload-artifact v3 step after the run, kept whatever the run ended with", () => {
12
+ const spec: ScheduledOpSpec = { name: "drift", schedule: "0 6 * * *", artifacts: [{ name: "drift-report", paths: ["reports/"], retentionDays: 7 }] };
13
+ const doc = parseYAML(generateForgejoOpPipeline([spec]).files[0]!.yaml) as { jobs: Record<string, { steps: Step[] }> };
14
+ const s = doc.jobs.drift!.steps;
15
+ const run = s.findIndex((x) => x.run?.includes("chant run drift"));
16
+ const up = s.findIndex((x) => x.uses?.startsWith("actions/upload-artifact@") || x.uses?.includes("/upload-artifact@"));
17
+ expect(up).toBeGreaterThan(run);
18
+ expect(s[up]!.uses).toContain(ACTION_PINS.uploadArtifact3.sha);
19
+ expect(s[up]!.if).toBe("always()");
20
+ expect(s[up]!.with).toEqual({ name: "drift-report", path: "reports/", "if-no-files-found": "ignore", "retention-days": 7 });
21
+ });
22
+ });
@@ -0,0 +1,28 @@
1
+ /** The commit jobs on Forgejo (#4722): GitHub's jobs under the Forgejo dialect. */
2
+ import { describe, expect, test } from "vitest";
3
+ import { parseYAML } from "@intentius/chant/yaml";
4
+ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
5
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
6
+
7
+ type Job = { if?: string; permissions?: unknown; steps: Array<{ env?: Record<string, string>; run?: string }> };
8
+
9
+ describe("commit jobs on Forgejo (#4722)", () => {
10
+ test("ask the forge who may write, drop permissions, and push with the token through the Forgejo client", () => {
11
+ const spec: ScheduledOpSpec = { name: "chant-fmt", commit: { on: "comment", comment: "/chant fmt", command: ["tofu", "fmt"], allowed: ["infra/"], tokenEnv: "CHANT_PUSH_TOKEN" } };
12
+ const doc = parseYAML(generateForgejoOpPipeline([spec]).files[0]!.yaml) as { jobs: Record<string, Job> };
13
+ const commit = doc.jobs["chant-fmt"]!;
14
+ expect(commit.if).toContain("github.event.is_pull");
15
+ expect(commit.if).not.toContain("author_association");
16
+ expect(commit.permissions).toBeUndefined();
17
+ const land = doc.jobs["chant-fmt-push"]!.steps.at(-1)!;
18
+ expect(land.env).toMatchObject({ CHANT_FORGEJO_TOKEN: "${{ secrets.CHANT_PUSH_TOKEN }}" });
19
+ expect(land.run).toContain("chant commit land --forge forgejo");
20
+ });
21
+
22
+ test("a check commit job follows the check", () => {
23
+ const spec: ScheduledOpSpec = { name: "fmt-check", trigger: { kind: "pull_request" }, commit: { on: "check", command: ["tofu", "fmt"], allowed: ["infra/"], tokenEnv: "CHANT_PUSH_TOKEN" } };
24
+ const doc = parseYAML(generateForgejoOpPipeline([spec]).files[0]!.yaml) as { jobs: Record<string, Job & { needs?: string }> };
25
+ expect(doc.jobs["fmt-check-commit"]!.needs).toBe("fmt-check");
26
+ expect(doc.jobs["fmt-check-commit-push"]).toBeDefined();
27
+ });
28
+ });
@@ -0,0 +1,25 @@
1
+ /** #4753: a Forgejo job that asks for an OIDC token sets enable-openid-connect. */
2
+ import { describe, expect, test } from "vitest";
3
+ import { parseYAML } from "@intentius/chant/yaml";
4
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
5
+ import { withOpenIdConnect } from "./ci-jobs";
6
+
7
+ type Job = Record<string, unknown>;
8
+ const jobs = (yaml: string) => (parseYAML(yaml) as { jobs: Record<string, Job> }).jobs;
9
+
10
+ describe("enable-openid-connect on Forgejo (#4753)", () => {
11
+ test("a scheduled Op whose permissions ask for id-token: write gets it on its job; one that does not, does not", () => {
12
+ const withToken = jobs(generateForgejoOpPipeline([{ name: "drift", schedule: "0 6 * * *", permissions: { "id-token": "write" } }]).files[0]!.yaml);
13
+ expect(withToken.drift!["enable-openid-connect"]).toBe(true);
14
+ const without = jobs(generateForgejoOpPipeline([{ name: "drift", schedule: "0 6 * * *" }]).files[0]!.yaml);
15
+ expect(without.drift!["enable-openid-connect"]).toBeUndefined();
16
+ });
17
+
18
+ test("a job's own permissions decide over the workflow's", () => {
19
+ const out = withOpenIdConnect({ a: { steps: [] }, b: { permissions: { "id-token": "write" } }, c: { permissions: { contents: "read" } } }, { "id-token": "write" });
20
+ expect(out.a).toMatchObject({ "enable-openid-connect": true });
21
+ expect(out.b).toMatchObject({ "enable-openid-connect": true });
22
+ expect(out.c).not.toHaveProperty("enable-openid-connect");
23
+ expect(withOpenIdConnect({ a: {} }, { contents: "read" }).a).toEqual({});
24
+ });
25
+ });