@intentius/chant-lexicon-forgejo 0.22.0 → 0.24.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.
Files changed (46) hide show
  1. package/README.md +13 -2
  2. package/dist/codegen/docs-cli.d.ts +3 -0
  3. package/dist/codegen/docs-cli.d.ts.map +1 -0
  4. package/dist/codegen/docs.d.ts +7 -0
  5. package/dist/codegen/docs.d.ts.map +1 -0
  6. package/dist/codegen/generate-cli.d.ts +3 -0
  7. package/dist/codegen/generate-cli.d.ts.map +1 -0
  8. package/dist/codegen/generate.d.ts +17 -0
  9. package/dist/codegen/generate.d.ts.map +1 -0
  10. package/dist/codegen/package-cli.d.ts +3 -0
  11. package/dist/codegen/package-cli.d.ts.map +1 -0
  12. package/dist/codegen/package.d.ts +15 -0
  13. package/dist/codegen/package.d.ts.map +1 -0
  14. package/dist/integrity.json +13 -0
  15. package/dist/lint/rules/delegate-to-github.d.ts +29 -0
  16. package/dist/lint/rules/delegate-to-github.d.ts.map +1 -0
  17. package/dist/lint/rules/index.d.ts +6 -0
  18. package/dist/lint/rules/index.d.ts.map +1 -0
  19. package/dist/lsp/completions.d.ts +12 -0
  20. package/dist/lsp/completions.d.ts.map +1 -0
  21. package/dist/lsp/hover.d.ts +10 -0
  22. package/dist/lsp/hover.d.ts.map +1 -0
  23. package/dist/manifest.json +8 -0
  24. package/dist/meta.json +1 -0
  25. package/dist/plugin.d.ts.map +1 -1
  26. package/dist/rules/delegate-to-github.ts +34 -0
  27. package/dist/rules/wfj010.ts +38 -0
  28. package/dist/rules/wfj011.ts +41 -0
  29. package/dist/skills/chant-forgejo.md +68 -0
  30. package/dist/types/index.d.ts +4 -0
  31. package/package.json +8 -5
  32. package/src/codegen/docs-cli.ts +7 -0
  33. package/src/codegen/docs.ts +116 -0
  34. package/src/codegen/generate-cli.ts +8 -0
  35. package/src/codegen/generate.ts +38 -0
  36. package/src/codegen/package-cli.ts +14 -0
  37. package/src/codegen/package.ts +60 -0
  38. package/src/lint/rules/delegate-to-github.test.ts +42 -0
  39. package/src/lint/rules/delegate-to-github.ts +34 -0
  40. package/src/lint/rules/index.ts +6 -0
  41. package/src/lsp/completions.test.ts +36 -0
  42. package/src/lsp/completions.ts +16 -0
  43. package/src/lsp/hover.test.ts +33 -0
  44. package/src/lsp/hover.ts +14 -0
  45. package/src/plugin.test.ts +111 -0
  46. package/src/plugin.ts +39 -2
package/README.md CHANGED
@@ -112,5 +112,16 @@ export default {
112
112
  reused github-lexicon entities (which are tagged `lexicon: "github"`). Its
113
113
  rule prefix is `WFJ` to keep Forgejo diagnostics namespaced apart from
114
114
  github's `GHA`.
115
- - No codegen: this lexicon has no spec of its own. Run `chant generate` in the
116
- github lexicon if you need to refresh entities.
115
+ - No entity codegen: this lexicon has no spec of its own, so `chant generate`
116
+ in this package is a no-op — run it in the github lexicon if you need to
117
+ refresh entities. `npm run generate`/`npm run bundle` still exist and
118
+ produce a (deliberately empty) `dist/manifest.json` and `dist/meta.json` so
119
+ forgejo packages like every other lexicon; see `src/codegen/generate.ts`.
120
+ - LSP completions/hover are delegated, not duplicated: `.completionProvider()`
121
+ and `.hoverProvider()` forward straight to `githubPlugin`'s
122
+ (`src/lsp/completions.ts`, `src/lsp/hover.ts`). Lint rules are wrapped, not
123
+ re-exported verbatim: `forgejoPlugin.lintRules()` returns every github rule
124
+ with its `id` prefixed `WFJ-` (e.g. `WFJ-GHA001`) and its check logic
125
+ untouched (`src/lint/rules/delegate-to-github.ts`) — a plain re-export would
126
+ collide by id the moment something loads the github and forgejo plugins
127
+ together (`chant audit` does exactly that).
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=docs-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-cli.d.ts","sourceRoot":"","sources":["../../src/codegen/docs-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generate documentation site for the Forgejo lexicon.
3
+ */
4
+ export declare function generateDocs(options?: {
5
+ verbose?: boolean;
6
+ }): Promise<void>;
7
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/codegen/docs.ts"],"names":[],"mappings":"AAoFA;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=generate-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-cli.d.ts","sourceRoot":"","sources":["../../src/codegen/generate-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Forgejo lexicon "generation" step.
3
+ *
4
+ * Forgejo has no spec of its own — every entity, property type, and enum a
5
+ * forgejo project uses comes from `@intentius/chant-lexicon-github` (see
6
+ * src/index.ts). There is nothing to fetch or parse here; this module exists
7
+ * only so `packagePipeline` (./package.ts) has a `GenerateResult` to build
8
+ * dist/manifest.json from. Refresh github's entities by running `chant
9
+ * generate` in the github lexicon, not here.
10
+ */
11
+ import type { GenerateResult } from "@intentius/chant/codegen/generate";
12
+ export type { GenerateResult };
13
+ export declare function generate(opts?: {
14
+ verbose?: boolean;
15
+ force?: boolean;
16
+ }): Promise<GenerateResult>;
17
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/codegen/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAExE,YAAY,EAAE,cAAc,EAAE,CAAC;AAW/B,wBAAsB,QAAQ,CAAC,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAarG"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=package-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-cli.d.ts","sourceRoot":"","sources":["../../src/codegen/package-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Forgejo lexicon packaging — delegates to core packagePipeline.
3
+ *
4
+ * Bundles what forgejo actually ships (its post-synth checks, its skill,
5
+ * an empty resource catalog) into dist/manifest.json + dist/meta.json so
6
+ * the lexicon has a real, versioned package manifest like every other
7
+ * lexicon — see ./generate.ts for why the catalog itself is empty.
8
+ */
9
+ import { type PackageOptions, type PackageResult } from "@intentius/chant/codegen/package";
10
+ export type { PackageOptions, PackageResult };
11
+ /**
12
+ * Package the Forgejo lexicon into a distributable BundleSpec.
13
+ */
14
+ export declare function packageLexicon(opts?: PackageOptions): Promise<PackageResult>;
15
+ //# sourceMappingURL=package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/codegen/package.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AAM9C;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6BtF"}
@@ -0,0 +1,13 @@
1
+ {
2
+ "algorithm": "sha256",
3
+ "artifacts": {
4
+ "manifest.json": "b22590c274ff84a849f92976703969d1248381d7cc95092bb3ea31c9b8d53850",
5
+ "meta.json": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
6
+ "types/index.d.ts": "66204549b2a864ab5489e3f02c1878e73ab4b3c9d938cfb480b66f71543a43de",
7
+ "rules/delegate-to-github.ts": "1060cda40f4b73d6cca3ba3fa13b80b4886147d4034c88584c2d80c5510dbe5e",
8
+ "rules/wfj010.ts": "f32834f8b8d0a8dd2a8e19f456df7e44aa63084cce3022ccb13f58f4a9f4f75c",
9
+ "rules/wfj011.ts": "fcb8bf6685d744af20ad804ce929488257995c74b9a3353b2eacae3768d1c83e",
10
+ "skills/chant-forgejo.md": "a1a560429db736c187e0b34cf8dd9efc6ad771afefcbf6f802d160d3d5274257"
11
+ },
12
+ "composite": "46062c09df9a0e84e650e48f408622388922646904dbf46110d115b90ec13ee4"
13
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Forgejo lint rules — wrapped from github, not duplicated.
3
+ *
4
+ * Forgejo has no lint rules of its own: entities, composites, and every
5
+ * expression helper a forgejo project imports are the exact same github
6
+ * classes (see src/index.ts's `export * from "@intentius/chant-lexicon-github"`).
7
+ * A forgejo workflow is TypeScript indistinguishable in shape from an
8
+ * equivalent github workflow, so github's lint rules (GHA001-GHA058 — typed
9
+ * composites, secret detection, supply-chain checks, etc.) apply to it
10
+ * unmodified. Re-implementing or forking any of them here would drift from
11
+ * github's the first time either side changed.
12
+ *
13
+ * Each rule is wrapped rather than re-exported verbatim under github's own
14
+ * id: `chant lint`/`chant audit` can load the github and forgejo plugins in
15
+ * the same process (e.g. `chant audit` scans a repo without knowing its CI
16
+ * provider up front — see packages/core/src/audit/core.ts's
17
+ * `defaultChecksProvider`, which already loads forgejo+github together for
18
+ * post-synth checks), and `loadPlugins`' cross-lexicon conflict check
19
+ * (packages/core/src/cli/conflict-check.ts) hard-fails on two plugins
20
+ * claiming the same rule id. Prefixing with `WFJ-` — forgejo's existing
21
+ * post-synth namespace — keeps every id globally unique while leaving the
22
+ * check (and fix, where one exists) exactly as github wrote it; only the
23
+ * `id` field changes.
24
+ */
25
+ import type { LintRule } from "@intentius/chant/lint/rule";
26
+ /** Wrap a github lint rule under forgejo's `WFJ-` namespace, unmodified otherwise. */
27
+ export declare function wrapGithubRule(rule: LintRule): LintRule;
28
+ export declare const forgejoLintRules: LintRule[];
29
+ //# sourceMappingURL=delegate-to-github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate-to-github.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/delegate-to-github.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAG3D,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAEvD;AAED,eAAO,MAAM,gBAAgB,EAAE,QAAQ,EAA2D,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * All lint rules provided by the Forgejo lexicon — delegated to github, see
3
+ * ./delegate-to-github.
4
+ */
5
+ export { forgejoLintRules } from "./delegate-to-github.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Forgejo LSP completions — delegated to github, not duplicated.
3
+ *
4
+ * Forgejo reuses github's entire authoring surface (entities, composites) —
5
+ * there is no forgejo-specific resource catalog to index. This forwards
6
+ * straight to `githubPlugin`'s own completion provider, which already reads
7
+ * github's generated entity catalog; forking a second copy of it here would
8
+ * only drift the moment github's catalog regenerates.
9
+ */
10
+ import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
11
+ export declare function forgejoCompletions(ctx: CompletionContext): CompletionItem[];
12
+ //# sourceMappingURL=completions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAGpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAE3E"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Forgejo LSP hover — delegated to github, not duplicated.
3
+ *
4
+ * Same reasoning as ./completions.ts: forgejo authors against github's
5
+ * exact classes, so hover information for them is github's hover
6
+ * information, unmodified.
7
+ */
8
+ import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
9
+ export declare function forgejoHover(ctx: HoverContext): HoverInfo | undefined;
10
+ //# sourceMappingURL=hover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAG1E,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAErE"}
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "forgejo",
3
+ "version": "0.24.0",
4
+ "chantVersion": ">=0.1.0",
5
+ "namespace": "Forgejo",
6
+ "intrinsics": [],
7
+ "pseudoParameters": {}
8
+ }
package/dist/meta.json ADDED
@@ -0,0 +1 @@
1
+ {}
@@ -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;AAahG,eAAO,MAAM,aAAa,EAAE,aA4J3B,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;AAiBhG,eAAO,MAAM,aAAa,EAAE,aA6L3B,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Forgejo lint rules — wrapped from github, not duplicated.
3
+ *
4
+ * Forgejo has no lint rules of its own: entities, composites, and every
5
+ * expression helper a forgejo project imports are the exact same github
6
+ * classes (see src/index.ts's `export * from "@intentius/chant-lexicon-github"`).
7
+ * A forgejo workflow is TypeScript indistinguishable in shape from an
8
+ * equivalent github workflow, so github's lint rules (GHA001-GHA058 — typed
9
+ * composites, secret detection, supply-chain checks, etc.) apply to it
10
+ * unmodified. Re-implementing or forking any of them here would drift from
11
+ * github's the first time either side changed.
12
+ *
13
+ * Each rule is wrapped rather than re-exported verbatim under github's own
14
+ * id: `chant lint`/`chant audit` can load the github and forgejo plugins in
15
+ * the same process (e.g. `chant audit` scans a repo without knowing its CI
16
+ * provider up front — see packages/core/src/audit/core.ts's
17
+ * `defaultChecksProvider`, which already loads forgejo+github together for
18
+ * post-synth checks), and `loadPlugins`' cross-lexicon conflict check
19
+ * (packages/core/src/cli/conflict-check.ts) hard-fails on two plugins
20
+ * claiming the same rule id. Prefixing with `WFJ-` — forgejo's existing
21
+ * post-synth namespace — keeps every id globally unique while leaving the
22
+ * check (and fix, where one exists) exactly as github wrote it; only the
23
+ * `id` field changes.
24
+ */
25
+
26
+ import type { LintRule } from "@intentius/chant/lint/rule";
27
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
28
+
29
+ /** Wrap a github lint rule under forgejo's `WFJ-` namespace, unmodified otherwise. */
30
+ export function wrapGithubRule(rule: LintRule): LintRule {
31
+ return { ...rule, id: `WFJ-${rule.id}` };
32
+ }
33
+
34
+ export const forgejoLintRules: LintRule[] = (githubPlugin.lintRules?.() ?? []).map(wrapGithubRule);
@@ -0,0 +1,38 @@
1
+ /**
2
+ * WFJ010: Unresolved action reference.
3
+ *
4
+ * After the forgejo dialect runs, every `uses:` should be a Forgejo-resolvable
5
+ * form (a full URL, a local `./` action, or `docker://`). A bare `owner/repo@ref`
6
+ * that survives into the output won't resolve — Forgejo has no GitHub
7
+ * Marketplace. Flags it so it's caught in `chant lint` / `forgejo:checks`, not
8
+ * only as a build-time warning.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
13
+ import { extractActionRefs } from "@intentius/chant-lexicon-github/lint/post-synth/yaml-helpers";
14
+ import { resolveActionRef } from "../../actions";
15
+
16
+ export const wfj010: PostSynthCheck = {
17
+ id: "WFJ010",
18
+ description: "Unresolved action reference (won't resolve on Forgejo)",
19
+
20
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
21
+ const diagnostics: PostSynthDiagnostic[] = [];
22
+ for (const [, output] of ctx.outputs) {
23
+ const yaml = getPrimaryOutput(output);
24
+ for (const ref of extractActionRefs(yaml)) {
25
+ if (resolveActionRef(ref.ref).warning) {
26
+ diagnostics.push({
27
+ checkId: "WFJ010",
28
+ severity: "warning",
29
+ message: `Job "${ref.job}" references '${ref.ref}', which has no Forgejo-resolvable form. Use a full repository URL or map it under forgejo.actionsRoot.`,
30
+ entity: ref.job,
31
+ lexicon: "forgejo",
32
+ });
33
+ }
34
+ }
35
+ }
36
+ return diagnostics;
37
+ },
38
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * WFJ011: GitHub-hosted runner label.
3
+ *
4
+ * `runs-on` labels like `macos-latest` / `windows-latest` name GitHub-hosted
5
+ * runners that don't exist on Forgejo, and `ubuntu-*` labels that the dialect
6
+ * didn't map (e.g. when mapping is overridden away) won't be advertised by a
7
+ * default Forgejo runner. Flags any such label that survives into the output.
8
+ */
9
+
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
+ import { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
12
+ import { extractRunsOnByJob } from "@intentius/chant-lexicon-github/lint/post-synth/yaml-helpers";
13
+
14
+ /** Labels that name a GitHub-hosted runner image (no fixed Forgejo equivalent). */
15
+ const GITHUB_HOSTED = /^(ubuntu|macos|windows)-/;
16
+
17
+ export const wfj011: PostSynthCheck = {
18
+ id: "WFJ011",
19
+ description: "GitHub-hosted runner label with no Forgejo equivalent",
20
+
21
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
22
+ const diagnostics: PostSynthDiagnostic[] = [];
23
+ for (const [, output] of ctx.outputs) {
24
+ const yaml = getPrimaryOutput(output);
25
+ for (const [job, labels] of extractRunsOnByJob(yaml)) {
26
+ for (const label of labels) {
27
+ if (GITHUB_HOSTED.test(label)) {
28
+ diagnostics.push({
29
+ checkId: "WFJ011",
30
+ severity: "warning",
31
+ message: `Job "${job}" runs on '${label}', a GitHub-hosted runner label with no Forgejo equivalent. Map it via forgejo.runnerLabels or target a label your Forgejo runners advertise.`,
32
+ entity: job,
33
+ lexicon: "forgejo",
34
+ });
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return diagnostics;
40
+ },
41
+ };
@@ -0,0 +1,68 @@
1
+ # chant-forgejo
2
+
3
+ Forgejo / Codeberg / Gitea Actions with chant.
4
+
5
+ Forgejo runs **GitHub-Actions-compatible** workflows, so the forgejo lexicon is
6
+ a thin dialect of the github lexicon. You author exactly as you would for GitHub
7
+ Actions — same `Workflow`, `Job`, `Step`, and composites — but import from
8
+ `@intentius/chant-lexicon-forgejo`. The dialect applies on build.
9
+
10
+ ## Authoring
11
+
12
+ ```ts
13
+ import { Workflow, Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-forgejo";
14
+
15
+ export const workflow = new Workflow({
16
+ name: "CI",
17
+ on: { push: { branches: ["main"] } },
18
+ });
19
+
20
+ export const build = new Job({
21
+ "runs-on": "ubuntu-latest",
22
+ steps: [
23
+ Checkout({}).step,
24
+ SetupNode({ nodeVersion: "22", cache: "npm" }).step,
25
+ new Step({ name: "Test", run: "npm test" }),
26
+ ],
27
+ });
28
+ ```
29
+
30
+ ## Build
31
+
32
+ Forgejo reads workflows from `.forgejo/workflows/` (or `.gitea/workflows/` for
33
+ Gitea):
34
+
35
+ ```sh
36
+ chant build src -o .forgejo/workflows/ci.yml
37
+ ```
38
+
39
+ ## What the dialect does on build
40
+
41
+ - **Drops keys the Forgejo runner ignores** — `permissions` and
42
+ `continue-on-error` are removed (each emits a build warning).
43
+ - **Maps runner labels** — `ubuntu-latest` → `docker` by default; override via
44
+ `forgejo.runnerLabels` in `chant.config.ts`. Unmapped labels warn.
45
+ - **Resolves `uses:` refs** — common `actions/*` rewrite under
46
+ `forgejo.actionsRoot` (default `https://code.forgejo.org`); `docker/*` pin to
47
+ their GitHub URL; unmapped refs warn.
48
+
49
+ ## Migrating from GitHub Actions
50
+
51
+ ```sh
52
+ chant migrate .github/workflows/ci.yml --to forgejo -o .forgejo/workflows/ci.yml --validate
53
+ ```
54
+
55
+ `--validate` prints a **Security posture** report: what survives the move and
56
+ what Forgejo silently drops (`permissions`/`continue-on-error` → lost,
57
+ unresolved `uses:` / unmapped runner labels → needs-review). The same view is
58
+ the `forgejo:compare` MCP tool.
59
+
60
+ ## Read-only context tools (MCP)
61
+
62
+ - `forgejo:workflow` — triggers and jobs as written
63
+ - `forgejo:references` — external actions/images and whether each is SHA-pinned
64
+ - `forgejo:affected` — jobs that re-run downstream of a given job
65
+ - `forgejo:checks` — Forgejo-specific findings (WFJ010/011)
66
+ - `forgejo:source` / `forgejo:owns` — where a job came from / whether it's declared here
67
+ - `forgejo:workflow-yaml` — the generated YAML
68
+ - `forgejo:compare` — the github→forgejo migration safety view
@@ -0,0 +1,4 @@
1
+ // forgejo has no resource types of its own. Every entity a forgejo
2
+ // project imports is declared in @intentius/chant-lexicon-github and
3
+ // re-exported from this package's main entry point.
4
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-forgejo",
3
- "version": "0.22.0",
3
+ "version": "0.24.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",
@@ -53,11 +53,14 @@
53
53
  "typescript": "^5.9.3"
54
54
  },
55
55
  "peerDependencies": {
56
- "@intentius/chant": "^0.22.0",
57
- "@intentius/chant-lexicon-github": "^0.22.0"
56
+ "@intentius/chant": "^0.24.0",
57
+ "@intentius/chant-lexicon-github": "^0.24.0"
58
58
  },
59
59
  "scripts": {
60
- "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete",
61
- "prepack": "npm run build"
60
+ "generate": "tsx src/codegen/generate-cli.ts",
61
+ "bundle": "tsx src/codegen/package-cli.ts",
62
+ "docs": "tsx src/codegen/docs-cli.ts",
63
+ "prepack": "npm run generate && npm run bundle && npm run build",
64
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete"
62
65
  }
63
66
  }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for `npm run docs` in lexicon-forgejo.
4
+ */
5
+ import { generateDocs } from "./docs";
6
+
7
+ await generateDocs({ verbose: true });
@@ -0,0 +1,116 @@
1
+ import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
2
+
3
+ const DIALECT_CONTENT = `Forgejo (the forge behind Codeberg, self-hosted Forgejo, and Gitea) runs
4
+ GitHub-Actions-compatible workflows. You author exactly as you would for
5
+ GitHub Actions — same \`Workflow\`, \`Job\`, \`Step\`, and composites, imported
6
+ from \`@intentius/chant-lexicon-forgejo\` instead of the github package:
7
+
8
+ \`\`\`ts
9
+ import { Workflow, Job, Step, Checkout, SetupNode } from "@intentius/chant-lexicon-forgejo";
10
+
11
+ export const workflow = new Workflow({
12
+ name: "CI",
13
+ on: { push: { branches: ["main"] } },
14
+ });
15
+
16
+ export const build = new Job({
17
+ "runs-on": "ubuntu-latest",
18
+ steps: [
19
+ Checkout({}).step,
20
+ SetupNode({ nodeVersion: "22", cache: "npm" }).step,
21
+ new Step({ name: "Test", run: "npm test" }),
22
+ ],
23
+ });
24
+ \`\`\`
25
+
26
+ On build, the dialect:
27
+
28
+ - **Drops keys Forgejo ignores** — \`permissions\` and \`continue-on-error\` are
29
+ silently ignored by the Forgejo runner, so they are removed from the output
30
+ and reported as build warnings (emitting them is misleading).
31
+ - **Maps runner labels** — GitHub-hosted labels like \`ubuntu-latest\` have no
32
+ fixed meaning on Forgejo. They are mapped to a default Forgejo label
33
+ (\`docker\`), overridable per project. Unmapped labels pass through with a
34
+ warning.
35
+ - **Resolves \`uses:\` action refs** — Forgejo has no GitHub Marketplace, so a
36
+ bare \`uses: actions/checkout@v4\` is rewritten to a resolvable form. Common
37
+ \`actions/*\` are mapped under an actions root (\`https://code.forgejo.org\` by
38
+ default, overridable via \`forgejo.actionsRoot\`); \`docker/*\` are pinned to
39
+ their full GitHub URL. Local (\`./...\`), \`docker://\`, and full-URL refs pass
40
+ through untouched. Anything else passes through **and is reported** as a
41
+ warning so it's never silently unresolvable.
42
+
43
+ Everything else is emitted by the github serializer, which already produces
44
+ the exact YAML shape Forgejo executes.
45
+
46
+ ## Lint rules and editor support
47
+
48
+ Forgejo has no lint rules or LSP completions/hover of its own — a forgejo
49
+ workflow is TypeScript indistinguishable in shape from an equivalent github
50
+ one, so \`chant lint\` and editor tooling run github's checks and catalog
51
+ against forgejo source unmodified (\`completionProvider()\` and
52
+ \`hoverProvider()\` forward straight to \`githubPlugin\`'s). Lint rules are
53
+ wrapped rather than re-exported verbatim — each GHA0xx rule appears under a
54
+ \`WFJ-\` prefix (e.g. \`WFJ-GHA001\`) with its check logic untouched, so a
55
+ project (or \`chant audit\`) can load the github and forgejo plugins together
56
+ without an id collision. See the
57
+ [github lexicon's Lint Rules page](/chant/lexicons/github/lint-rules/) for
58
+ the full GHA0xx catalog.
59
+
60
+ ## Migrating from GitHub Actions
61
+
62
+ Because github → forgejo YAML is near-identical, the migration is thin — it
63
+ applies the same dialect as a build. Its real value is the **compare**: what
64
+ the move costs.
65
+
66
+ \`\`\`sh
67
+ chant migrate .github/workflows/ci.yml --to forgejo -o .forgejo/workflows/ci.yml --validate
68
+ \`\`\`
69
+
70
+ \`--validate\` prints a **security posture** report classifying each property's
71
+ fate across the edge:
72
+
73
+ | Fate | Meaning |
74
+ |---|---|
75
+ | \`translated\` | carried across as-is |
76
+ | \`approximated\` | carried with a close equivalent |
77
+ | \`needs-review\` | confirm/adjust on Forgejo (unresolved \`uses:\`, unmapped runner label) |
78
+ | \`lost\` | the Forgejo runner ignores it (\`permissions\`, \`continue-on-error\`) |
79
+
80
+ The same view is available to agents as the \`forgejo:compare\` MCP tool, which
81
+ takes a workflow file and returns per-property fates plus summary counts —
82
+ read-only.
83
+ `;
84
+
85
+ /**
86
+ * Generate documentation site for the Forgejo lexicon.
87
+ */
88
+ export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
89
+ const config = {
90
+ name: "forgejo",
91
+ displayName: "Forgejo",
92
+ description: "Forgejo / Codeberg / Gitea Actions lexicon documentation — a thin GitHub Actions dialect",
93
+ distDir: "./dist",
94
+ outDir: "./docs",
95
+ basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/forgejo/",
96
+ overview:
97
+ "Forgejo Actions is a thin dialect of the GitHub Actions lexicon (chant #338): every entity, composite, and expression helper you write for a Forgejo workflow is the same class you'd import from the github lexicon — this package supplies only what's genuinely Forgejo-specific: the serializer dialect (dropped GitHub-only keys, `uses:` action resolution, runner-label mapping) and a github → forgejo migration path.",
98
+ outputFormat:
99
+ "The Forgejo lexicon serializes entities into the same YAML shape as GitHub Actions, then applies the Forgejo dialect (see [The Forgejo Dialect](/chant/lexicons/forgejo/dialect/)) before writing to `.forgejo/workflows/*.yml` (or `.gitea/workflows/*.yml` for Gitea).",
100
+ extraPages: [
101
+ {
102
+ slug: "dialect",
103
+ title: "The Forgejo Dialect",
104
+ description: "What changes when a GitHub Actions workflow serializes to Forgejo, and how migration works",
105
+ content: DIALECT_CONTENT,
106
+ },
107
+ ],
108
+ };
109
+
110
+ const result = docsPipeline(config);
111
+ writeDocsSite(config, result);
112
+
113
+ if (options?.verbose) {
114
+ console.error("Documentation generated");
115
+ }
116
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for `npm run generate` in lexicon-forgejo.
4
+ */
5
+ import { generate } from "./generate";
6
+
7
+ const result = await generate({ verbose: true });
8
+ console.error(`forgejo: ${result.resources} resources (reuses github's wholesale)`);
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Forgejo lexicon "generation" step.
3
+ *
4
+ * Forgejo has no spec of its own — every entity, property type, and enum a
5
+ * forgejo project uses comes from `@intentius/chant-lexicon-github` (see
6
+ * src/index.ts). There is nothing to fetch or parse here; this module exists
7
+ * only so `packagePipeline` (./package.ts) has a `GenerateResult` to build
8
+ * dist/manifest.json from. Refresh github's entities by running `chant
9
+ * generate` in the github lexicon, not here.
10
+ */
11
+
12
+ import type { GenerateResult } from "@intentius/chant/codegen/generate";
13
+
14
+ export type { GenerateResult };
15
+
16
+ // No entities of forgejo's own — the catalog is intentionally empty.
17
+ const LEXICON_JSON = "{}";
18
+
19
+ const TYPES_DTS = `// forgejo has no resource types of its own. Every entity a forgejo
20
+ // project imports is declared in @intentius/chant-lexicon-github and
21
+ // re-exported from this package's main entry point.
22
+ export {};
23
+ `;
24
+
25
+ export async function generate(opts?: { verbose?: boolean; force?: boolean }): Promise<GenerateResult> {
26
+ if (opts?.verbose) {
27
+ console.error("forgejo: no spec of its own — reuses the github lexicon's entities wholesale (0 own resources)");
28
+ }
29
+ return {
30
+ lexiconJSON: LEXICON_JSON,
31
+ typesDTS: TYPES_DTS,
32
+ indexTS: "",
33
+ resources: 0,
34
+ properties: 0,
35
+ enums: 0,
36
+ warnings: [],
37
+ };
38
+ }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * CLI entry point for `npm run bundle` in lexicon-forgejo.
4
+ */
5
+ import { packageLexicon } from "./package";
6
+ import { writeBundleSpec } from "@intentius/chant/codegen/package";
7
+ import { join, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+
10
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
11
+ const { spec, stats } = await packageLexicon({ verbose: true });
12
+ writeBundleSpec(spec, join(pkgDir, "dist"));
13
+
14
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Forgejo lexicon packaging — delegates to core packagePipeline.
3
+ *
4
+ * Bundles what forgejo actually ships (its post-synth checks, its skill,
5
+ * an empty resource catalog) into dist/manifest.json + dist/meta.json so
6
+ * the lexicon has a real, versioned package manifest like every other
7
+ * lexicon — see ./generate.ts for why the catalog itself is empty.
8
+ */
9
+
10
+ import { readFileSync } from "fs";
11
+ import { forgejoPlugin } from "../plugin";
12
+ import { join, dirname } from "path";
13
+ import { fileURLToPath } from "url";
14
+ import {
15
+ packagePipeline,
16
+ collectSkills,
17
+ type PackageOptions,
18
+ type PackageResult,
19
+ } from "@intentius/chant/codegen/package";
20
+ import { generate } from "./generate";
21
+
22
+ export type { PackageOptions, PackageResult };
23
+
24
+ // package.ts is at src/codegen/package.ts — 2 dirname calls reach src/
25
+ // then join(pkgDir, "..") is the package root where package.json lives
26
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
27
+
28
+ /**
29
+ * Package the Forgejo lexicon into a distributable BundleSpec.
30
+ */
31
+ export async function packageLexicon(opts: PackageOptions = {}): Promise<PackageResult> {
32
+ const pkgJson = JSON.parse(readFileSync(join(pkgDir, "..", "package.json"), "utf-8"));
33
+
34
+ return packagePipeline(
35
+ {
36
+ generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
37
+
38
+ buildManifest: (_genResult) => {
39
+ return {
40
+ name: "forgejo",
41
+ version: pkgJson.version ?? "0.0.1",
42
+ chantVersion: ">=0.1.0",
43
+ namespace: "Forgejo",
44
+ intrinsics: [],
45
+ pseudoParameters: {},
46
+ };
47
+ },
48
+
49
+ srcDir: pkgDir,
50
+
51
+ collectSkills: () => {
52
+ const skillDefs = forgejoPlugin.skills?.() ?? [];
53
+ return collectSkills(skillDefs);
54
+ },
55
+
56
+ version: pkgJson.version ?? "0.0.1",
57
+ },
58
+ opts,
59
+ );
60
+ }
@@ -0,0 +1,42 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { forgejoLintRules, wrapGithubRule } from "./delegate-to-github";
3
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
4
+
5
+ describe("forgejo lint rules — wrapped from github", () => {
6
+ test("returns a non-empty rule set", () => {
7
+ expect(Array.isArray(forgejoLintRules)).toBe(true);
8
+ expect(forgejoLintRules.length).toBeGreaterThan(0);
9
+ });
10
+
11
+ test("one wrapped rule per github rule, under the WFJ- namespace", () => {
12
+ const githubRules = githubPlugin.lintRules?.() ?? [];
13
+ expect(forgejoLintRules).toHaveLength(githubRules.length);
14
+ expect(forgejoLintRules.map((r) => r.id)).toEqual(githubRules.map((r) => `WFJ-${r.id}`));
15
+ });
16
+
17
+ test("does not collide with any github rule id", () => {
18
+ const githubIds = new Set((githubPlugin.lintRules?.() ?? []).map((r) => r.id));
19
+ for (const rule of forgejoLintRules) {
20
+ expect(githubIds.has(rule.id)).toBe(false);
21
+ }
22
+ });
23
+
24
+ test("includes WFJ-GHA001 (use typed action composites)", () => {
25
+ const ids = forgejoLintRules.map((r) => r.id);
26
+ expect(ids).toContain("WFJ-GHA001");
27
+ });
28
+
29
+ test("includes WFJ-GHA003 (no hardcoded secrets)", () => {
30
+ const ids = forgejoLintRules.map((r) => r.id);
31
+ expect(ids).toContain("WFJ-GHA003");
32
+ });
33
+
34
+ test("wrapGithubRule preserves check() behavior, only renaming id", () => {
35
+ const [githubRule] = githubPlugin.lintRules?.() ?? [];
36
+ const wrapped = wrapGithubRule(githubRule);
37
+ expect(wrapped.check).toBe(githubRule.check);
38
+ expect(wrapped.severity).toBe(githubRule.severity);
39
+ expect(wrapped.category).toBe(githubRule.category);
40
+ expect(wrapped.id).toBe(`WFJ-${githubRule.id}`);
41
+ });
42
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Forgejo lint rules — wrapped from github, not duplicated.
3
+ *
4
+ * Forgejo has no lint rules of its own: entities, composites, and every
5
+ * expression helper a forgejo project imports are the exact same github
6
+ * classes (see src/index.ts's `export * from "@intentius/chant-lexicon-github"`).
7
+ * A forgejo workflow is TypeScript indistinguishable in shape from an
8
+ * equivalent github workflow, so github's lint rules (GHA001-GHA058 — typed
9
+ * composites, secret detection, supply-chain checks, etc.) apply to it
10
+ * unmodified. Re-implementing or forking any of them here would drift from
11
+ * github's the first time either side changed.
12
+ *
13
+ * Each rule is wrapped rather than re-exported verbatim under github's own
14
+ * id: `chant lint`/`chant audit` can load the github and forgejo plugins in
15
+ * the same process (e.g. `chant audit` scans a repo without knowing its CI
16
+ * provider up front — see packages/core/src/audit/core.ts's
17
+ * `defaultChecksProvider`, which already loads forgejo+github together for
18
+ * post-synth checks), and `loadPlugins`' cross-lexicon conflict check
19
+ * (packages/core/src/cli/conflict-check.ts) hard-fails on two plugins
20
+ * claiming the same rule id. Prefixing with `WFJ-` — forgejo's existing
21
+ * post-synth namespace — keeps every id globally unique while leaving the
22
+ * check (and fix, where one exists) exactly as github wrote it; only the
23
+ * `id` field changes.
24
+ */
25
+
26
+ import type { LintRule } from "@intentius/chant/lint/rule";
27
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
28
+
29
+ /** Wrap a github lint rule under forgejo's `WFJ-` namespace, unmodified otherwise. */
30
+ export function wrapGithubRule(rule: LintRule): LintRule {
31
+ return { ...rule, id: `WFJ-${rule.id}` };
32
+ }
33
+
34
+ export const forgejoLintRules: LintRule[] = (githubPlugin.lintRules?.() ?? []).map(wrapGithubRule);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * All lint rules provided by the Forgejo lexicon — delegated to github, see
3
+ * ./delegate-to-github.
4
+ */
5
+
6
+ export { forgejoLintRules } from "./delegate-to-github";
@@ -0,0 +1,36 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { forgejoCompletions } from "./completions";
3
+ import type { CompletionContext } from "@intentius/chant/lsp/types";
4
+
5
+ function makeCtx(overrides: Partial<CompletionContext>): CompletionContext {
6
+ return {
7
+ uri: "file:///test.ts",
8
+ content: "",
9
+ position: { line: 0, character: 0 },
10
+ wordAtCursor: "",
11
+ linePrefix: "",
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ describe("forgejoCompletions", () => {
17
+ test("delegates to github's resource completions after `new `", () => {
18
+ const ctx = makeCtx({
19
+ linePrefix: "const build = new Job",
20
+ wordAtCursor: "Job",
21
+ content: "const build = new Job",
22
+ position: { line: 0, character: 22 },
23
+ });
24
+
25
+ const items = forgejoCompletions(ctx);
26
+ const jobItem = items.find((i) => i.label === "Job");
27
+ expect(jobItem).toBeDefined();
28
+ expect(jobItem?.kind).toBe("resource");
29
+ expect(jobItem?.detail).toContain("GitHub::Actions::Job");
30
+ });
31
+
32
+ test("returns [] outside a completion context", () => {
33
+ const ctx = makeCtx({});
34
+ expect(forgejoCompletions(ctx)).toEqual([]);
35
+ });
36
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Forgejo LSP completions — delegated to github, not duplicated.
3
+ *
4
+ * Forgejo reuses github's entire authoring surface (entities, composites) —
5
+ * there is no forgejo-specific resource catalog to index. This forwards
6
+ * straight to `githubPlugin`'s own completion provider, which already reads
7
+ * github's generated entity catalog; forking a second copy of it here would
8
+ * only drift the moment github's catalog regenerates.
9
+ */
10
+
11
+ import type { CompletionContext, CompletionItem } from "@intentius/chant/lsp/types";
12
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
13
+
14
+ export function forgejoCompletions(ctx: CompletionContext): CompletionItem[] {
15
+ return githubPlugin.completionProvider?.(ctx) ?? [];
16
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { forgejoHover } from "./hover";
3
+ import type { HoverContext } from "@intentius/chant/lsp/types";
4
+
5
+ function makeCtx(overrides: Partial<HoverContext>): HoverContext {
6
+ return {
7
+ uri: "file:///test.ts",
8
+ content: "",
9
+ position: { line: 0, character: 0 },
10
+ word: "",
11
+ lineText: "",
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ describe("forgejoHover", () => {
17
+ test("delegates to github's hover info for Job", () => {
18
+ const ctx = makeCtx({ word: "Job" });
19
+ const info = forgejoHover(ctx);
20
+ expect(info).toBeDefined();
21
+ expect(info!.contents).toContain("GitHub::Actions::Job");
22
+ });
23
+
24
+ test("returns undefined for an unknown word", () => {
25
+ const ctx = makeCtx({ word: "NotARealForgejoOrGithubThing12345" });
26
+ expect(forgejoHover(ctx)).toBeUndefined();
27
+ });
28
+
29
+ test("returns undefined for an empty word", () => {
30
+ const ctx = makeCtx({ word: "" });
31
+ expect(forgejoHover(ctx)).toBeUndefined();
32
+ });
33
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Forgejo LSP hover — delegated to github, not duplicated.
3
+ *
4
+ * Same reasoning as ./completions.ts: forgejo authors against github's
5
+ * exact classes, so hover information for them is github's hover
6
+ * information, unmodified.
7
+ */
8
+
9
+ import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
10
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
11
+
12
+ export function forgejoHover(ctx: HoverContext): HoverInfo | undefined {
13
+ return githubPlugin.hoverProvider?.(ctx);
14
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Forgejo plugin tests.
3
+ */
4
+
5
+ import { describe, expect, it } from "vitest";
6
+ import { forgejoPlugin } from "./plugin";
7
+ import { githubPlugin } from "@intentius/chant-lexicon-github";
8
+ import { isLexiconPlugin } from "@intentius/chant/lexicon";
9
+ import type { CompletionContext, HoverContext } from "@intentius/chant/lsp/types";
10
+
11
+ describe("forgejo plugin", () => {
12
+ it("is a valid LexiconPlugin", () => {
13
+ expect(isLexiconPlugin(forgejoPlugin)).toBe(true);
14
+ });
15
+
16
+ it("has the correct name", () => {
17
+ expect(forgejoPlugin.name).toBe("forgejo");
18
+ });
19
+
20
+ it("has a serializer (name 'github' — serializes reused github entities)", () => {
21
+ expect(forgejoPlugin.serializer).toBeDefined();
22
+ expect(forgejoPlugin.serializer.name).toBe("github");
23
+ });
24
+
25
+ it("postSynthChecks() returns the WFJ checks", () => {
26
+ const checks = forgejoPlugin.postSynthChecks?.();
27
+ expect(Array.isArray(checks)).toBe(true);
28
+ const ids = checks?.map((c) => c.id).sort();
29
+ expect(ids).toEqual(["WFJ010", "WFJ011"]);
30
+ });
31
+
32
+ it("lintRules() wraps github's lint rules under a WFJ- namespace (no id collision)", () => {
33
+ const rules = forgejoPlugin.lintRules?.() ?? [];
34
+ const githubRules = githubPlugin.lintRules?.() ?? [];
35
+ expect(rules.length).toBe(githubRules.length);
36
+ expect(rules.map((r) => r.id)).toEqual(githubRules.map((r) => `WFJ-${r.id}`));
37
+ // Same check() behavior — only the id changes, so forgejo and github can
38
+ // load together (e.g. `chant audit`) without loadPlugins' cross-lexicon
39
+ // rule-id conflict check rejecting the pair.
40
+ expect(rules.map((r) => r.check)).toEqual(githubRules.map((r) => r.check));
41
+ });
42
+
43
+ it("completionProvider() delegates to github's completions", () => {
44
+ const ctx: CompletionContext = {
45
+ uri: "file:///t.ts",
46
+ content: "const j = new Job",
47
+ position: { line: 0, character: 18 },
48
+ wordAtCursor: "Job",
49
+ linePrefix: "const j = new Job",
50
+ };
51
+ const items = forgejoPlugin.completionProvider?.(ctx);
52
+ expect(items?.some((i) => i.label === "Job")).toBe(true);
53
+ });
54
+
55
+ it("hoverProvider() delegates to github's hover info", () => {
56
+ const ctx: HoverContext = {
57
+ uri: "file:///t.ts",
58
+ content: "",
59
+ position: { line: 0, character: 0 },
60
+ word: "Job",
61
+ lineText: "",
62
+ };
63
+ const info = forgejoPlugin.hoverProvider?.(ctx);
64
+ expect(info?.contents).toContain("GitHub::Actions::Job");
65
+ });
66
+
67
+ it("detectTemplate() recognizes github-actions-shaped data", () => {
68
+ expect(forgejoPlugin.detectTemplate?.({ on: {}, jobs: {} })).toBe(true);
69
+ expect(forgejoPlugin.detectTemplate?.({ build: { "runs-on": "ubuntu-latest" } })).toBe(true);
70
+ expect(forgejoPlugin.detectTemplate?.({})).toBe(false);
71
+ expect(forgejoPlugin.detectTemplate?.(null)).toBe(false);
72
+ });
73
+
74
+ it("migrationSource('github') detects a github workflow and rejects unrelated content", () => {
75
+ const src = forgejoPlugin.migrationSource?.("github");
76
+ expect(src).toBeDefined();
77
+ expect(src?.detect("on:\n push: {}\njobs:\n build:\n runs-on: ubuntu-latest\n")).toBe(true);
78
+ expect(src?.detect("just some text")).toBe(false);
79
+ });
80
+
81
+ it("migrationSource() returns undefined for an unknown source", () => {
82
+ expect(forgejoPlugin.migrationSource?.("gitlab")).toBeUndefined();
83
+ });
84
+
85
+ it("mcpTools() includes the diff tool and context tools", () => {
86
+ const tools = forgejoPlugin.mcpTools?.();
87
+ expect(Array.isArray(tools)).toBe(true);
88
+ expect(tools?.some((t) => t.name === "forgejo:diff")).toBe(true);
89
+ expect(tools!.length).toBeGreaterThan(1);
90
+ });
91
+
92
+ it("skills() returns chant-forgejo", () => {
93
+ const skills = forgejoPlugin.skills?.();
94
+ expect(skills?.map((s) => s.name)).toContain("chant-forgejo");
95
+ });
96
+
97
+ describe("initTemplates", () => {
98
+ it("default template returns src with pipeline.ts", () => {
99
+ const result = forgejoPlugin.initTemplates?.();
100
+ expect(result?.src?.["pipeline.ts"]).toBeDefined();
101
+ expect(result?.src?.["pipeline.ts"]).toContain("@intentius/chant-lexicon-forgejo");
102
+ });
103
+
104
+ it("'docker-build' template returns a docker build workflow", () => {
105
+ const result = forgejoPlugin.initTemplates?.("docker-build");
106
+ const src = result?.src?.["pipeline.ts"] as string;
107
+ expect(src).toContain("Docker Build");
108
+ expect(src).toContain("docker build");
109
+ });
110
+ });
111
+ });
package/src/plugin.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { LexiconPlugin, InitTemplateSet, MigrationSource } from "@intentius/chant/lexicon";
12
+ import type { LintRule } from "@intentius/chant/lint/rule";
12
13
  import { postSynthChecks as postSynthCheckList } from "./lint/post-synth";
13
14
  import { forgejoAuditCatalog } from "./lint/audit-catalog";
14
15
  import { createSkillsLoader, createDiffTool } from "@intentius/chant/lexicon-plugin-helpers";
@@ -17,6 +18,9 @@ import { fileURLToPath } from "url";
17
18
  import { forgejoSerializer } from "./serializer";
18
19
  import { forgejoContextTools } from "./mcp/context-tools";
19
20
  import { generateForgejoPipeline } from "./components/generate-pipeline";
21
+ import { forgejoLintRules } from "./lint/rules/delegate-to-github";
22
+ import { forgejoCompletions } from "./lsp/completions";
23
+ import { forgejoHover } from "./lsp/hover";
20
24
 
21
25
  const reuseNote =
22
26
  "forgejo reuses the github lexicon's entities — run `chant generate` in the github lexicon instead.";
@@ -133,6 +137,20 @@ export const build = new Job({
133
137
  return postSynthCheckList;
134
138
  },
135
139
 
140
+ // Lint rules and LSP support are delegated to github, not forked — see
141
+ // ./lint/rules/delegate-to-github.ts and ./lsp/{completions,hover}.ts.
142
+ lintRules(): LintRule[] {
143
+ return forgejoLintRules;
144
+ },
145
+
146
+ completionProvider(ctx: import("@intentius/chant/lsp/types").CompletionContext) {
147
+ return forgejoCompletions(ctx);
148
+ },
149
+
150
+ hoverProvider(ctx: import("@intentius/chant/lsp/types").HoverContext) {
151
+ return forgejoHover(ctx);
152
+ },
153
+
136
154
  mcpTools() {
137
155
  return [
138
156
  createDiffTool(forgejoSerializer, "Compare current build output against previous output for Forgejo Actions", "forgejo"),
@@ -174,7 +192,26 @@ export const build = new Job({
174
192
  async coverage(): Promise<void> {
175
193
  console.error(reuseNote);
176
194
  },
177
- async package(): Promise<void> {
178
- console.error(reuseNote);
195
+
196
+ // Packaging is real (unlike generate/validate/coverage above): forgejo
197
+ // ships its own dist/manifest.json — an empty resource catalog plus its
198
+ // own post-synth checks and skill — even though the catalog it packages
199
+ // is empty. See ./codegen/{generate,package}.ts.
200
+ async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
201
+ const { packageLexicon } = await import("./codegen/package");
202
+ const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
203
+ const { join: pathJoin, dirname: pathDirname } = await import("path");
204
+ const { fileURLToPath: toPath } = await import("url");
205
+
206
+ const { spec, stats } = await packageLexicon(options);
207
+ const pkgDir = pathDirname(pathDirname(toPath(import.meta.url)));
208
+ writeBundleSpec(spec, pathJoin(pkgDir, "dist"));
209
+
210
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
211
+ },
212
+
213
+ async docs(options?: { verbose?: boolean }): Promise<void> {
214
+ const { generateDocs } = await import("./codegen/docs");
215
+ return generateDocs(options);
179
216
  },
180
217
  };