@intentius/chant-lexicon-forgejo 0.23.0 → 0.25.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.
- package/README.md +13 -2
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +7 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +17 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package-cli.d.ts +3 -0
- package/dist/codegen/package-cli.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +15 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/integrity.json +13 -0
- package/dist/lint/rules/delegate-to-github.d.ts +29 -0
- package/dist/lint/rules/delegate-to-github.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +12 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +10 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/delegate-to-github.ts +34 -0
- package/dist/rules/wfj010.ts +38 -0
- package/dist/rules/wfj011.ts +41 -0
- package/dist/skills/chant-forgejo.md +68 -0
- package/dist/types/index.d.ts +4 -0
- package/package.json +8 -5
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +116 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +38 -0
- package/src/codegen/package-cli.ts +14 -0
- package/src/codegen/package.ts +60 -0
- package/src/lint/rules/delegate-to-github.test.ts +42 -0
- package/src/lint/rules/delegate-to-github.ts +34 -0
- package/src/lint/rules/index.ts +6 -0
- package/src/lsp/completions.test.ts +36 -0
- package/src/lsp/completions.ts +16 -0
- package/src/lsp/hover.test.ts +33 -0
- package/src/lsp/hover.ts +14 -0
- package/src/plugin.test.ts +111 -0
- 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
|
|
116
|
-
github lexicon if you need to
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"docs-cli.d.ts","sourceRoot":"","sources":["../../src/codegen/docs-cli.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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": "3d55bd2014e74805aed971d37f12b2f619f0b37a0047c0fb77f8fcce534becaf",
|
|
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": "429b0e4fc00c62677ea22d4414e60a87471bd6ae300a79f85cdc7923ef4f81d0"
|
|
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 @@
|
|
|
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"}
|
package/dist/meta.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-forgejo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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.
|
|
57
|
-
"@intentius/chant-lexicon-github": "^0.
|
|
56
|
+
"@intentius/chant": "^0.25.0",
|
|
57
|
+
"@intentius/chant-lexicon-github": "^0.25.0"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
|
-
"
|
|
61
|
-
"
|
|
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,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,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
|
+
});
|
package/src/lsp/hover.ts
ADDED
|
@@ -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
|
-
|
|
178
|
-
|
|
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
|
};
|