@intentius/chant-lexicon-forgejo 0.18.19 → 0.18.21

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,37 @@
1
+ /**
2
+ * Generate mode — component → Forgejo Actions workflow YAML (#969, epic #885).
3
+ *
4
+ * The forgejo counterpart to the github generator
5
+ * (`@intentius/chant-lexicon-github/components/generate-pipeline`, #891). Forgejo
6
+ * Actions is a GitHub-Actions dialect, and this lexicon is already a thin
7
+ * github-dialect serializer, so the pipeline SHAPE is identical: one job per
8
+ * component, `needs:` mirroring `dependsOn`, cross-stack outputs threaded as
9
+ * workflow artifacts, thin trigger jobs. Rather than re-derive the component
10
+ * graph, this reuses github's `buildGithubPipelineDoc` to build the exact same
11
+ * `on`/`env`/`jobs` structure, then applies the Forgejo dialect
12
+ * ({@link transformWorkflowObject}) before emitting — the same transform the
13
+ * forgejo serializer applies to authored workflows:
14
+ *
15
+ * - runner labels: `runs-on: ubuntu-latest` → the project's Forgejo label
16
+ * (default `docker`, the label a fresh `act_runner` exposes);
17
+ * - `uses:` refs (`actions/checkout@v4`, `actions/upload-artifact@v4`, …)
18
+ * rewritten to a Forgejo-resolvable form;
19
+ * - keys the Forgejo runner ignores (`permissions`, `continue-on-error`)
20
+ * dropped — the github generator emits none today, but the transform keeps
21
+ * the two dialects in lock-step if that changes.
22
+ *
23
+ * The output path convention is `.forgejo/workflows/` (vs github's
24
+ * `.github/workflows/`), set by the caller's `-o` flag — this module emits the
25
+ * workflow content, not its location, exactly like the github generator.
26
+ */
27
+ import type { DriverComponent } from "@intentius/chant/components/driver";
28
+ import type { ComponentPipelineOptions, ComponentPipelineResult } from "@intentius/chant/lexicon";
29
+ import { type ForgejoDialectOptions } from "../dialect.js";
30
+ /**
31
+ * Synthesize a `.forgejo/workflows/*.yml` pipeline from a set of components.
32
+ * Reuses github's job/`needs:`/artifact structure ({@link buildGithubPipelineDoc}),
33
+ * then applies the Forgejo dialect. Wired into core's generate mode via the
34
+ * forgejo lexicon plugin's `generateComponentPipeline` (../plugin.ts).
35
+ */
36
+ export declare function generateForgejoPipeline(components: DriverComponent[], options?: ComponentPipelineOptions, dialectOptions?: ForgejoDialectOptions): ComponentPipelineResult;
37
+ //# sourceMappingURL=generate-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAOjF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,wBAA6B,EACtC,cAAc,GAAE,qBAA0B,GACzC,uBAAuB,CAYzB"}
@@ -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;AAYhG,eAAO,MAAM,aAAa,EAAE,aAuJ3B,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;AAahG,eAAO,MAAM,aAAa,EAAE,aA4J3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-forgejo",
3
- "version": "0.18.19",
3
+ "version": "0.18.21",
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",
@@ -53,8 +53,8 @@
53
53
  "typescript": "^5.9.3"
54
54
  },
55
55
  "peerDependencies": {
56
- "@intentius/chant": "^0.18.19",
57
- "@intentius/chant-lexicon-github": "^0.18.19"
56
+ "@intentius/chant": "^0.18.21",
57
+ "@intentius/chant-lexicon-github": "^0.18.21"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete",
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tests for generate mode's component → Forgejo Actions workflow YAML
3
+ * synthesis (#969, epic #885). Mirrors the github generator's suite
4
+ * (`lexicons/github/src/components/generate-pipeline.test.ts`) — Forgejo Actions
5
+ * is a GitHub-Actions dialect, so the pipeline SHAPE is identical (one job per
6
+ * component, `needs:` = `dependsOn`, cross-stack outputs as artifacts) and this
7
+ * suite additionally proves the Forgejo dialect is applied: runner labels
8
+ * remapped (`ubuntu-latest` → `docker`) and `uses:` refs rewritten to a
9
+ * Forgejo-resolvable form.
10
+ */
11
+
12
+ import { describe, test, expect } from "vitest";
13
+ import { parseYAML } from "@intentius/chant/yaml";
14
+ import { generateForgejoPipeline } from "./generate-pipeline";
15
+ import { generateGithubPipeline } from "@intentius/chant-lexicon-github/components/generate-pipeline";
16
+ import { type DriverComponent } from "@intentius/chant/components/driver";
17
+ import { searchService } from "@intentius/chant/components/pilots/alb-ecs.pilot";
18
+ import { ordersTable } from "@intentius/chant/components/pilots/dynamodb.pilot";
19
+
20
+ function pilotComponents(): DriverComponent[] {
21
+ return [
22
+ { name: ordersTable.name, dependsOn: ordersTable.dependsOn, deploy: ordersTable.deploy },
23
+ { name: "shared-alb", dependsOn: [], deploy: [] },
24
+ { name: searchService.name, dependsOn: searchService.dependsOn, deploy: searchService.deploy },
25
+ ];
26
+ }
27
+
28
+ interface ParsedStep {
29
+ name?: string;
30
+ uses?: string;
31
+ run?: string;
32
+ with?: Record<string, unknown>;
33
+ }
34
+ interface ParsedJob {
35
+ "runs-on"?: string;
36
+ container?: string;
37
+ needs?: string[];
38
+ steps: ParsedStep[];
39
+ }
40
+ function parsedJobs(yaml: string): Record<string, ParsedJob> {
41
+ return (parseYAML(yaml).jobs ?? {}) as Record<string, ParsedJob>;
42
+ }
43
+ function usesRefs(job: ParsedJob): string[] {
44
+ return job.steps.filter((s) => typeof s.uses === "string").map((s) => s.uses as string);
45
+ }
46
+
47
+ describe("generateForgejoPipeline: structure (github-shaped)", () => {
48
+ test("valid workflow — on: + one job per component with runs-on/steps", () => {
49
+ const result = generateForgejoPipeline(pilotComponents());
50
+ const parsed = parseYAML(result.yaml);
51
+ expect(parsed.on).toBeDefined();
52
+ const jobs = parsedJobs(result.yaml);
53
+ expect(Object.keys(jobs).sort()).toEqual(["orders-table", "search-service", "shared-alb"]);
54
+ for (const job of Object.values(jobs)) {
55
+ expect(job["runs-on"]).toBeDefined();
56
+ expect(Array.isArray(job.steps)).toBe(true);
57
+ }
58
+ });
59
+
60
+ test("needs: mirrors dependsOn — search-service waits on shared-alb, nothing else", () => {
61
+ const jobs = parsedJobs(generateForgejoPipeline(pilotComponents()).yaml);
62
+ expect(jobs["search-service"].needs).toEqual(["shared-alb"]);
63
+ expect(jobs["orders-table"].needs ?? []).toEqual([]);
64
+ expect(jobs["shared-alb"].needs ?? []).toEqual([]);
65
+ });
66
+
67
+ test("cross-stack outputs threaded as artifacts: producer uploads, dependent downloads", () => {
68
+ const jobs = parsedJobs(generateForgejoPipeline(pilotComponents()).yaml);
69
+ const upload = jobs["shared-alb"].steps.find((s) => (s.uses ?? "").includes("upload-artifact"));
70
+ expect(upload?.with).toEqual({ name: "shared-alb-outputs", path: "shared-alb.outputs.json" });
71
+ const download = jobs["search-service"].steps.find((s) => (s.uses ?? "").includes("download-artifact"));
72
+ expect(download?.with).toEqual({ name: "shared-alb-outputs", path: "." });
73
+ });
74
+
75
+ test("stages/jobs parity with the github generator (same graph resolution)", () => {
76
+ const fj = generateForgejoPipeline(pilotComponents());
77
+ const gh = generateGithubPipeline(pilotComponents());
78
+ expect(fj.stages).toEqual(gh.stages);
79
+ expect(fj.jobs).toEqual(gh.jobs);
80
+ });
81
+ });
82
+
83
+ describe("generateForgejoPipeline: dialect applied", () => {
84
+ test("runner label ubuntu-latest is remapped to the Forgejo default (docker)", () => {
85
+ const jobs = parsedJobs(generateForgejoPipeline(pilotComponents()).yaml);
86
+ for (const job of Object.values(jobs)) {
87
+ expect(job["runs-on"]).toBe("docker");
88
+ }
89
+ // The github generator uses the un-remapped label — proving the dialect ran.
90
+ const ghJobs = parsedJobs(generateGithubPipeline(pilotComponents()).yaml);
91
+ expect(ghJobs["shared-alb"]["runs-on"]).toBe("ubuntu-latest");
92
+ });
93
+
94
+ test("uses: refs are rewritten to a Forgejo-resolvable mirror (not bare actions/*)", () => {
95
+ const jobs = parsedJobs(generateForgejoPipeline(pilotComponents()).yaml);
96
+ const all = Object.values(jobs).flatMap(usesRefs);
97
+ expect(all.length).toBeGreaterThan(0);
98
+ for (const ref of all) {
99
+ expect(ref.startsWith("actions/")).toBe(false);
100
+ expect(ref).toContain("code.forgejo.org/actions/");
101
+ }
102
+ // checkout is present and mirrored.
103
+ expect(all.some((r) => r === "https://code.forgejo.org/actions/checkout@v4")).toBe(true);
104
+ });
105
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Generate mode — component → Forgejo Actions workflow YAML (#969, epic #885).
3
+ *
4
+ * The forgejo counterpart to the github generator
5
+ * (`@intentius/chant-lexicon-github/components/generate-pipeline`, #891). Forgejo
6
+ * Actions is a GitHub-Actions dialect, and this lexicon is already a thin
7
+ * github-dialect serializer, so the pipeline SHAPE is identical: one job per
8
+ * component, `needs:` mirroring `dependsOn`, cross-stack outputs threaded as
9
+ * workflow artifacts, thin trigger jobs. Rather than re-derive the component
10
+ * graph, this reuses github's `buildGithubPipelineDoc` to build the exact same
11
+ * `on`/`env`/`jobs` structure, then applies the Forgejo dialect
12
+ * ({@link transformWorkflowObject}) before emitting — the same transform the
13
+ * forgejo serializer applies to authored workflows:
14
+ *
15
+ * - runner labels: `runs-on: ubuntu-latest` → the project's Forgejo label
16
+ * (default `docker`, the label a fresh `act_runner` exposes);
17
+ * - `uses:` refs (`actions/checkout@v4`, `actions/upload-artifact@v4`, …)
18
+ * rewritten to a Forgejo-resolvable form;
19
+ * - keys the Forgejo runner ignores (`permissions`, `continue-on-error`)
20
+ * dropped — the github generator emits none today, but the transform keeps
21
+ * the two dialects in lock-step if that changes.
22
+ *
23
+ * The output path convention is `.forgejo/workflows/` (vs github's
24
+ * `.github/workflows/`), set by the caller's `-o` flag — this module emits the
25
+ * workflow content, not its location, exactly like the github generator.
26
+ */
27
+
28
+ import {
29
+ buildGithubPipelineDoc,
30
+ emitPipelineYAML,
31
+ type GithubPipelineDoc,
32
+ } from "@intentius/chant-lexicon-github/components/generate-pipeline";
33
+ import type { DriverComponent } from "@intentius/chant/components/driver";
34
+ import type {
35
+ ComponentPipelineOptions,
36
+ ComponentPipelineResult,
37
+ } from "@intentius/chant/lexicon";
38
+ import { transformWorkflowObject, type ForgejoDialectOptions } from "../dialect";
39
+
40
+ /** Apply the Forgejo dialect to one section of the pipeline doc. */
41
+ function forgejoize(value: Record<string, unknown>, dialect: ForgejoDialectOptions): Record<string, unknown> {
42
+ return transformWorkflowObject(value, dialect).value as Record<string, unknown>;
43
+ }
44
+
45
+ /**
46
+ * Synthesize a `.forgejo/workflows/*.yml` pipeline from a set of components.
47
+ * Reuses github's job/`needs:`/artifact structure ({@link buildGithubPipelineDoc}),
48
+ * then applies the Forgejo dialect. Wired into core's generate mode via the
49
+ * forgejo lexicon plugin's `generateComponentPipeline` (../plugin.ts).
50
+ */
51
+ export function generateForgejoPipeline(
52
+ components: DriverComponent[],
53
+ options: ComponentPipelineOptions = {},
54
+ dialectOptions: ForgejoDialectOptions = {},
55
+ ): ComponentPipelineResult {
56
+ const doc = buildGithubPipelineDoc(components, options);
57
+
58
+ const forgejoDoc: GithubPipelineDoc = {
59
+ on: forgejoize(doc.on, dialectOptions),
60
+ ...(doc.env ? { env: forgejoize(doc.env, dialectOptions) } : {}),
61
+ jobsDoc: forgejoize(doc.jobsDoc, dialectOptions),
62
+ stages: doc.stages,
63
+ jobs: doc.jobs,
64
+ };
65
+
66
+ return { yaml: emitPipelineYAML(forgejoDoc), stages: doc.stages, jobs: doc.jobs };
67
+ }
package/src/plugin.ts CHANGED
@@ -16,6 +16,7 @@ import { join, dirname } from "path";
16
16
  import { fileURLToPath } from "url";
17
17
  import { forgejoSerializer } from "./serializer";
18
18
  import { forgejoContextTools } from "./mcp/context-tools";
19
+ import { generateForgejoPipeline } from "./components/generate-pipeline";
19
20
 
20
21
  const reuseNote =
21
22
  "forgejo reuses the github lexicon's entities — run `chant generate` in the github lexicon instead.";
@@ -25,6 +26,11 @@ export const forgejoPlugin: LexiconPlugin = {
25
26
  auditCatalog: () => forgejoAuditCatalog,
26
27
  serializer: forgejoSerializer,
27
28
 
29
+ // Generate mode (#969): component graph → Forgejo Actions workflow. Reuses
30
+ // github's pipeline structure and applies the Forgejo dialect (runner labels,
31
+ // `uses:` resolution) — see ./components/generate-pipeline.
32
+ generateComponentPipeline: (components, options) => generateForgejoPipeline(components, options),
33
+
28
34
  initTemplates(template?: string): InitTemplateSet {
29
35
  if (template === "docker-build") {
30
36
  return {