/** * Public entry point for the GitHub Actions → GitLab CI migration tool. * * Lazy-imports `@intentius/chant-lexicon-github` so users who don't need * migration don't pay the install cost (the github lexicon is an optional * peer dependency of `@intentius/chant-lexicon-gitlab`). */ import type { TemplateIR } from "@intentius/chant/import/parser"; import type { LintDiagnostic } from "@intentius/chant/lint/rule"; import { ProvenanceAccumulator, type ProvenanceRecord } from "./provenance"; import { transformIR } from "./transformer"; import { emitGitlabYaml } from "./emit-yaml"; import { provenanceToDiagnostics } from "./diagnostics"; import { analyzeSecurity, runSecurityChecks, renderSecurityPosture } from "./security"; import { GitLabGenerator } from "../../import/generator"; import { applyComposites } from "./composites/rewriter"; import type { ActionMappingRegistry } from "./actions/registry"; // Importing `./actions/index` triggers auto-registration of Tier 1 // marketplace action mappings into the default registry. This is the // single chokepoint where the registry is wired up. import "./actions/index"; export interface MigrateOptions { /** Output format. */ emit?: "yaml" | "ts"; /** Enable composite-pattern recognition (Node patterns in v1). */ useComposites?: boolean; /** Source file path for provenance (display only). */ sourceFile?: string; /** Inject a custom action mapping registry for testing. */ registry?: ActionMappingRegistry; /** Escalate needs-review diagnostics to errors. */ strict?: boolean; /** * Security-aware migration (#306): classify each security property's fate * across the edge and run the GitLab security post-synth checks against the * migrated YAML. Enabled by the CLI's `--validate` path. */ security?: boolean; } export interface MigrationResult { /** GitLab IR (consumed by `chant import`-style tools downstream). */ ir: TemplateIR; /** Rendered output (YAML by default, TS when emit: "ts"). */ output: string; /** Per-key provenance records. */ provenance: ProvenanceRecord[]; /** SARIF-shaped diagnostics derived from provenance. */ diagnostics: LintDiagnostic[]; /** Inferred stage list. */ stages: string[]; /** Markdown "Security posture" section (#306). */ securityPosture: string; } /** * Migrate a GitHub Actions workflow YAML into GitLab CI. * * @param yamlContent raw .github/workflows/*.yml content * @param opts migration options */ export async function transform( yamlContent: string, opts: MigrateOptions = {}, ): Promise { // Lazy-import the GitHub parser to keep the github lexicon dep optional. let GitHubActionsParser: typeof import("@intentius/chant-lexicon-github/import/parser").GitHubActionsParser; try { ({ GitHubActionsParser } = await import("@intentius/chant-lexicon-github/import/parser")); } catch { throw new Error( "chant migrate from github requires @intentius/chant-lexicon-github. " + "Install it: npm install --save-dev @intentius/chant-lexicon-github", ); } const ghIR = new GitHubActionsParser().parse(yamlContent); const provAcc = new ProvenanceAccumulator(); const transformed = await transformIR(ghIR, { sourceFile: opts.sourceFile, registry: opts.registry, provenance: provAcc, }); let { ir } = transformed; const stages = transformed.stages; // --use-composites: opt-in IR rewrite that turns recognised shapes // (NodePipeline / NodeCI) into composite calls. if (opts.useComposites) { const r = applyComposites(ir); ir = r.ir; provAcc.pushAll(r.provenance); } let output: string; if (opts.emit === "ts") { const generator = new GitLabGenerator(); const files = generator.generate(ir); // For single-file emit (default), concatenate. The migration banner // + per-resource provenance comments are interleaved. const banner = `// Migrated from ${opts.sourceFile ?? "(stdin)"} by chant migrate. // Source tool: github-actions. Edit freely — chant build will pick this up.\n\n`; output = banner + files.map((f) => f.content).join("\n"); // Append NeedsReview TODOs at the bottom for visibility. const todos = provAcc.byCategory("needs-review"); if (todos.length > 0) { output += "\n// TODO(migration): items needing manual review:\n"; for (const t of todos) { output += `// - ${t.rule}: ${t.note ?? ""}\n`; } } } else { output = emitGitlabYaml(ir); } // Security-aware migration (#306): classify each security property's fate // across the edge, and run the GitLab security post-synth checks against the // migrated YAML so weaknesses lost in translation surface on the target side. if (opts.security) { const yamlForSecurity = opts.emit === "ts" ? emitGitlabYaml(ir) : output; provAcc.pushAll(analyzeSecurity(yamlContent, { sourceFile: opts.sourceFile })); provAcc.pushAll(await runSecurityChecks(yamlForSecurity, { sourceFile: opts.sourceFile })); } const provenance = provAcc.all(); const diagnostics = provenanceToDiagnostics(provenance, { strict: opts.strict }); const securityPosture = renderSecurityPosture(provenance); return { ir, output, provenance, diagnostics, stages, securityPosture }; } /** * Lightweight detector: does this YAML look like a GitHub Actions workflow? * Used by the plugin's `migrationSource("github")` extension. */ export function detectGitHubWorkflow(content: string): boolean { // Cheap detection: top-level `jobs:` + `on:` or `runs-on:` appearing nested. if (!/^\s*jobs\s*:/m.test(content)) return false; return /^\s*on\s*:/m.test(content) || /^\s*runs-on\s*:/m.test(content); } export { ProvenanceAccumulator } from "./provenance"; export type { ProvenanceRecord, ProvenanceCategory } from "./provenance"; export type { ActionMapping, ActionMapCtx, ActionMappedResult, ActionMappingRegistry } from "./actions/registry"; export { createRegistry, getDefaultRegistry, lookupAction } from "./actions/registry";