@intentius/chant-lexicon-forgejo 0.124.54 → 0.124.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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,CAanI"}
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"}
@@ -68,5 +68,6 @@ export function forgejoWorkflowDoc(doc, dialect, header) {
68
68
  jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
69
69
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
70
70
  ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
71
+ ...(doc.agentDoc ? { agentDoc: forgejoize(doc.agentDoc, dialect) } : {}),
71
72
  };
72
73
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The control repo's own workflow (#4568) on Forgejo: github's document
3
+ * (`controlGithubDoc`) with the Forgejo dialect applied, the way
4
+ * ./generate-pipeline.ts reuses github's pipeline. Forgejo has no
5
+ * `repository_dispatch`, so a member's dispatch is a workflow dispatch of
6
+ * this file by name.
7
+ */
8
+ import { type ControlPipelineFile, type ControlPipelineOptions } from "@intentius/chant-lexicon-github/components/control-pipeline";
9
+ import type { ForgejoDialectOptions } from "../dialect.js";
10
+ /** The control repo's workflow on Forgejo: `.forgejo/workflows/<product>-dependents.yml`. */
11
+ export declare function generateControlPipeline(options?: ControlPipelineOptions, dialect?: ForgejoDialectOptions): ControlPipelineFile;
12
+ //# sourceMappingURL=control-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/control-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,6DAA6D,CAAC;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD,6FAA6F;AAC7F,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,sBAA2B,EAAE,OAAO,GAAE,qBAA0B,GAAG,mBAAmB,CAQtI"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The control repo's own workflow (#4568) on Forgejo: github's document
3
+ * (`controlGithubDoc`) with the Forgejo dialect applied, the way
4
+ * ./generate-pipeline.ts reuses github's pipeline. Forgejo has no
5
+ * `repository_dispatch`, so a member's dispatch is a workflow dispatch of
6
+ * this file by name.
7
+ */
8
+ import { controlGithubDoc, } from "@intentius/chant-lexicon-github/components/control-pipeline";
9
+ import { emitWorkflowYAML } from "@intentius/chant-lexicon-github/components/ci-jobs";
10
+ import { forgejoize } from "./ci-jobs.js";
11
+ /** The control repo's workflow on Forgejo: `.forgejo/workflows/<product>-dependents.yml`. */
12
+ export function generateControlPipeline(options = {}, dialect = {}) {
13
+ const { doc, path, notes } = controlGithubDoc("forgejo", options);
14
+ const content = emitWorkflowYAML({
15
+ ...doc,
16
+ on: forgejoize(doc.on, dialect),
17
+ jobsDoc: forgejoize(doc.jobsDoc, dialect),
18
+ });
19
+ return { path, content, notes };
20
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAMH,OAAO,KAAK,EACV,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,wBAA6B,EACtC,cAAc,GAAE,qBAA0B,GACzC,gBAAgB,CAmBlB"}
1
+ {"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAMH,OAAO,KAAK,EACV,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,wBAA6B,EACtC,cAAc,GAAE,qBAA0B,GACzC,gBAAgB,CAqBlB"}
@@ -86,6 +86,7 @@
86
86
  * sibling Op's job receives.
87
87
  */
88
88
  import { buildGithubOpPipelineDocs, emitOpPipelineYAML, } from "@intentius/chant-lexicon-github/components/generate-op-pipeline";
89
+ import { assertRunnerLabels } from "@intentius/chant/ci-jobs";
89
90
  import { droppedEnvironmentHeader, forgejoWorkflowDoc } from "./ci-jobs.js";
90
91
  /**
91
92
  * Synthesize one `.forgejo/workflows/*.yml` per scheduled Op. Reuses github's
@@ -100,7 +101,10 @@ export function generateForgejoOpPipeline(ops, options = {}, dialectOptions = {}
100
101
  // `findingMode: "comment"` used to be refused by name here (#2231), and
101
102
  // `findingMode: "issue"` from #2332 to #2351. Neither is now: both post
102
103
  // through the Forgejo client — see the module doc above.
103
- const { files, jobs } = buildGithubOpPipelineDocs(ops, options);
104
+ // #4616: a spec's runner is a label or a list of labels here.
105
+ for (const spec of ops)
106
+ assertRunnerLabels(`Scheduled Op "${spec.name}"`, "runner", spec.runner, "Forgejo");
107
+ const { files, jobs } = buildGithubOpPipelineDocs(ops, options, "forgejo");
104
108
  return {
105
109
  // One file per spec, in spec order, which is what lets the header below
106
110
  // read its Op's own `environment` off the input by position.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "0a6dc57e7d8145e558ff06623c2d0e382f169c0dd47004d1bec2807e7c424c08",
4
+ "manifest.json": "18127222bb3fd237d748da8a38aafe6888852b53cdf62b7cfebb4918a040905e",
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": "3011e602acaca2080397cd441b1153f053cbc914e39da45949674d2055d02a37"
12
+ "composite": "366563b3a7c31fc2bfa18468fe4382a732acf7f8ba302197ff1e1fe61910367d"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.124.54",
3
+ "version": "0.124.55",
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.54",
3
+ "version": "0.124.55",
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.54",
58
- "@intentius/chant-lexicon-github": "^0.124.54"
57
+ "@intentius/chant": "^0.124.55",
58
+ "@intentius/chant-lexicon-github": "^0.124.55"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
@@ -77,5 +77,6 @@ export function forgejoWorkflowDoc(doc: GithubOpPipelineDoc, dialect: ForgejoDia
77
77
  jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
78
78
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
79
79
  ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
80
+ ...(doc.agentDoc ? { agentDoc: forgejoize(doc.agentDoc, dialect) } : {}),
80
81
  };
81
82
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The control repo's own workflow (#4568) on Forgejo: github's document
3
+ * (`controlGithubDoc`) with the Forgejo dialect applied, the way
4
+ * ./generate-pipeline.ts reuses github's pipeline. Forgejo has no
5
+ * `repository_dispatch`, so a member's dispatch is a workflow dispatch of
6
+ * this file by name.
7
+ */
8
+
9
+ import {
10
+ controlGithubDoc,
11
+ type ControlPipelineFile,
12
+ type ControlPipelineOptions,
13
+ } from "@intentius/chant-lexicon-github/components/control-pipeline";
14
+ import { emitWorkflowYAML } from "@intentius/chant-lexicon-github/components/ci-jobs";
15
+ import type { ForgejoDialectOptions } from "../dialect";
16
+ import { forgejoize } from "./ci-jobs";
17
+
18
+ /** The control repo's workflow on Forgejo: `.forgejo/workflows/<product>-dependents.yml`. */
19
+ export function generateControlPipeline(options: ControlPipelineOptions = {}, dialect: ForgejoDialectOptions = {}): ControlPipelineFile {
20
+ const { doc, path, notes } = controlGithubDoc("forgejo", options);
21
+ const content = emitWorkflowYAML({
22
+ ...doc,
23
+ on: forgejoize(doc.on, dialect),
24
+ jobsDoc: forgejoize(doc.jobsDoc, dialect),
25
+ });
26
+ return { path, content, notes };
27
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The agent jobs on Forgejo (#4616): github's jobs in the Forgejo dialect,
3
+ * with the commenter's access asked of the forge, and a runner a label.
4
+ */
5
+
6
+ import { describe, expect, test } from "vitest";
7
+ import { parseYAML } from "@intentius/chant/yaml";
8
+ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
9
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
10
+
11
+ const doc = (spec: ScheduledOpSpec) => parseYAML(generateForgejoOpPipeline([spec]).files[0]!.yaml) as unknown as { jobs: Record<string, { if?: string; "runs-on"?: unknown; steps: Array<{ run?: string; env?: Record<string, string> }> }> };
12
+
13
+ describe("the comment agent on Forgejo", () => {
14
+ const d = doc({ name: "chant-agent", runner: "agents", agent: { on: "comment", comment: "/chant agent" } });
15
+
16
+ test("answers a comment on a pull request with no author association to read", () => {
17
+ const agent = d.jobs["chant-agent"]!;
18
+ expect(agent.if).toContain("github.event.is_pull");
19
+ expect(agent.if).not.toContain("author_association");
20
+ expect(agent["runs-on"]).toBe("agents");
21
+ });
22
+
23
+ test("the push job hands the forge client its token", () => {
24
+ const land = d.jobs["chant-agent-push"]!.steps.at(-1)!;
25
+ expect(land.run).toContain("chant agent land --forge forgejo");
26
+ expect(land.env?.CHANT_FORGEJO_TOKEN).toBeDefined();
27
+ });
28
+
29
+ test("refuses a runner group, which only GitHub has", () => {
30
+ expect(() => doc({ name: "drift-app", trigger: { kind: "cron", schedule: "0 6 * * *" }, runner: { group: "infra" } })).toThrow(/runner group/);
31
+ });
32
+ });
33
+
34
+ // #4619: on Forgejo too, a member's drift Op hands its own output on, and the agent job takes it at the root.
35
+ describe("the drift agent of a workspace member on Forgejo", () => {
36
+ const out = generateForgejoOpPipeline([{ name: "drift-app", trigger: { kind: "cron", schedule: "0 6 * * *" }, agent: { on: "drift" } }], { member: { name: "app", dir: "stacks/app" } });
37
+ const d = parseYAML(out.files[0]!.yaml) as unknown as { jobs: Record<string, { defaults?: { run?: { "working-directory"?: string } }; steps: Array<{ name?: string; run?: string }> }> };
38
+
39
+ test("the drift check runs in the member's directory, and the agent job reads what it handed on", () => {
40
+ expect(d.jobs["drift-app"]!.defaults?.run?.["working-directory"]).toBe("stacks/app");
41
+ const agent = d.jobs["drift-app-agent"]!;
42
+ expect(agent.defaults).toBeUndefined();
43
+ expect(agent.steps.find((s) => s.name === "Take the drift from the drift check")!.run).toContain(" .chant/agent <<");
44
+ expect(agent.steps.some((s) => s.run?.includes("chant agent prepare --forge forgejo --output .chant/agent"))).toBe(true);
45
+ });
46
+ });
@@ -95,6 +95,7 @@ import type {
95
95
  OpPipelineResult,
96
96
  ScheduledOpSpec,
97
97
  } from "@intentius/chant/lexicon";
98
+ import { assertRunnerLabels } from "@intentius/chant/ci-jobs";
98
99
  import type { ForgejoDialectOptions } from "../dialect";
99
100
  import { droppedEnvironmentHeader, forgejoWorkflowDoc } from "./ci-jobs";
100
101
 
@@ -116,7 +117,9 @@ export function generateForgejoOpPipeline(
116
117
  // `findingMode: "issue"` from #2332 to #2351. Neither is now: both post
117
118
  // through the Forgejo client — see the module doc above.
118
119
 
119
- const { files, jobs } = buildGithubOpPipelineDocs(ops, options);
120
+ // #4616: a spec's runner is a label or a list of labels here.
121
+ for (const spec of ops) assertRunnerLabels(`Scheduled Op "${spec.name}"`, "runner", spec.runner, "Forgejo");
122
+ const { files, jobs } = buildGithubOpPipelineDocs(ops, options, "forgejo");
120
123
 
121
124
  return {
122
125
  // One file per spec, in spec order, which is what lets the header below
@@ -0,0 +1,38 @@
1
+ /**
2
+ * An Op waves spec's `control` on Forgejo (#4617): github's control job
3
+ * under the dialect, dispatching the control repo's dependents workflow by
4
+ * name with the outcome as an input. Written for the ci-products session;
5
+ * run at epic-ready.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import type { OpWavesSpec } from "@intentius/chant/op/op-waves";
10
+ import { generateForgejoOpWavesPipeline } from "./generate-op-waves-pipeline";
11
+
12
+ const spec: OpWavesSpec = {
13
+ name: "stacks",
14
+ op: "pulumi-up",
15
+ plan: ["./bin/plan", "{target}", "{plan}"],
16
+ waves: [{ name: "all", runs: [{ target: "dev" }, { target: "prod" }] }],
17
+ control: { repo: "forgejo.example.com/acme/control", op: "deploy", member: "network", ref: "trunk" },
18
+ };
19
+
20
+ const options = { specFile: "waves.json", bin: "npx --no-install plywood", words: { product: "plywood" } };
21
+
22
+ describe("the control dispatch after the last wave on Forgejo (#4617)", () => {
23
+ test("a workflow dispatch of the control repo's dependents file, the outcome among its inputs", () => {
24
+ const yaml = generateForgejoOpWavesPipeline(spec, options).files[0]!.yaml;
25
+ expect(yaml).toMatch(/control-dispatch:[\s\S]*needs:\n\s+- wave-1-all\n\s+if: '!cancelled\(\)'/);
26
+ expect(yaml).toContain("https://forgejo.example.com/api/v1/repos/acme/control/actions/workflows/plywood-dependents.yml/dispatches");
27
+ expect(yaml).toContain(`{"ref":"trunk","inputs":{"member":"'"$member"'","op":"'"$op"'","sha":"'"$sha"'","outcome":"'"$outcome"'"}}`);
28
+ expect(yaml).toContain("Authorization: token %s");
29
+ expect(yaml).toContain("CHANT_CONTROL_TOKEN: '${{ secrets.CHANT_CONTROL_TOKEN }}'");
30
+ expect(yaml).toContain("npx --no-install plywood run wave --spec waves.json --waves-outcome");
31
+ });
32
+
33
+ test("the dialect drops permissions and rewrites the download action", () => {
34
+ const yaml = generateForgejoOpWavesPipeline(spec, options).files[0]!.yaml;
35
+ expect(yaml).not.toContain("permissions:");
36
+ expect(yaml).toContain("name: Download the waves' outcomes");
37
+ });
38
+ });
@@ -119,4 +119,21 @@ describe("an Op wave's jobs by cloud identity (#4332)", () => {
119
119
  expect(job.steps[1]!.uses).toContain("checkout");
120
120
  }
121
121
  });
122
+
123
+ test("words.bin is the command in every job line: the waves, resume and the plans job (#4607)", () => {
124
+ const bin = "npx --no-install plywood";
125
+ const withBin = { ...spec, resume: { schedule: "*/10 * * * *" }, waves: spec.waves.map((w, i) => (i === spec.waves.length - 1 ? { ...w, approval: "pr-review" as const } : w)) };
126
+ const { files, jobs } = generateForgejoOpWavesPipeline(withBin, { specFile: "waves.json", bin });
127
+ expect(jobs.every((j) => j.command.slice(0, 3).join(" ") === bin)).toBe(true);
128
+ const yaml = files.map((f) => f.yaml).join("\n");
129
+ expect(yaml).toContain("run: npx --no-install plywood run wave --spec waves.json --wave 1");
130
+ expect(yaml).toContain("npx --no-install plywood run resume --op migrations");
131
+ expect(yaml).toContain("npx --no-install plywood run wave --spec waves.json --record-plans");
132
+ expect(yaml).not.toMatch(/(^|\s)chant run (wave|resume)/m);
133
+ });
134
+
135
+ test("no bin keeps chant (#4607)", () => {
136
+ const { jobs } = generateForgejoOpWavesPipeline(spec, { specFile: "waves.json" });
137
+ expect(jobs.every((j) => j.command[0] === "chant")).toBe(true);
138
+ });
122
139
  });
@@ -0,0 +1,25 @@
1
+ /** `/<product> lock` and `/<product> unlock` on Forgejo (#4588): the GitHub generator's comment jobs, reading Forgejo's `is_pull`, with no author check. */
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { parseYAML } from "@intentius/chant/yaml";
5
+ import type { DriverComponent } from "@intentius/chant/components/driver";
6
+ import { generateForgejoPipeline } from "./generate-pipeline";
7
+
8
+ const ESTATE: DriverComponent[] = [
9
+ { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
10
+ ];
11
+
12
+ const WORDS = { product: "plywood", commands: { lock: "lock" as const, unlock: "unlock" as const } };
13
+
14
+ type Doc = { jobs: Record<string, { if?: string; steps: Array<{ run?: string }> }> };
15
+
16
+ describe("the Forgejo workflow's lock and unlock comments", () => {
17
+ const doc = parseYAML(generateForgejoPipeline(ESTATE, { env: "prod", prLoop: { locks: "plan", words: WORDS } }).yaml) as Doc;
18
+
19
+ test("each comment job reads is_pull and runs on forgejo", () => {
20
+ expect(doc.jobs["comment-lock"]!.if).toContain("github.event.is_pull");
21
+ expect(doc.jobs["comment-lock"]!.if).not.toContain("author_association");
22
+ expect(doc.jobs["comment-lock"]!.steps.some((s) => s.run === 'chant lock --keep --pr "$PR_NUMBER" --forge forgejo')).toBe(true);
23
+ expect(doc.jobs["comment-unlock"]!.steps.some((s) => s.run === 'chant unlock --pr "$PR_NUMBER"')).toBe(true);
24
+ });
25
+ });
@@ -0,0 +1,31 @@
1
+ /** The preview comment on Forgejo (#4620): the GitHub generator's jobs, reading Forgejo's `is_pull`, with no author check. */
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { parseYAML } from "@intentius/chant/yaml";
5
+ import type { DriverComponent } from "@intentius/chant/components/driver";
6
+ import { generateForgejoPipeline } from "./generate-pipeline";
7
+
8
+ const ESTATE: DriverComponent[] = [
9
+ { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
10
+ ];
11
+
12
+ const WORDS = { product: "plywood", stages: { plan: "preview", apply: "up" }, commands: { preview: "plan" as const } };
13
+
14
+ type Doc = { jobs: Record<string, { if?: string; permissions?: Record<string, string>; steps: Array<{ run?: string }> }> };
15
+
16
+ describe("the Forgejo workflow's preview comment", () => {
17
+ const doc = parseYAML(generateForgejoPipeline(ESTATE, { env: "prod", prLoop: { previewComment: true, words: WORDS } }).yaml) as Doc;
18
+
19
+ test("the preview job reads is_pull, drops the tokens before the change's code runs, and the note job posts on forgejo", () => {
20
+ const preview = doc.jobs["comment-preview"]!;
21
+ expect(preview.if).toContain("github.event.is_pull");
22
+ expect(preview.if).not.toContain("author_association");
23
+ // The Forgejo runner ignores `permissions`, so the dialect drops it (bar id-token), as for every job.
24
+ expect(preview.permissions).toBeUndefined();
25
+ const drop = preview.steps.findIndex((s) => s.run?.includes("-u CHANT_FORGE_TOKEN") ?? false);
26
+ const plan = preview.steps.findIndex((s) => s.run?.includes("pr-plan") ?? false);
27
+ expect(drop).toBeGreaterThan(0);
28
+ expect(plan).toBeGreaterThan(drop);
29
+ expect(doc.jobs["comment-preview-note"]!.steps.at(-1)!.run).toBe('chant components pr-post --pr "$PR_NUMBER" --head "$HEAD_SHA" --env prod --output .chant/pr --forge forgejo --result "$PLAN_RESULT"');
30
+ });
31
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * #4619: with a control repo, the Forgejo pull-request workflow's plan-note
3
+ * job fetches the control repo's chant/lifecycle, read-only, and sets
4
+ * CHANT_CONTROL_DIR before it posts, through the Forgejo dialect. Generated
5
+ * YAML only.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { parseYAML } from "@intentius/chant/yaml";
10
+ import type { DriverComponent } from "@intentius/chant/components/driver";
11
+ import { resolveControlTarget } from "@intentius/chant/control/dispatch";
12
+ import { generateForgejoPipeline } from "./generate-pipeline";
13
+
14
+ const root = (name: string, dependsOn: string[] = []): DriverComponent => ({ name, dependsOn, deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: name }] }] });
15
+ const ESTATE: DriverComponent[] = [root("net"), root("app", ["net"])];
16
+ const control = { target: resolveControlTarget({ repo: "forgejo.example.com/acme/control", op: "deploy", readTokenSecret: "PLYWOOD_CONTROL_READ" }, "network") };
17
+
18
+ describe("the Forgejo plan-note job's read of the control repo (#4619)", () => {
19
+ test("fetches the control repo's chant/lifecycle with the read secret before it posts", () => {
20
+ const { yaml } = generateForgejoPipeline(ESTATE, { env: "prod", prLoop: { applyOnMerge: true, control, words: { product: "plywood" } } });
21
+ const doc = parseYAML(yaml) as { jobs: Record<string, { steps: Array<{ run?: string; env?: Record<string, string> }> }> };
22
+ const steps = doc.jobs["plan-note"]!.steps;
23
+ const read = steps.findIndex((s) => s.run?.includes("refs/heads/chant/lifecycle"));
24
+ expect(read).toBeGreaterThan(-1);
25
+ expect(steps[read + 1]!.run).toContain("components pr-post");
26
+ expect(steps[read]!.env?.CHANT_CONTROL_READ_TOKEN).toBe("${{ secrets.PLYWOOD_CONTROL_READ || github.token }}");
27
+ expect(steps[read]!.run).toContain("'https://forgejo.example.com/acme/control.git'");
28
+ expect(steps[read]!.run).toContain(">>\"$GITHUB_ENV\"");
29
+ });
30
+ });