@intentius/chant-lexicon-gitlab 0.1.12 → 0.1.14
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 +4 -0
- package/dist/integrity.json +3 -2
- package/dist/manifest.json +1 -1
- package/dist/skills/chant-gitlab-migrate.md +117 -0
- package/package.json +11 -4
- package/src/import/generator.ts +20 -2
- package/src/migrate/from-github/actions/index.ts +27 -0
- package/src/migrate/from-github/actions/registry.ts +112 -0
- package/src/migrate/from-github/actions/tier-1.test.ts +128 -0
- package/src/migrate/from-github/actions/tier-1.ts +325 -0
- package/src/migrate/from-github/actions/tier-2-3.test.ts +144 -0
- package/src/migrate/from-github/actions/tier-2.ts +296 -0
- package/src/migrate/from-github/actions/tier-3.ts +124 -0
- package/src/migrate/from-github/composites/patterns.ts +167 -0
- package/src/migrate/from-github/composites/rewriter.test.ts +98 -0
- package/src/migrate/from-github/composites/rewriter.ts +29 -0
- package/src/migrate/from-github/diagnostics.ts +45 -0
- package/src/migrate/from-github/emit-ts.test.ts +49 -0
- package/src/migrate/from-github/emit-yaml.ts +128 -0
- package/src/migrate/from-github/expressions.test.ts +124 -0
- package/src/migrate/from-github/expressions.ts +302 -0
- package/src/migrate/from-github/fixtures/README.md +27 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-checkout/expected-report.json +15 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-checkout/expected.gitlab-ci.yml +13 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-checkout/input.yml +7 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-node/expected-report.json +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-node/expected.gitlab-ci.yml +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-node/input.yml +12 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-python/expected-report.json +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-python/expected.gitlab-ci.yml +17 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/actions-setup-python/input.yml +12 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/docker-build-push/expected-report.json +24 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/docker-build-push/expected.gitlab-ci.yml +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/docker-build-push/input.yml +16 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/upload-download-artifact/expected-report.json +24 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/upload-download-artifact/expected.gitlab-ci.yml +27 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-1/upload-download-artifact/input.yml +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/codecov-action/expected-report.json +24 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/codecov-action/expected.gitlab-ci.yml +15 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/codecov-action/input.yml +13 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/setup-bun/expected-report.json +20 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/setup-bun/expected.gitlab-ci.yml +17 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-2/setup-bun/input.yml +11 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-3/paths-filter/expected-report.json +21 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-3/paths-filter/expected.gitlab-ci.yml +15 -0
- package/src/migrate/from-github/fixtures/marketplace-actions/tier-3/paths-filter/input.yml +11 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/01-triggers/expected-report.json +20 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/01-triggers/expected.gitlab-ci.yml +16 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/01-triggers/input.yml +12 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/02-stages-needs/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/02-stages-needs/expected.gitlab-ci.yml +31 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/02-stages-needs/input.yml +16 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/03-matrix/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/03-matrix/expected.gitlab-ci.yml +20 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/03-matrix/input.yml +10 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/04-env-secrets/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/04-env-secrets/expected.gitlab-ci.yml +18 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/04-env-secrets/input.yml +11 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/05-conditional/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/05-conditional/expected.gitlab-ci.yml +24 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/05-conditional/input.yml +12 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/06-services/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/06-services/expected.gitlab-ci.yml +18 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/06-services/input.yml +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/07-job-control/expected-report.json +20 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/07-job-control/expected.gitlab-ci.yml +17 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/07-job-control/input.yml +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/08-workflow-name/expected-report.json +13 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/08-workflow-name/expected.gitlab-ci.yml +14 -0
- package/src/migrate/from-github/fixtures/syntax-mapping/08-workflow-name/input.yml +7 -0
- package/src/migrate/from-github/fixtures.test.ts +92 -0
- package/src/migrate/from-github/index.ts +128 -0
- package/src/migrate/from-github/provenance.ts +68 -0
- package/src/migrate/from-github/rules.ts +82 -0
- package/src/migrate/from-github/stages.test.ts +99 -0
- package/src/migrate/from-github/stages.ts +177 -0
- package/src/migrate/from-github/transformer.test.ts +278 -0
- package/src/migrate/from-github/transformer.ts +719 -0
- package/src/migrate.mcp.test.ts +69 -0
- package/src/plugin.test.ts +7 -3
- package/src/plugin.ts +105 -1
- package/src/skills/chant-gitlab-migrate.md +117 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke test for the gitlab MCP `migrate` tool. Exercises the tool's
|
|
3
|
+
* handler directly (no JSON-RPC harness needed — the tool contract is
|
|
4
|
+
* a plain async function from inputSchema params to a result object).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect } from "vitest";
|
|
8
|
+
import { gitlabPlugin } from "./plugin";
|
|
9
|
+
|
|
10
|
+
describe("gitlab MCP migrate tool", () => {
|
|
11
|
+
test("registered alongside the diff tool", () => {
|
|
12
|
+
const tools = gitlabPlugin.mcpTools?.() ?? [];
|
|
13
|
+
const names = tools.map((t) => t.name);
|
|
14
|
+
// The MCP server applies ${plugin.name}: namespacing at registration
|
|
15
|
+
// time. Diff is pre-prefixed by createDiffTool; migrate is plain.
|
|
16
|
+
expect(names).toContain("gitlab:diff");
|
|
17
|
+
expect(names).toContain("migrate");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("handler translates a trivial workflow", async () => {
|
|
21
|
+
const tools = gitlabPlugin.mcpTools?.() ?? [];
|
|
22
|
+
const migrate = tools.find((t) => t.name === "migrate");
|
|
23
|
+
expect(migrate).toBeDefined();
|
|
24
|
+
const result = await migrate!.handler({
|
|
25
|
+
content: `on: push
|
|
26
|
+
jobs:
|
|
27
|
+
build:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- run: echo hello
|
|
31
|
+
`,
|
|
32
|
+
}) as { output: string; diagnostics: unknown[]; provenance: unknown[]; stages: string[] };
|
|
33
|
+
|
|
34
|
+
expect(result.output).toContain("stages:");
|
|
35
|
+
expect(result.output).toContain("build:");
|
|
36
|
+
expect(Array.isArray(result.diagnostics)).toBe(true);
|
|
37
|
+
expect(Array.isArray(result.provenance)).toBe(true);
|
|
38
|
+
expect(result.stages).toContain("build");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("respects useComposites toggle", async () => {
|
|
42
|
+
const tools = gitlabPlugin.mcpTools?.() ?? [];
|
|
43
|
+
const migrate = tools.find((t) => t.name === "migrate")!;
|
|
44
|
+
const nodePipelineWorkflow = `on: push
|
|
45
|
+
jobs:
|
|
46
|
+
build:
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: actions/setup-node@v4
|
|
51
|
+
with: { node-version: '20' }
|
|
52
|
+
- run: npm ci
|
|
53
|
+
- run: npm run build
|
|
54
|
+
test:
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
needs: build
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/setup-node@v4
|
|
60
|
+
with: { node-version: '20' }
|
|
61
|
+
- run: npm ci
|
|
62
|
+
- run: npm test
|
|
63
|
+
`;
|
|
64
|
+
const without = await migrate.handler({ content: nodePipelineWorkflow, emit: "ts" }) as { output: string };
|
|
65
|
+
const withC = await migrate.handler({ content: nodePipelineWorkflow, emit: "ts", useComposites: true }) as { output: string };
|
|
66
|
+
expect(without.output).not.toContain("NodePipeline(");
|
|
67
|
+
expect(withC.output).toContain("NodePipeline(");
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/plugin.test.ts
CHANGED
|
@@ -214,9 +214,13 @@ describe("gitlabPlugin", () => {
|
|
|
214
214
|
|
|
215
215
|
test("returns MCP tools", () => {
|
|
216
216
|
const tools = gitlabPlugin.mcpTools!();
|
|
217
|
-
expect(tools).
|
|
218
|
-
|
|
219
|
-
expect(
|
|
217
|
+
expect(tools.length).toBeGreaterThanOrEqual(2);
|
|
218
|
+
const names = tools.map((t) => t.name);
|
|
219
|
+
expect(names).toContain("gitlab:diff");
|
|
220
|
+
expect(names).toContain("migrate");
|
|
221
|
+
for (const t of tools) {
|
|
222
|
+
expect(typeof t.handler).toBe("function");
|
|
223
|
+
}
|
|
220
224
|
});
|
|
221
225
|
|
|
222
226
|
test("returns MCP resources", () => {
|
package/src/plugin.ts
CHANGED
|
@@ -252,7 +252,68 @@ export const test = new Job({
|
|
|
252
252
|
},
|
|
253
253
|
|
|
254
254
|
mcpTools() {
|
|
255
|
-
return [
|
|
255
|
+
return [
|
|
256
|
+
createDiffTool(gitlabSerializer, "Compare current build output against previous output for GitLab CI", "gitlab"),
|
|
257
|
+
{
|
|
258
|
+
name: "migrate",
|
|
259
|
+
description: "Translate a GitHub Actions workflow YAML into a GitLab CI/CD pipeline. Returns the rendered output plus diagnostic + provenance arrays.",
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: "object" as const,
|
|
262
|
+
properties: {
|
|
263
|
+
content: { type: "string", description: "Raw .github/workflows/*.yml content" },
|
|
264
|
+
emit: { type: "string", enum: ["yaml", "ts"], description: "Output format (default: yaml)" },
|
|
265
|
+
useComposites: { type: "boolean", description: "Recognise composite patterns and emit NodePipeline/NodeCI calls" },
|
|
266
|
+
strict: { type: "boolean", description: "Escalate needs-review diagnostics to errors" },
|
|
267
|
+
},
|
|
268
|
+
required: ["content"],
|
|
269
|
+
},
|
|
270
|
+
async handler(params: Record<string, unknown>): Promise<unknown> {
|
|
271
|
+
const { transform } = await import("./migrate/from-github/index");
|
|
272
|
+
const result = await transform(params.content as string, {
|
|
273
|
+
emit: (params.emit as "yaml" | "ts" | undefined) ?? "yaml",
|
|
274
|
+
useComposites: !!params.useComposites,
|
|
275
|
+
strict: !!params.strict,
|
|
276
|
+
sourceFile: "<mcp-input>",
|
|
277
|
+
});
|
|
278
|
+
return {
|
|
279
|
+
output: result.output,
|
|
280
|
+
diagnostics: result.diagnostics,
|
|
281
|
+
provenance: result.provenance,
|
|
282
|
+
stages: result.stages,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
];
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
migrationSource(from: string) {
|
|
290
|
+
if (from !== "github") return undefined;
|
|
291
|
+
return {
|
|
292
|
+
detect(content: string): boolean {
|
|
293
|
+
// Avoid bringing the migrate code into the import graph until needed
|
|
294
|
+
if (!/^\s*jobs\s*:/m.test(content)) return false;
|
|
295
|
+
return /^\s*on\s*:/m.test(content) || /^\s*runs-on\s*:/m.test(content);
|
|
296
|
+
},
|
|
297
|
+
async transform(content: string, opts) {
|
|
298
|
+
const { transform } = await import("./migrate/from-github/index");
|
|
299
|
+
const result = await transform(content, {
|
|
300
|
+
emit: opts.emit,
|
|
301
|
+
useComposites: opts.useComposites,
|
|
302
|
+
sourceFile: opts.sourceFile,
|
|
303
|
+
strict: opts.strict,
|
|
304
|
+
});
|
|
305
|
+
// The composites rewriter (when enabled) replaces several Job
|
|
306
|
+
// resources with a single Composite resource — stages: in the
|
|
307
|
+
// top-level YAML output is now stale for that path. The yaml
|
|
308
|
+
// emitter reads metadata.stages directly; no special handling
|
|
309
|
+
// needed at the call site.
|
|
310
|
+
return {
|
|
311
|
+
output: result.output,
|
|
312
|
+
provenance: result.provenance as unknown as Array<Record<string, unknown>>,
|
|
313
|
+
diagnostics: result.diagnostics as unknown as Array<Record<string, unknown>>,
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
};
|
|
256
317
|
},
|
|
257
318
|
|
|
258
319
|
mcpResources() {
|
|
@@ -400,6 +461,49 @@ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \\
|
|
|
400
461
|
},
|
|
401
462
|
],
|
|
402
463
|
},
|
|
464
|
+
{
|
|
465
|
+
file: "chant-gitlab-migrate.md",
|
|
466
|
+
name: "chant-gitlab-migrate",
|
|
467
|
+
description: "Translate GitHub Actions workflows into GitLab CI/CD pipelines via chant migrate",
|
|
468
|
+
triggers: [
|
|
469
|
+
{ type: "file-pattern", value: "**/.github/workflows/*.yml" },
|
|
470
|
+
{ type: "file-pattern", value: "**/.github/workflows/*.yaml" },
|
|
471
|
+
{ type: "context", value: "migrate from github actions" },
|
|
472
|
+
{ type: "context", value: "github actions to gitlab" },
|
|
473
|
+
{ type: "context", value: "convert workflow" },
|
|
474
|
+
],
|
|
475
|
+
preConditions: [
|
|
476
|
+
"chant CLI is installed (chant --version succeeds)",
|
|
477
|
+
"@intentius/chant-lexicon-gitlab is installed",
|
|
478
|
+
],
|
|
479
|
+
postConditions: [
|
|
480
|
+
"Translated .gitlab-ci.yml or .ts source on disk",
|
|
481
|
+
"Migration report visible to the user (Markdown + SARIF if --report)",
|
|
482
|
+
],
|
|
483
|
+
parameters: [],
|
|
484
|
+
examples: [
|
|
485
|
+
{
|
|
486
|
+
title: "Translate a single workflow file",
|
|
487
|
+
description: "Migrate .github/workflows/ci.yml into .gitlab-ci.yml with a SARIF report",
|
|
488
|
+
input: "Migrate this GitHub workflow to GitLab CI",
|
|
489
|
+
output: `npx chant migrate .github/workflows/ci.yml \\
|
|
490
|
+
--output .gitlab-ci.yml \\
|
|
491
|
+
--report migration.sarif`,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
title: "Translate to chant TypeScript",
|
|
495
|
+
description: "Produce typed chant source instead of YAML so the user can maintain the pipeline in chant going forward",
|
|
496
|
+
input: "I want to maintain this in chant — produce TypeScript",
|
|
497
|
+
output: `npx chant migrate .github/workflows/ci.yml --emit ts --output src/pipeline.ts`,
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
title: "Recognise and emit composites",
|
|
501
|
+
description: "Collapse a 2-job NodePipeline-shaped workflow into a single NodePipeline() call",
|
|
502
|
+
input: "Use composites for the upgrade",
|
|
503
|
+
output: `npx chant migrate .github/workflows/ci.yml --emit ts --use-composites --output src/pipeline.ts`,
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
},
|
|
403
507
|
{
|
|
404
508
|
file: "chant-gitlab-patterns.md",
|
|
405
509
|
name: "chant-gitlab-patterns",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-gitlab-migrate
|
|
3
|
+
description: Translate GitHub Actions workflows into GitLab CI/CD pipelines via chant migrate
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Chant GitHub Actions → GitLab CI/CD Migration
|
|
8
|
+
|
|
9
|
+
## When to invoke this skill
|
|
10
|
+
|
|
11
|
+
The user wants to translate a `.github/workflows/*.yml` workflow into a `.gitlab-ci.yml` pipeline. Trigger phrases include:
|
|
12
|
+
|
|
13
|
+
- "migrate this GitHub workflow to GitLab"
|
|
14
|
+
- "convert .github/workflows/ci.yml to GitLab CI"
|
|
15
|
+
- pasting a GitHub workflow and asking "how would this look in GitLab"
|
|
16
|
+
- evaluating GitLab CI/CD from a GitHub Actions background
|
|
17
|
+
- "what's the GitLab equivalent of <action>"
|
|
18
|
+
|
|
19
|
+
This skill is the operational glue around `chant migrate`. The translation logic lives in `@intentius/chant-lexicon-gitlab`; this skill knows how to invoke it, surface the report, and suggest GitLab-only upgrade moments.
|
|
20
|
+
|
|
21
|
+
## Distinction from the upstream GitLab skill
|
|
22
|
+
|
|
23
|
+
The upstream `gitlab-org/ci-cd/github-actions-to-gitlab-ci` Agent Skill (MIT) translates workflows by direct LLM prompting — stateless, freehand each run. `chant migrate` ports the same translation rules into a typed compiler so the output is reproducible, testable, and re-runnable. The skills are complementary:
|
|
24
|
+
|
|
25
|
+
- Use the upstream skill for one-shot read-and-respond ("here's my YAML, paste the answer").
|
|
26
|
+
- Use `chant migrate` when the translation has to survive evolution of the source workflow.
|
|
27
|
+
|
|
28
|
+
## Step 1: Detect
|
|
29
|
+
|
|
30
|
+
Confirm the input is a GitHub Actions workflow. Heuristic: top-level `jobs:` plus either `on:` or per-job `runs-on:`. If you're given a path, read the file. If pasted inline, work from the paste.
|
|
31
|
+
|
|
32
|
+
## Step 2: Dry-run migrate
|
|
33
|
+
|
|
34
|
+
Run with no `--strict`, no `--validate` first so the user sees the full translation including any NeedsReview items:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx chant migrate path/to/workflow.yml --output /tmp/proposed.gitlab-ci.yml --report /tmp/migration.sarif
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or for inline content, pipe via a temp file:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
TMPF=$(mktemp --suffix=.yml) && cat > "$TMPF" <<'EOF'
|
|
44
|
+
<paste workflow here>
|
|
45
|
+
EOF
|
|
46
|
+
npx chant migrate "$TMPF" --output /tmp/proposed.gitlab-ci.yml --report /tmp/migration.sarif
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The Markdown summary always prints to stderr. The SARIF v2.1.0 report goes to `--report`.
|
|
50
|
+
|
|
51
|
+
## Step 3: Review NeedsReview items
|
|
52
|
+
|
|
53
|
+
The transformer surfaces categorised provenance. The categories are:
|
|
54
|
+
|
|
55
|
+
| Category | Severity (default) | Meaning |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| literal | (none, no diagnostic) | Direct key rename (env → variables) |
|
|
58
|
+
| rewrite | (none, no diagnostic) | Expression substitution (github.ref → $CI_COMMIT_REF_NAME) |
|
|
59
|
+
| synthesis | (none, no diagnostic) | Emitted construct with no GH original (inferred stage) |
|
|
60
|
+
| action-map tier 3 | warning | Marketplace action mapping that requires manual review |
|
|
61
|
+
| skipped | info | Intentionally dropped (actions/checkout) |
|
|
62
|
+
| needs-review | warning | No clean translation — user must decide |
|
|
63
|
+
|
|
64
|
+
Common needs-review rules and the right response:
|
|
65
|
+
|
|
66
|
+
- **MIG-PERMISSIONS-001**: GitHub `permissions:` has no per-job equivalent. Configure CI/CD token access at project level (Settings > CI/CD > Token Access).
|
|
67
|
+
- **MIG-ON-SCHEDULE**: GitLab cron schedules live in the UI under CI/CD > Schedules, not in YAML. The workflow rule (`$CI_PIPELINE_SOURCE == "schedule"`) is emitted; the schedule itself needs UI setup.
|
|
68
|
+
- **MIG-ON-DISPATCH**: `workflow_dispatch` inputs require `spec:inputs` (GitLab 17+) with defaults on every input (auto-triggered pipelines can't prompt).
|
|
69
|
+
- **MIG-NEEDS-OUTPUTS-001**: GitHub job outputs require the `artifacts:reports:dotenv` pattern in GitLab. Manual rewire needed.
|
|
70
|
+
- **MIG-ACTION-UNKNOWN**: A marketplace action has no registered mapping. Either replace with an inline script, or add an `--action-mapping <file>` extension (future flag).
|
|
71
|
+
|
|
72
|
+
## Step 4: Validate (optional)
|
|
73
|
+
|
|
74
|
+
If the user has `glci` or `glab` installed locally, run validation:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx chant migrate path/to/workflow.yml --output .gitlab-ci.yml --validate
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The CLI picks `glci` (offline, no auth) first; `glab ci lint` second. If neither is on PATH, `--validate` warns and skips. Pair with `--strict` to make validation failures hard.
|
|
81
|
+
|
|
82
|
+
## Step 5: Decide the emit mode
|
|
83
|
+
|
|
84
|
+
Two modes serve different intents:
|
|
85
|
+
|
|
86
|
+
- `--emit yaml` (default): produces `.gitlab-ci.yml` directly. Right when the user wants the YAML and is done with chant going forward.
|
|
87
|
+
- `--emit ts`: produces chant TypeScript source the user owns. Right when the user wants to maintain the pipeline in chant — they can edit the typed source and rebuild via `chant build` to refresh the YAML.
|
|
88
|
+
|
|
89
|
+
When in doubt, do `--emit yaml` first to validate the translation, then offer `--emit ts` as the long-term ownership option.
|
|
90
|
+
|
|
91
|
+
## Step 6: Suggest the upgrade moments
|
|
92
|
+
|
|
93
|
+
After surfacing the literal translation, opportunistically suggest GitLab-native features that improve on the GitHub original. Only suggest when they actually apply:
|
|
94
|
+
|
|
95
|
+
- `--use-composites` recognises Node-shaped pipelines and emits `NodePipeline({...})` or `NodeCI({...})` instead of raw `Job` constructors. The output is 5-10x shorter and easier to maintain.
|
|
96
|
+
- DAG `needs:` removes stage barriers — jobs run as soon as deps complete (already handled by the transformer for explicit `needs:`).
|
|
97
|
+
- `rules:changes:` enables monorepo path-based job filtering — no GitHub equivalent.
|
|
98
|
+
- `resource_group:` serialises deploys to the same environment (covered when GH `concurrency.group:` is translated).
|
|
99
|
+
- `include:` shares config across projects and workflows.
|
|
100
|
+
- GitLab CI templates (`Auto-DevOps`, `Security/SAST`, `Terraform/Base`) — no GitHub Actions equivalent.
|
|
101
|
+
|
|
102
|
+
## Self-check rubric
|
|
103
|
+
|
|
104
|
+
Before reporting "done" to the user, verify:
|
|
105
|
+
|
|
106
|
+
- [ ] Was `actions/checkout` removed (GitLab clones automatically)?
|
|
107
|
+
- [ ] Was `runs-on:` translated to `image:` (Linux runners) or `tags:` (self-hosted)?
|
|
108
|
+
- [ ] Did every `uses:` step either map cleanly or get flagged with NeedsReview?
|
|
109
|
+
- [ ] Did `if:` conditions translate to `rules:if:` (NOT a string substitution; semantics differ)?
|
|
110
|
+
- [ ] Were `permissions:` items documented as manual project-level setup?
|
|
111
|
+
- [ ] Was the migration report (Markdown + SARIF) surfaced to the user?
|
|
112
|
+
- [ ] Were `NeedsReview` items called out explicitly?
|
|
113
|
+
- [ ] Did I suggest `--use-composites` if Node patterns were detected?
|
|
114
|
+
|
|
115
|
+
## Inspired by
|
|
116
|
+
|
|
117
|
+
`gitlab-org/ci-cd/github-actions-to-gitlab-ci` — MIT. The trigger-phrase patterns, the four-category report shape, and the "suggest GitLab improvements" step are direct ports. The difference: this skill *calls* a compiler instead of *being* one.
|