@intentius/chant-lexicon-forgejo 0.104.2 → 0.104.4
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 +7 -5
- package/dist/dialect.d.ts +5 -2
- package/dist/dialect.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +3 -3
- package/src/components/__fixtures__/pr-loop.forgejo.golden.yml +8 -0
- package/src/components/generate-pr-pipeline.test.ts +2 -2
- package/src/dialect.test.ts +16 -3
- package/src/dialect.ts +7 -4
- package/src/migrate/from-github/security.test.ts +2 -2
- package/src/migrate/from-github/security.ts +4 -4
- package/src/migrate.mcp.test.ts +1 -3
- package/src/migrate.test.ts +2 -2
- package/src/serializer.test.ts +7 -7
package/README.md
CHANGED
|
@@ -31,9 +31,11 @@ export const build = new Job({
|
|
|
31
31
|
|
|
32
32
|
On build, the Forgejo dialect:
|
|
33
33
|
|
|
34
|
-
- **Drops keys Forgejo ignores** — `
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
- **Drops keys Forgejo ignores** — `continue-on-error` is silently ignored by
|
|
35
|
+
the Forgejo runner, so it is removed from the output and reported as a build
|
|
36
|
+
warning (emitting it is misleading). `permissions` is kept, so a job can
|
|
37
|
+
declare `id-token: write` to request an OIDC token; a Forgejo version that
|
|
38
|
+
does not honour it ignores the key.
|
|
37
39
|
- **Maps runner labels** — GitHub-hosted labels like `ubuntu-latest` have no
|
|
38
40
|
fixed meaning on Forgejo. They are mapped to a default Forgejo label
|
|
39
41
|
(`docker`), overridable per project. Unmapped labels pass through with a
|
|
@@ -75,8 +77,8 @@ fate across the edge:
|
|
|
75
77
|
|---|---|
|
|
76
78
|
| `translated` | carried across as-is |
|
|
77
79
|
| `approximated` | carried with a close equivalent |
|
|
78
|
-
| `needs-review` | confirm/adjust on Forgejo (unresolved `uses:`, unmapped runner label) |
|
|
79
|
-
| `lost` | the Forgejo runner ignores it (`
|
|
80
|
+
| `needs-review` | confirm/adjust on Forgejo (`permissions`, unresolved `uses:`, unmapped runner label) |
|
|
81
|
+
| `lost` | the Forgejo runner ignores it (`continue-on-error`) |
|
|
80
82
|
|
|
81
83
|
The same view is available to agents as the `forgejo:compare` MCP tool, which
|
|
82
84
|
takes a workflow file and returns per-property fates plus summary counts —
|
package/dist/dialect.d.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* the right shape. The dialect differs in three small ways, all handled here as
|
|
7
7
|
* a pre-pass over the resolved entity graph before the github serializer runs:
|
|
8
8
|
*
|
|
9
|
-
* 1. `
|
|
10
|
-
*
|
|
9
|
+
* 1. `continue-on-error` is silently ignored by the Forgejo runner — we drop
|
|
10
|
+
* it from the output and warn per occurrence. `permissions` is kept: a
|
|
11
|
+
* Forgejo version that honours it (15 and later may) needs `id-token: write`
|
|
12
|
+
* on a job to request an OIDC token, and an older runner ignores the key.
|
|
13
|
+
* There is no version setting in the dialect, so the key always passes.
|
|
11
14
|
* 2. GitHub-hosted runner labels (`ubuntu-latest`, …) have no fixed meaning on
|
|
12
15
|
* Forgejo — we map them to a default Forgejo label, overridable per project.
|
|
13
16
|
* 3. Anything we can't place (an unmapped runner label) passes through with a
|
package/dist/dialect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAgBlG;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKxD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,iEAAiE;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAmFD,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,qBAA0B,GAClC,qBAAqB,CAKvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAWtB"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "ea465622093caf8c29ac10e03f2e8c869a78203f7f034ffdc8c4cb09ccdfd327",
|
|
5
5
|
"meta.json": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
|
6
6
|
"types/index.d.ts": "66204549b2a864ab5489e3f02c1878e73ab4b3c9d938cfb480b66f71543a43de",
|
|
7
7
|
"rules/delegate-to-github.ts": "1060cda40f4b73d6cca3ba3fa13b80b4886147d4034c88584c2d80c5510dbe5e",
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
"rules/wfj011.ts": "fcb8bf6685d744af20ad804ce929488257995c74b9a3353b2eacae3768d1c83e",
|
|
10
10
|
"skills/chant-forgejo.md": "a1a560429db736c187e0b34cf8dd9efc6ad771afefcbf6f802d160d3d5274257"
|
|
11
11
|
},
|
|
12
|
-
"composite": "
|
|
12
|
+
"composite": "439fbfd53af4302505724bcb258cf699c7b005bdb6d4267b22ed32f15d00be57"
|
|
13
13
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-forgejo",
|
|
3
|
-
"version": "0.104.
|
|
3
|
+
"version": "0.104.4",
|
|
4
4
|
"description": "Forgejo / Codeberg / Gitea Actions lexicon for chant — a thin GitHub Actions dialect",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"zod": "^4.3.6",
|
|
57
|
-
"@intentius/chant": "^0.104.
|
|
58
|
-
"@intentius/chant-lexicon-github": "^0.104.
|
|
57
|
+
"@intentius/chant": "^0.104.4",
|
|
58
|
+
"@intentius/chant-lexicon-github": "^0.104.4"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"generate": "tsx src/codegen/generate-cli.ts",
|
|
@@ -16,6 +16,10 @@ jobs:
|
|
|
16
16
|
if: github.event_name == 'pull_request'
|
|
17
17
|
runs-on: docker
|
|
18
18
|
container: node:22-slim
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
pull-requests: write
|
|
22
|
+
statuses: write
|
|
19
23
|
steps:
|
|
20
24
|
- uses: https://code.forgejo.org/actions/checkout@v7
|
|
21
25
|
with:
|
|
@@ -41,6 +45,10 @@ jobs:
|
|
|
41
45
|
concurrency:
|
|
42
46
|
group: chant-apply-prod
|
|
43
47
|
cancel-in-progress: false
|
|
48
|
+
permissions:
|
|
49
|
+
contents: write
|
|
50
|
+
pull-requests: write
|
|
51
|
+
statuses: write
|
|
44
52
|
steps:
|
|
45
53
|
- uses: https://code.forgejo.org/actions/checkout@v7
|
|
46
54
|
with:
|
|
@@ -38,11 +38,11 @@ describe("the Forgejo pull-request workflow", () => {
|
|
|
38
38
|
expect(result.yaml).toBe(readFileSync(GOLDEN, "utf-8"));
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
test("its commands talk to Forgejo, on Forgejo's runner, with
|
|
41
|
+
test("its commands talk to Forgejo, on Forgejo's runner, with the job permissions kept", () => {
|
|
42
42
|
expect(result.yaml).toContain("--forge forgejo");
|
|
43
43
|
expect(result.yaml).not.toContain("--forge github");
|
|
44
44
|
expect(result.yaml).toContain("runs-on: docker");
|
|
45
|
-
expect(result.yaml).
|
|
45
|
+
expect(result.yaml).toMatch(/^\s+permissions:/m);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test.skipIf(!hasActionlint())("passes actionlint, less the runner label and actions by URL", () => {
|
package/src/dialect.test.ts
CHANGED
|
@@ -31,13 +31,26 @@ function entityMap(...pairs: Array<[string, Declarable]>): Map<string, Declarabl
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
describe("applyForgejoDialect — dropped keys", () => {
|
|
34
|
-
test("
|
|
34
|
+
test("keeps workflow-level permissions without warning", () => {
|
|
35
35
|
const wf = new MockWorkflow({ name: "CI", permissions: { contents: "read" } });
|
|
36
36
|
const { entities, warnings } = applyForgejoDialect(entityMap(["workflow", wf]));
|
|
37
37
|
const props = (entities.get("workflow") as MockWorkflow).props;
|
|
38
|
-
expect(props.permissions).
|
|
38
|
+
expect(props.permissions).toEqual({ contents: "read" });
|
|
39
39
|
expect(props.name).toBe("CI");
|
|
40
|
-
expect(warnings.filter((w) => w.includes("permissions"))).toHaveLength(
|
|
40
|
+
expect(warnings.filter((w) => w.includes("permissions"))).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("keeps job permissions, so a job can request an OIDC token", () => {
|
|
44
|
+
const job = new MockJob({
|
|
45
|
+
"runs-on": "ubuntu-latest",
|
|
46
|
+
permissions: { "id-token": "write", contents: "read" },
|
|
47
|
+
steps: [{ name: "a", run: "echo a" }],
|
|
48
|
+
});
|
|
49
|
+
const { entities, warnings } = applyForgejoDialect(entityMap(["job", job]));
|
|
50
|
+
const props = (entities.get("job") as MockJob).props;
|
|
51
|
+
expect(props.permissions).toEqual({ "id-token": "write", contents: "read" });
|
|
52
|
+
expect(props["runs-on"]).toBe("docker");
|
|
53
|
+
expect(warnings.filter((w) => w.includes("permissions"))).toHaveLength(0);
|
|
41
54
|
});
|
|
42
55
|
|
|
43
56
|
test("drops job and step continue-on-error, one warning each", () => {
|
package/src/dialect.ts
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* the right shape. The dialect differs in three small ways, all handled here as
|
|
7
7
|
* a pre-pass over the resolved entity graph before the github serializer runs:
|
|
8
8
|
*
|
|
9
|
-
* 1. `
|
|
10
|
-
*
|
|
9
|
+
* 1. `continue-on-error` is silently ignored by the Forgejo runner — we drop
|
|
10
|
+
* it from the output and warn per occurrence. `permissions` is kept: a
|
|
11
|
+
* Forgejo version that honours it (15 and later may) needs `id-token: write`
|
|
12
|
+
* on a job to request an OIDC token, and an older runner ignores the key.
|
|
13
|
+
* There is no version setting in the dialect, so the key always passes.
|
|
11
14
|
* 2. GitHub-hosted runner labels (`ubuntu-latest`, …) have no fixed meaning on
|
|
12
15
|
* Forgejo — we map them to a default Forgejo label, overridable per project.
|
|
13
16
|
* 3. Anything we can't place (an unmapped runner label) passes through with a
|
|
@@ -25,11 +28,11 @@ import { isDeclarable, isResourceDeclarable, type Declarable } from "@intentius/
|
|
|
25
28
|
import { resolveActionRef } from "./actions";
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
|
-
* Keys the Forgejo runner ignores. Emitting them is misleading (they look
|
|
31
|
+
* Keys the Forgejo runner ignores. `permissions` is not among them: see above. Emitting them is misleading (they look
|
|
29
32
|
* enforced but aren't), so the dialect drops them. Compared in kebab-case so
|
|
30
33
|
* both `continueOnError` and `continue-on-error` spellings are caught.
|
|
31
34
|
*/
|
|
32
|
-
const DROPPED_KEYS = new Set(["
|
|
35
|
+
const DROPPED_KEYS = new Set(["continue-on-error"]);
|
|
33
36
|
|
|
34
37
|
/** Property key whose value is a runner-label selector. */
|
|
35
38
|
const RUNS_ON_KEY = "runs-on";
|
|
@@ -7,7 +7,7 @@ function analyze(yaml: string) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
describe("analyzeForgejoSecurity — fate classes", () => {
|
|
10
|
-
test("workflow-level permissions
|
|
10
|
+
test("workflow-level permissions needs review", () => {
|
|
11
11
|
const records = analyze(`on: push
|
|
12
12
|
permissions:
|
|
13
13
|
contents: read
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
`);
|
|
20
20
|
const perm = records.filter((r) => r.rule === "MIG-FJ-PERMISSIONS");
|
|
21
21
|
expect(perm).toHaveLength(1);
|
|
22
|
-
expect(perm[0].security.fate).toBe("
|
|
22
|
+
expect(perm[0].security.fate).toBe("needs-review");
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
test("continue-on-error is lost (job and step)", () => {
|
|
@@ -78,12 +78,12 @@ export function analyzeForgejoSecurity(
|
|
|
78
78
|
records.push({
|
|
79
79
|
sourceKey: childPath,
|
|
80
80
|
sourceFile: file,
|
|
81
|
-
category: "
|
|
81
|
+
category: "needs-review",
|
|
82
82
|
rule: "MIG-FJ-PERMISSIONS",
|
|
83
|
-
note: `permissions: is
|
|
84
|
-
security: { property: "Least-privilege permissions", fate: "
|
|
83
|
+
note: `permissions: is kept, but only a Forgejo version that honours it enforces it (15 and later may). An older runner ignores it, so confirm the token scope in your Forgejo/runner and repository settings.`,
|
|
84
|
+
security: { property: "Least-privilege permissions", fate: "needs-review", severity: "warning", reestablish: "Forgejo runner/token settings on a version that ignores permissions" },
|
|
85
85
|
});
|
|
86
|
-
continue;
|
|
86
|
+
continue;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (kebab === "continue-on-error") {
|
package/src/migrate.mcp.test.ts
CHANGED
|
@@ -58,9 +58,7 @@ describe("forgejo MCP compare tool", () => {
|
|
|
58
58
|
|
|
59
59
|
expect(result.found).toBe(true);
|
|
60
60
|
const fates = result.properties.map((p) => p.fate);
|
|
61
|
-
expect(fates).toContain("
|
|
62
|
-
expect(fates).toContain("needs-review"); // unmapped action ref
|
|
63
|
-
expect(result.summary.lost).toBeGreaterThanOrEqual(1);
|
|
61
|
+
expect(fates).toContain("needs-review"); // permissions, unmapped action ref
|
|
64
62
|
expect(result.summary["needs-review"]).toBeGreaterThanOrEqual(1);
|
|
65
63
|
});
|
|
66
64
|
|
package/src/migrate.test.ts
CHANGED
|
@@ -23,7 +23,7 @@ describe("github → forgejo transform", () => {
|
|
|
23
23
|
test("emits forgejo YAML with the dialect applied", async () => {
|
|
24
24
|
const { output } = await transform(GHA_WORKFLOW, { sourceFile: "ci.yml" });
|
|
25
25
|
expect(output).toContain("runs-on: docker");
|
|
26
|
-
expect(output).
|
|
26
|
+
expect(output).toContain("permissions");
|
|
27
27
|
expect(output).not.toContain("continue-on-error");
|
|
28
28
|
expect(output).toContain("https://code.forgejo.org/actions/checkout@v4");
|
|
29
29
|
// unmapped ref still present (passed through), surfaced in the compare
|
|
@@ -36,7 +36,7 @@ describe("github → forgejo transform", () => {
|
|
|
36
36
|
expect(rules).toContain("MIG-FJ-PERMISSIONS");
|
|
37
37
|
expect(rules).toContain("MIG-FJ-CONTINUE-ON-ERROR");
|
|
38
38
|
expect(rules).toContain("MIG-FJ-ACTION-UNRESOLVED");
|
|
39
|
-
expect(provenance.find((r) => r.rule === "MIG-FJ-PERMISSIONS")?.security.fate).toBe("
|
|
39
|
+
expect(provenance.find((r) => r.rule === "MIG-FJ-PERMISSIONS")?.security.fate).toBe("needs-review");
|
|
40
40
|
expect(securityPosture).toContain("## Security posture");
|
|
41
41
|
expect(diagnostics.length).toBe(provenance.length);
|
|
42
42
|
});
|
package/src/serializer.test.ts
CHANGED
|
@@ -47,12 +47,12 @@ describe("forgejoSerializer — github-style source roundtrip", () => {
|
|
|
47
47
|
expect(primary).toContain("npm run build");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
test("
|
|
50
|
+
test("keeps permissions, drops continue-on-error with a warning on each", () => {
|
|
51
51
|
const result = asResult(forgejoSerializer.serialize(buildSource()));
|
|
52
|
-
expect(result.primary).
|
|
52
|
+
expect(result.primary).toContain("permissions");
|
|
53
53
|
expect(result.primary).not.toContain("continue-on-error");
|
|
54
|
-
//
|
|
55
|
-
expect((result.warnings ?? []).filter((w) => w.includes("permissions"))).toHaveLength(
|
|
54
|
+
// job continue-on-error + step continue-on-error
|
|
55
|
+
expect((result.warnings ?? []).filter((w) => w.includes("permissions"))).toHaveLength(0);
|
|
56
56
|
expect((result.warnings ?? []).filter((w) => w.includes("continue-on-error"))).toHaveLength(2);
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -162,9 +162,9 @@ describe("forgejoSerializer — inherits PrPlanReport from github (#1983)", () =
|
|
|
162
162
|
const result = asResult(out);
|
|
163
163
|
expect(result.primary).toContain("runs-on: docker");
|
|
164
164
|
expect(result.primary).not.toContain("ubuntu-latest");
|
|
165
|
-
// The job carries `permissions
|
|
166
|
-
expect(result.primary).
|
|
167
|
-
expect((result.warnings ?? []).filter((w) => w.includes("permissions"))).toHaveLength(
|
|
165
|
+
// The job carries `permissions`, which the dialect keeps.
|
|
166
|
+
expect(result.primary).toContain("permissions:");
|
|
167
|
+
expect((result.warnings ?? []).filter((w) => w.includes("permissions"))).toHaveLength(0);
|
|
168
168
|
// The sticky-comment mechanism is a raw script, nothing to remap or drop.
|
|
169
169
|
expect(result.primary).toContain("Post or update PR comment");
|
|
170
170
|
expect(result.primary).toContain("gh api");
|