@intentius/chant-lexicon-forgejo 0.115.0 → 0.117.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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Op waves → Forgejo Actions workflow (#3679).
3
+ *
4
+ * The github workflow ({@link buildGithubOpWavesDoc}) under the Forgejo
5
+ * dialect: action refs rewritten, `permissions:` dropped (the runner reads
6
+ * none), and each wave's `environment:` dropped with a header line saying so,
7
+ * since Forgejo Actions has no environments. The wave's chant gate is what
8
+ * holds it there.
9
+ *
10
+ * With `resume` on the spec (#3683), `<name>-resume.yml` runs `chant run
11
+ * resume --op <name>` on that schedule. Forgejo has no API to re-run a run,
12
+ * so it dispatches the waves workflow again on its branch, with the
13
+ * `CHANT_FORGE_TOKEN` secret (a token with the `write:repository` scope).
14
+ */
15
+ import type { OpWavesSpec } from "@intentius/chant/op/op-waves";
16
+ import type { OpWavesPipelineOptions, OpWavesPipelineResult } from "@intentius/chant/lexicon";
17
+ import { type ForgejoDialectOptions } from "../dialect.js";
18
+ /** Render an Op waves spec as one Forgejo Actions workflow, `<name>.yml`. */
19
+ export declare function generateForgejoOpWavesPipeline(spec: OpWavesSpec, options: OpWavesPipelineOptions, dialectOptions?: ForgejoDialectOptions): OpWavesPipelineResult;
20
+ //# sourceMappingURL=generate-op-waves-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAMjF,6EAA6E;AAC7E,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,sBAAsB,EAC/B,cAAc,GAAE,qBAA0B,GACzC,qBAAqB,CA6CvB"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "a0c7472b522e4b439095a1bf9d0cdef9239a8a46c000e93b99e53c8f9ec61e33",
4
+ "manifest.json": "bd0b5cbea165a77cb8a066acbf0d382f8dd45d8d767a4d88ae874e60bac02d0d",
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": "45673c46187e15f40d82dedd6f92a7c89a077de57d8ae200fe686c41679ea468"
12
+ "composite": "73b7f0b11a0b2392b370713f429927c565a53e71742ae5a10f2d3be661e9fc4c"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.115.0",
3
+ "version": "0.117.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Forgejo",
6
6
  "intrinsics": [],
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,0BAA0B,CAAC;AAmBhG,eAAO,MAAM,aAAa,EAAE,aAiM3B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,0BAA0B,CAAC;AAoBhG,eAAO,MAAM,aAAa,EAAE,aAmM3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-forgejo",
3
- "version": "0.115.0",
3
+ "version": "0.117.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.115.0",
58
- "@intentius/chant-lexicon-github": "^0.115.0"
57
+ "@intentius/chant": "^0.117.0",
58
+ "@intentius/chant-lexicon-github": "^0.117.0"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { OpWavesSpec } from "@intentius/chant/op/op-waves";
3
+ import { generateForgejoOpWavesPipeline } from "./generate-op-waves-pipeline";
4
+
5
+ const spec: OpWavesSpec = {
6
+ name: "migrations",
7
+ op: "migrate",
8
+ plan: ["./bin/plan", "{target}", "{plan}"],
9
+ waves: [
10
+ { name: "dev", runs: [{ target: "dev" }], gate: "never" },
11
+ { name: "staging", runs: [{ target: "staging" }] },
12
+ { name: "prod", runs: [{ target: "prod" }], environment: { name: "production" } },
13
+ ],
14
+ };
15
+
16
+ describe("generateForgejoOpWavesPipeline", () => {
17
+ test("the waves under the Forgejo dialect: chained by needs, no permissions, the environment named as dropped", () => {
18
+ const { files, jobs } = generateForgejoOpWavesPipeline(spec, { specFile: "waves.json" });
19
+ expect(jobs.map((j) => [j.jobName, j.needs])).toEqual([
20
+ ["wave-1-dev", []],
21
+ ["wave-2-staging", ["wave-1-dev"]],
22
+ ["wave-3-prod", ["wave-2-staging"]],
23
+ ]);
24
+ const yaml = files[0]!.yaml;
25
+ expect(files[0]!.name).toBe("migrations.yml");
26
+ expect(yaml).toContain("# chant dropped the environment of each of these waves: prod -> production.");
27
+ expect(yaml).not.toMatch(/^\s+environment:/m);
28
+ expect(yaml).not.toMatch(/^permissions:/m);
29
+ expect(yaml).toMatch(/wave-3-prod:\n\s+runs-on: docker\n\s+needs:\n\s+- wave-2-staging/);
30
+ expect(yaml).toContain("run: chant run wave --spec waves.json --wave 3\n");
31
+ expect(yaml).toContain("fetch-depth: 0");
32
+ });
33
+
34
+ test("resume renders a scheduled workflow that dispatches with the CHANT_FORGE_TOKEN secret (#3683)", () => {
35
+ const { files } = generateForgejoOpWavesPipeline({ ...spec, resume: { schedule: "*/10 * * * *" } }, { specFile: "waves.json" });
36
+ expect(files.map((f) => f.name)).toEqual(["migrations.yml", "migrations-resume.yml"]);
37
+ const yaml = files[1]!.yaml;
38
+ expect(yaml).toContain("run: chant run resume --op migrations");
39
+ expect(yaml).toContain("CHANT_FORGE_TOKEN: '${{ secrets.CHANT_FORGE_TOKEN }}'");
40
+ expect(yaml).not.toContain("github.token");
41
+ expect(yaml).not.toMatch(/^permissions:/m);
42
+ });
43
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Op waves → Forgejo Actions workflow (#3679).
3
+ *
4
+ * The github workflow ({@link buildGithubOpWavesDoc}) under the Forgejo
5
+ * dialect: action refs rewritten, `permissions:` dropped (the runner reads
6
+ * none), and each wave's `environment:` dropped with a header line saying so,
7
+ * since Forgejo Actions has no environments. The wave's chant gate is what
8
+ * holds it there.
9
+ *
10
+ * With `resume` on the spec (#3683), `<name>-resume.yml` runs `chant run
11
+ * resume --op <name>` on that schedule. Forgejo has no API to re-run a run,
12
+ * so it dispatches the waves workflow again on its branch, with the
13
+ * `CHANT_FORGE_TOKEN` secret (a token with the `write:repository` scope).
14
+ */
15
+
16
+ import { buildGithubOpWavesDoc } from "@intentius/chant-lexicon-github/components/generate-op-waves-pipeline";
17
+ import { emitOpPipelineYAML, type GithubOpPipelineDoc } from "@intentius/chant-lexicon-github/components/generate-op-pipeline";
18
+ import type { OpWavesSpec } from "@intentius/chant/op/op-waves";
19
+ import type { OpWavesPipelineOptions, OpWavesPipelineResult } from "@intentius/chant/lexicon";
20
+ import { transformWorkflowObject, type ForgejoDialectOptions } from "../dialect";
21
+
22
+ function forgejoize(value: Record<string, unknown>, dialect: ForgejoDialectOptions): Record<string, unknown> {
23
+ return transformWorkflowObject(value, dialect).value as Record<string, unknown>;
24
+ }
25
+
26
+ /** Render an Op waves spec as one Forgejo Actions workflow, `<name>.yml`. */
27
+ export function generateForgejoOpWavesPipeline(
28
+ spec: OpWavesSpec,
29
+ options: OpWavesPipelineOptions,
30
+ dialectOptions: ForgejoDialectOptions = {},
31
+ ): OpWavesPipelineResult {
32
+ const { doc, jobs, resumeDoc } = buildGithubOpWavesDoc(spec, options);
33
+ const jobsDoc = Object.fromEntries(
34
+ Object.entries(doc.jobsDoc).map(([name, job]) => {
35
+ const { environment: _dropped, ...rest } = job as Record<string, unknown>;
36
+ return [name, rest];
37
+ }),
38
+ );
39
+ const dropped = spec.waves.filter((w) => w.environment).map((w) => `${w.name} -> ${w.environment!.name}`);
40
+ const forgejoDoc: GithubOpPipelineDoc = {
41
+ ...(dropped.length > 0
42
+ ? {
43
+ header: [
44
+ `# chant dropped the environment of each of these waves: ${dropped.join(", ")}.`,
45
+ "# Forgejo Actions has no environments, so nothing on the forge holds these jobs.",
46
+ "# Each wave's own chant gate does: it records a pending fact on chant/lifecycle",
47
+ "# and the wave exits 3 until `chant approve` approves its set digest.",
48
+ ],
49
+ }
50
+ : {}),
51
+ name: doc.name,
52
+ on: forgejoize(doc.on, dialectOptions),
53
+ ...(doc.env ? { env: forgejoize(doc.env, dialectOptions) } : {}),
54
+ concurrency: forgejoize(doc.concurrency, dialectOptions),
55
+ permissions: {},
56
+ jobsDoc: forgejoize(jobsDoc, dialectOptions),
57
+ };
58
+ const files = [{ name: `${spec.name}.yml`, yaml: emitOpPipelineYAML(forgejoDoc) }];
59
+ if (resumeDoc) {
60
+ const resumeJobs = forgejoize(resumeDoc.jobsDoc, dialectOptions) as Record<string, { steps: Array<Record<string, unknown>> }>;
61
+ for (const job of Object.values(resumeJobs)) {
62
+ for (const step of job.steps) if (step.env) step.env = { CHANT_FORGE_TOKEN: "${{ secrets.CHANT_FORGE_TOKEN }}" };
63
+ }
64
+ files.push({
65
+ name: `${spec.name}-resume.yml`,
66
+ yaml: emitOpPipelineYAML({
67
+ name: resumeDoc.name!,
68
+ on: forgejoize(resumeDoc.on, dialectOptions),
69
+ concurrency: forgejoize(resumeDoc.concurrency, dialectOptions),
70
+ permissions: {},
71
+ jobsDoc: resumeJobs,
72
+ }),
73
+ });
74
+ }
75
+ return { files, jobs };
76
+ }
package/src/plugin.ts CHANGED
@@ -19,6 +19,7 @@ import { forgejoSerializer } from "./serializer";
19
19
  import { forgejoContextTools } from "./mcp/context-tools";
20
20
  import { generateForgejoPipeline } from "./components/generate-pipeline";
21
21
  import { generateForgejoOpPipeline } from "./components/generate-op-pipeline";
22
+ import { generateForgejoOpWavesPipeline } from "./components/generate-op-waves-pipeline";
22
23
  import { forgejoLintRules } from "./lint/rules/delegate-to-github";
23
24
  import { forgejoCompletions } from "./lsp/completions";
24
25
  import { forgejoHover } from "./lsp/hover";
@@ -40,6 +41,8 @@ export const forgejoPlugin: LexiconPlugin = {
40
41
 
41
42
  // Generate mode, Op counterpart (#927): scheduled Op → Forgejo Actions workflow.
42
43
  generateOpPipeline: (ops, options) => generateForgejoOpPipeline(ops, options),
44
+ // Ordered waves of Op runs, each behind one gate (#3679).
45
+ generateOpWavesPipeline: (spec, options) => generateForgejoOpWavesPipeline(spec, options),
43
46
 
44
47
  initTemplates(template?: string): InitTemplateSet {
45
48
  if (template === "docker-build") {