@intentius/chant-lexicon-gitlab 0.124.30 → 0.124.32
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/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +157 -0
- package/dist/codegen/fetch.js +93 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +36 -0
- package/dist/codegen/generate-typescript.js +105 -0
- package/dist/codegen/generate.js +125 -0
- package/dist/codegen/naming.js +48 -0
- package/dist/codegen/package.js +41 -0
- package/dist/codegen/parse.js +539 -0
- package/dist/codegen/patches.js +84 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/components/ci-jobs.js +16 -0
- package/dist/components/generate-op-pipeline.js +288 -0
- package/dist/components/generate-op-waves-pipeline.js +125 -0
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +369 -0
- package/dist/composites/catalog.js +418 -0
- package/dist/composites/docker-build.js +38 -0
- package/dist/composites/index.js +5 -0
- package/dist/composites/mr-plan-report.js +42 -0
- package/dist/composites/node-pipeline.js +67 -0
- package/dist/composites/python-pipeline.js +45 -0
- package/dist/composites/review-app.js +32 -0
- package/dist/coverage.js +35 -0
- package/dist/generated/index.js +25 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/graph-edges.js +202 -0
- package/dist/import/generator.js +196 -0
- package/dist/import/parser.js +111 -0
- package/dist/index.js +26 -0
- package/dist/integrity.json +3 -3
- package/dist/intrinsics.js +33 -0
- package/dist/lint/audit-catalog.js +53 -0
- package/dist/lint/audit-lineage.js +64 -0
- package/dist/lint/post-synth/index.js +84 -0
- package/dist/lint/post-synth/wgl010.js +34 -0
- package/dist/lint/post-synth/wgl011.js +48 -0
- package/dist/lint/post-synth/wgl012.d.ts.map +1 -1
- package/dist/lint/post-synth/wgl012.js +68 -0
- package/dist/lint/post-synth/wgl013.js +57 -0
- package/dist/lint/post-synth/wgl014.js +43 -0
- package/dist/lint/post-synth/wgl015.js +70 -0
- package/dist/lint/post-synth/wgl016.js +70 -0
- package/dist/lint/post-synth/wgl017.js +38 -0
- package/dist/lint/post-synth/wgl018.js +34 -0
- package/dist/lint/post-synth/wgl019.js +43 -0
- package/dist/lint/post-synth/wgl020.js +47 -0
- package/dist/lint/post-synth/wgl021.js +52 -0
- package/dist/lint/post-synth/wgl022.js +38 -0
- package/dist/lint/post-synth/wgl023.js +44 -0
- package/dist/lint/post-synth/wgl024.js +41 -0
- package/dist/lint/post-synth/wgl025.js +42 -0
- package/dist/lint/post-synth/wgl026.js +58 -0
- package/dist/lint/post-synth/wgl027.js +49 -0
- package/dist/lint/post-synth/wgl028.js +58 -0
- package/dist/lint/post-synth/wgl029.js +55 -0
- package/dist/lint/post-synth/wgl030.js +43 -0
- package/dist/lint/post-synth/wgl031.js +33 -0
- package/dist/lint/post-synth/wgl032.js +64 -0
- package/dist/lint/post-synth/wgl033.js +32 -0
- package/dist/lint/post-synth/wgl034.js +37 -0
- package/dist/lint/post-synth/wgl035.js +40 -0
- package/dist/lint/post-synth/wgl036.js +38 -0
- package/dist/lint/post-synth/wgl037.js +41 -0
- package/dist/lint/post-synth/wgl038.js +43 -0
- package/dist/lint/post-synth/wgl039.js +34 -0
- package/dist/lint/post-synth/wgl040.js +35 -0
- package/dist/lint/post-synth/wgl041.js +53 -0
- package/dist/lint/post-synth/wgl042.js +41 -0
- package/dist/lint/post-synth/wgl043.js +39 -0
- package/dist/lint/post-synth/wgl044.js +35 -0
- package/dist/lint/post-synth/wgl045.js +54 -0
- package/dist/lint/post-synth/wgl046.js +40 -0
- package/dist/lint/post-synth/wgl047.js +34 -0
- package/dist/lint/post-synth/wgl048.js +32 -0
- package/dist/lint/post-synth/wgl049.js +53 -0
- package/dist/lint/post-synth/wgl050.js +42 -0
- package/dist/lint/post-synth/yaml-helpers.js +432 -0
- package/dist/lint/rules/artifact-no-expiry.js +53 -0
- package/dist/lint/rules/data/known-include-sources.js +44 -0
- package/dist/lint/rules/data/untrusted-variables.js +30 -0
- package/dist/lint/rules/deprecated-only-except.js +46 -0
- package/dist/lint/rules/import-source.js +61 -0
- package/dist/lint/rules/index.js +7 -0
- package/dist/lint/rules/missing-script.js +67 -0
- package/dist/lint/rules/missing-stage.js +60 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +33 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/context-tools.js +262 -0
- package/dist/migrate/from-github/actions/index.js +17 -0
- package/dist/migrate/from-github/actions/registry.js +61 -0
- package/dist/migrate/from-github/actions/tier-1.js +298 -0
- package/dist/migrate/from-github/actions/tier-2.js +241 -0
- package/dist/migrate/from-github/actions/tier-3.js +96 -0
- package/dist/migrate/from-github/composites/patterns.js +151 -0
- package/dist/migrate/from-github/composites/rewriter.js +22 -0
- package/dist/migrate/from-github/diagnostics.js +53 -0
- package/dist/migrate/from-github/emit-yaml.js +121 -0
- package/dist/migrate/from-github/expressions.js +275 -0
- package/dist/migrate/from-github/index.js +96 -0
- package/dist/migrate/from-github/provenance.js +35 -0
- package/dist/migrate/from-github/rules.js +74 -0
- package/dist/migrate/from-github/security.js +189 -0
- package/dist/migrate/from-github/stages.js +156 -0
- package/dist/migrate/from-github/transformer.js +633 -0
- package/dist/op/activities/gitlab.js +38 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +26 -0
- package/dist/package-cli.js +21 -0
- package/dist/plugin.js +536 -0
- package/dist/rules/wgl012.ts +2 -2
- package/dist/serializer.js +197 -0
- package/dist/testdata/create-fixture.js +40 -0
- package/dist/testdata/load-fixtures.js +20 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +29 -0
- package/dist/variables.js +30 -0
- package/package.json +8 -8
- package/src/codegen/fetch.ts +2 -2
- package/src/codegen/package.ts +2 -2
- package/src/components/__fixtures__/pr-loop-build.gitlab.golden.yml +3 -3
- package/src/components/__fixtures__/pr-loop-waves.gitlab.golden.yml +101 -0
- package/src/components/__fixtures__/pr-loop.gitlab.golden.yml +3 -3
- package/src/components/generate-pipeline.ts +60 -27
- package/src/components/generate-pr-pipeline.test.ts +14 -9
- package/src/components/generate-pr-waves-pipeline.test.ts +88 -0
- package/src/lint/post-synth/wgl012.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 3 marketplace action mappings — 5 niche actions. Several map to
|
|
3
|
+
* native GitLab keywords (rules:changes, retry:) rather than scripts.
|
|
4
|
+
*/
|
|
5
|
+
import { getDefaultRegistry } from "./registry.js";
|
|
6
|
+
const prov = (ctx, actionName, note, category = "action-map") => ({
|
|
7
|
+
gitlabPath: `jobs.${ctx.logicalId}.script`,
|
|
8
|
+
gitlabLogicalId: ctx.logicalId,
|
|
9
|
+
sourceKey: `jobs.${ctx.jobName}.steps[${ctx.stepIndex}].uses`,
|
|
10
|
+
sourceFile: ctx.sourceFile,
|
|
11
|
+
category,
|
|
12
|
+
rule: `ACT-${actionName.replace(/[\/-]/g, "-")}`,
|
|
13
|
+
note,
|
|
14
|
+
actionRef: actionName,
|
|
15
|
+
mappingTier: 3,
|
|
16
|
+
});
|
|
17
|
+
const getWith = (s) => s.with ?? {};
|
|
18
|
+
const tjActionsChangedFiles = {
|
|
19
|
+
actionName: "tj-actions/changed-files",
|
|
20
|
+
tier: 3,
|
|
21
|
+
translate(step, ctx) {
|
|
22
|
+
const w = getWith(step);
|
|
23
|
+
const files = w.files ?? "";
|
|
24
|
+
return {
|
|
25
|
+
scriptLines: [
|
|
26
|
+
`git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA${files ? ` | grep -E '${files.replace(/\n/g, "|")}' || true` : ""}`,
|
|
27
|
+
],
|
|
28
|
+
provenance: [prov(ctx, "tj-actions/changed-files", "tj-actions/changed-files → git diff inline; for path-based job gating prefer rules:changes:")],
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
// dorny/paths-filter: native rules:changes:
|
|
33
|
+
const dornyPathsFilter = {
|
|
34
|
+
actionName: "dorny/paths-filter",
|
|
35
|
+
tier: 3,
|
|
36
|
+
translate(_step, ctx) {
|
|
37
|
+
return {
|
|
38
|
+
scriptLines: [
|
|
39
|
+
"# dorny/paths-filter has no script equivalent in GitLab.",
|
|
40
|
+
"# Use rules:changes: on each job to gate on file path changes.",
|
|
41
|
+
"# Example: rules: [{ changes: ['src/backend/**'] }]",
|
|
42
|
+
],
|
|
43
|
+
provenance: [prov(ctx, "dorny/paths-filter", "dorny/paths-filter → native rules:changes:; emit rule on each gated job", "needs-review")],
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
// nick-invision/retry / nick-fields/retry: native retry:
|
|
48
|
+
const nickFieldsRetry = {
|
|
49
|
+
actionName: "nick-fields/retry",
|
|
50
|
+
tier: 3,
|
|
51
|
+
translate(step, ctx) {
|
|
52
|
+
const w = getWith(step);
|
|
53
|
+
const command = w.command ?? "";
|
|
54
|
+
return {
|
|
55
|
+
scriptLines: command ? [command] : [],
|
|
56
|
+
provenance: [prov(ctx, "nick-fields/retry", "nick-fields/retry → native retry: { max, when } at job level; configure manually", "needs-review")],
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const preCommitAction = {
|
|
61
|
+
actionName: "pre-commit/action",
|
|
62
|
+
tier: 3,
|
|
63
|
+
translate(_step, ctx) {
|
|
64
|
+
return {
|
|
65
|
+
scriptLines: ["pip install pre-commit", "pre-commit run --all-files"],
|
|
66
|
+
provenance: [prov(ctx, "pre-commit/action", "pre-commit/action → pip install pre-commit + pre-commit run")],
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const slackapiGithubAction = {
|
|
71
|
+
actionName: "slackapi/slack-github-action",
|
|
72
|
+
tier: 3,
|
|
73
|
+
translate(step, ctx) {
|
|
74
|
+
const w = getWith(step);
|
|
75
|
+
const payload = w.payload ?? '{"text":"Build completed"}';
|
|
76
|
+
return {
|
|
77
|
+
scriptLines: [
|
|
78
|
+
`curl -X POST -H 'Content-type: application/json' --data '${payload.replace(/'/g, "'\\''")}' $SLACK_WEBHOOK_URL`,
|
|
79
|
+
],
|
|
80
|
+
provenance: [prov(ctx, "slackapi/slack-github-action", "slack-github-action → curl to $SLACK_WEBHOOK_URL")],
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const TIER_3_MAPPINGS = [
|
|
85
|
+
tjActionsChangedFiles,
|
|
86
|
+
dornyPathsFilter,
|
|
87
|
+
nickFieldsRetry,
|
|
88
|
+
preCommitAction,
|
|
89
|
+
slackapiGithubAction,
|
|
90
|
+
];
|
|
91
|
+
export function registerTier3(registry = getDefaultRegistry()) {
|
|
92
|
+
for (const m of TIER_3_MAPPINGS) {
|
|
93
|
+
registry.register(m);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export { TIER_3_MAPPINGS };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern recognisers for `chant migrate --use-composites`.
|
|
3
|
+
*
|
|
4
|
+
* Each pattern walks the GitLab IR looking for a recognisable shape
|
|
5
|
+
* (e.g. NodePipeline-shaped 2-job setup) and rewrites the matched
|
|
6
|
+
* resources into a sentinel IR type (`GitLab::Composite::NodePipeline`)
|
|
7
|
+
* that the GitLab generator (`lexicons/gitlab/src/import/generator.ts`)
|
|
8
|
+
* will emit as `NodePipeline({...})` instead of raw `new Job(...)`.
|
|
9
|
+
*/
|
|
10
|
+
function getProp(r, k) {
|
|
11
|
+
return r.properties[k];
|
|
12
|
+
}
|
|
13
|
+
function looksLikeNodeImage(image) {
|
|
14
|
+
return typeof image === "string" && /^node:/i.test(image);
|
|
15
|
+
}
|
|
16
|
+
function detectPackageManager(script) {
|
|
17
|
+
const text = script.join("\n");
|
|
18
|
+
if (/\b(bun|bunx)\s+(install|test|run)/.test(text))
|
|
19
|
+
return "bun";
|
|
20
|
+
if (/\bpnpm\s+(install|run|test)/.test(text))
|
|
21
|
+
return "pnpm";
|
|
22
|
+
if (/\byarn\s+(install|run|test)/.test(text))
|
|
23
|
+
return "yarn";
|
|
24
|
+
return "npm";
|
|
25
|
+
}
|
|
26
|
+
function hasInstallStep(script, pm) {
|
|
27
|
+
const installers = {
|
|
28
|
+
npm: /\bnpm\s+(ci|install)\b/,
|
|
29
|
+
yarn: /\byarn\s+install\b/,
|
|
30
|
+
pnpm: /\bpnpm\s+install\b/,
|
|
31
|
+
bun: /\bbun\s+install\b/,
|
|
32
|
+
};
|
|
33
|
+
return script.some((line) => installers[pm].test(line));
|
|
34
|
+
}
|
|
35
|
+
function hasBuildStep(script, pm) {
|
|
36
|
+
const re = pm === "npm" ? /\bnpm\s+run\s+build\b/ : new RegExp(`\\b${pm}\\s+(run\\s+)?build\\b`);
|
|
37
|
+
return script.some((line) => re.test(line));
|
|
38
|
+
}
|
|
39
|
+
function hasTestStep(script, pm) {
|
|
40
|
+
const re = pm === "npm"
|
|
41
|
+
? /\bnpm\s+(test|run\s+test)\b/
|
|
42
|
+
: new RegExp(`\\b${pm}\\s+(run\\s+)?test\\b`);
|
|
43
|
+
return script.some((line) => re.test(line));
|
|
44
|
+
}
|
|
45
|
+
const nodePipelinePattern = {
|
|
46
|
+
name: "NodePipeline",
|
|
47
|
+
match(ir) {
|
|
48
|
+
const jobs = ir.resources.filter((r) => r.type === "GitLab::CI::Job");
|
|
49
|
+
if (jobs.length !== 2)
|
|
50
|
+
return null;
|
|
51
|
+
// Find build job + test job
|
|
52
|
+
let buildJob;
|
|
53
|
+
let testJob;
|
|
54
|
+
for (const j of jobs) {
|
|
55
|
+
const script = (getProp(j, "script") ?? []);
|
|
56
|
+
if (!looksLikeNodeImage(getProp(j, "image")))
|
|
57
|
+
return null;
|
|
58
|
+
const pm = detectPackageManager(script);
|
|
59
|
+
if (hasBuildStep(script, pm) && hasInstallStep(script, pm)) {
|
|
60
|
+
buildJob = j;
|
|
61
|
+
}
|
|
62
|
+
else if (hasTestStep(script, pm) && hasInstallStep(script, pm)) {
|
|
63
|
+
testJob = j;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!buildJob || !testJob)
|
|
67
|
+
return null;
|
|
68
|
+
if (buildJob.logicalId === testJob.logicalId)
|
|
69
|
+
return null;
|
|
70
|
+
const buildScript = (getProp(buildJob, "script") ?? []);
|
|
71
|
+
const pm = detectPackageManager(buildScript);
|
|
72
|
+
const image = getProp(buildJob, "image") ?? "node:22";
|
|
73
|
+
const nodeVersion = image.replace(/^node:/, "") || "22";
|
|
74
|
+
return {
|
|
75
|
+
removed: [buildJob.logicalId, testJob.logicalId],
|
|
76
|
+
added: {
|
|
77
|
+
logicalId: "app",
|
|
78
|
+
type: "GitLab::Composite::NodePipeline",
|
|
79
|
+
properties: {
|
|
80
|
+
nodeVersion,
|
|
81
|
+
packageManager: pm,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
rewrite(ir, match) {
|
|
87
|
+
const keep = ir.resources.filter((r) => !match.removed.includes(r.logicalId));
|
|
88
|
+
return {
|
|
89
|
+
ir: { ...ir, resources: [...keep, match.added] },
|
|
90
|
+
provenance: [
|
|
91
|
+
{
|
|
92
|
+
gitlabPath: `jobs.${match.added.logicalId}`,
|
|
93
|
+
gitlabLogicalId: match.added.logicalId,
|
|
94
|
+
category: "synthesis",
|
|
95
|
+
rule: "MIG-COMPOSITE-NODEPIPELINE",
|
|
96
|
+
note: `Recognised NodePipeline shape (${match.removed.join(", ")}) → NodePipeline composite`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const nodeCiPattern = {
|
|
103
|
+
name: "NodeCI",
|
|
104
|
+
match(ir) {
|
|
105
|
+
const jobs = ir.resources.filter((r) => r.type === "GitLab::CI::Job");
|
|
106
|
+
if (jobs.length !== 1)
|
|
107
|
+
return null;
|
|
108
|
+
const j = jobs[0];
|
|
109
|
+
const script = (getProp(j, "script") ?? []);
|
|
110
|
+
if (!looksLikeNodeImage(getProp(j, "image")))
|
|
111
|
+
return null;
|
|
112
|
+
const pm = detectPackageManager(script);
|
|
113
|
+
if (!hasInstallStep(script, pm))
|
|
114
|
+
return null;
|
|
115
|
+
if (!hasBuildStep(script, pm) && !hasTestStep(script, pm))
|
|
116
|
+
return null;
|
|
117
|
+
const image = getProp(j, "image") ?? "node:22";
|
|
118
|
+
const nodeVersion = image.replace(/^node:/, "") || "22";
|
|
119
|
+
return {
|
|
120
|
+
removed: [j.logicalId],
|
|
121
|
+
added: {
|
|
122
|
+
logicalId: "app",
|
|
123
|
+
type: "GitLab::Composite::NodeCI",
|
|
124
|
+
properties: {
|
|
125
|
+
nodeVersion,
|
|
126
|
+
packageManager: pm,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
rewrite(ir, match) {
|
|
132
|
+
const keep = ir.resources.filter((r) => !match.removed.includes(r.logicalId));
|
|
133
|
+
return {
|
|
134
|
+
ir: { ...ir, resources: [...keep, match.added] },
|
|
135
|
+
provenance: [
|
|
136
|
+
{
|
|
137
|
+
gitlabPath: `jobs.${match.added.logicalId}`,
|
|
138
|
+
gitlabLogicalId: match.added.logicalId,
|
|
139
|
+
category: "synthesis",
|
|
140
|
+
rule: "MIG-COMPOSITE-NODECI",
|
|
141
|
+
note: `Recognised NodeCI shape (${match.removed.join(", ")}) → NodeCI composite`,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
export const PATTERNS = [
|
|
148
|
+
// Match NodePipeline first (2 jobs); NodeCI second (single job)
|
|
149
|
+
nodePipelinePattern,
|
|
150
|
+
nodeCiPattern,
|
|
151
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--use-composites` orchestrator: walks the registered patterns and
|
|
3
|
+
* applies the first match. Patterns are tried in declaration order
|
|
4
|
+
* (NodePipeline before NodeCI so the 2-job shape wins over the 1-job
|
|
5
|
+
* shape when both could match).
|
|
6
|
+
*/
|
|
7
|
+
import { PATTERNS } from "./patterns.js";
|
|
8
|
+
export function applyComposites(ir, registry = PATTERNS) {
|
|
9
|
+
const provenance = [];
|
|
10
|
+
let current = ir;
|
|
11
|
+
for (const pattern of registry) {
|
|
12
|
+
const match = pattern.match(current);
|
|
13
|
+
if (match) {
|
|
14
|
+
const result = pattern.rewrite(current, match);
|
|
15
|
+
current = result.ir;
|
|
16
|
+
provenance.push(...result.provenance);
|
|
17
|
+
// Only one composite rewrite per migrate run (keeps semantics simple)
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { ir: current, provenance };
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert ProvenanceRecord[] into LintDiagnostic[] for SARIF output.
|
|
3
|
+
*
|
|
4
|
+
* Mapping:
|
|
5
|
+
* - category "needs-review" → severity "warning" (or "error" if --strict)
|
|
6
|
+
* - category "skipped" → severity "info"
|
|
7
|
+
* - category "action-map" tier 3 → severity "warning"
|
|
8
|
+
* - all others → no diagnostic (clean translation)
|
|
9
|
+
*/
|
|
10
|
+
export function provenanceToDiagnostics(records, opts = {}) {
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const r of records) {
|
|
13
|
+
// Security records always emit a diagnostic, at their own severity
|
|
14
|
+
// (escalated to error under --strict when the property was lost or needs
|
|
15
|
+
// review), independent of the functional category.
|
|
16
|
+
if (r.security) {
|
|
17
|
+
const sev = opts.strict && (r.security.fate === "lost" || r.security.fate === "needs-review")
|
|
18
|
+
? "error"
|
|
19
|
+
: r.security.severity;
|
|
20
|
+
out.push({
|
|
21
|
+
file: r.sourceFile ?? "<input>",
|
|
22
|
+
line: r.sourceLine ?? 1,
|
|
23
|
+
column: r.sourceColumn ?? 1,
|
|
24
|
+
ruleId: r.rule,
|
|
25
|
+
severity: sev,
|
|
26
|
+
message: r.note ?? r.rule,
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let severity;
|
|
31
|
+
if (r.category === "needs-review") {
|
|
32
|
+
severity = opts.strict ? "error" : "warning";
|
|
33
|
+
}
|
|
34
|
+
else if (r.category === "skipped") {
|
|
35
|
+
severity = "info";
|
|
36
|
+
}
|
|
37
|
+
else if (r.category === "action-map" && r.mappingTier === 3) {
|
|
38
|
+
severity = "warning";
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
out.push({
|
|
44
|
+
file: r.sourceFile ?? "<input>",
|
|
45
|
+
line: r.sourceLine ?? 1,
|
|
46
|
+
column: r.sourceColumn ?? 1,
|
|
47
|
+
ruleId: r.rule,
|
|
48
|
+
severity,
|
|
49
|
+
message: r.note ?? r.rule,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct GitLab TemplateIR → `.gitlab-ci.yml` text emission.
|
|
3
|
+
*
|
|
4
|
+
* We deliberately bypass the existing `gitlabSerializer.serialize()` because
|
|
5
|
+
* that path consumes `Map<string, Declarable>` (entity instances built by
|
|
6
|
+
* user TS code), not a raw IR. Going through entity instances would require
|
|
7
|
+
* runtime construction of typed objects we don't have. Direct emission is
|
|
8
|
+
* ~60 LOC.
|
|
9
|
+
*/
|
|
10
|
+
import { emitYAML } from "@intentius/chant/yaml";
|
|
11
|
+
const RESERVED_TOP_LEVEL_ORDER = [
|
|
12
|
+
"spec",
|
|
13
|
+
"include",
|
|
14
|
+
"default",
|
|
15
|
+
"stages",
|
|
16
|
+
"variables",
|
|
17
|
+
"workflow",
|
|
18
|
+
"image",
|
|
19
|
+
"services",
|
|
20
|
+
"before_script",
|
|
21
|
+
"after_script",
|
|
22
|
+
"cache",
|
|
23
|
+
"pages",
|
|
24
|
+
];
|
|
25
|
+
function isReservedTopLevelKey(key) {
|
|
26
|
+
return RESERVED_TOP_LEVEL_ORDER.includes(key);
|
|
27
|
+
}
|
|
28
|
+
function emitKeyValue(key, value, indent) {
|
|
29
|
+
const prefix = " ".repeat(indent);
|
|
30
|
+
if (value === null || value === undefined)
|
|
31
|
+
return `${prefix}${key}: null`;
|
|
32
|
+
if (typeof value === "boolean" || typeof value === "number")
|
|
33
|
+
return `${prefix}${key}: ${value}`;
|
|
34
|
+
if (typeof value === "string")
|
|
35
|
+
return `${prefix}${key}: ${emitYAML(value, indent + 1)}`;
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
if (value.length === 0)
|
|
38
|
+
return `${prefix}${key}: []`;
|
|
39
|
+
return `${prefix}${key}:${emitYAML(value, indent + 1)}`;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "object") {
|
|
42
|
+
if (Object.keys(value).length === 0)
|
|
43
|
+
return `${prefix}${key}: {}`;
|
|
44
|
+
return `${prefix}${key}:${emitYAML(value, indent + 1)}`;
|
|
45
|
+
}
|
|
46
|
+
return `${prefix}${key}: ${String(value)}`;
|
|
47
|
+
}
|
|
48
|
+
function emitBlock(obj, indent) {
|
|
49
|
+
return Object.entries(obj)
|
|
50
|
+
.filter(([, v]) => v !== undefined)
|
|
51
|
+
.map(([k, v]) => emitKeyValue(k, v, indent))
|
|
52
|
+
.join("\n");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convert a GitLab `TemplateIR` (produced by the GH→GL transformer) into
|
|
56
|
+
* `.gitlab-ci.yml` text.
|
|
57
|
+
*/
|
|
58
|
+
export function emitGitlabYaml(ir) {
|
|
59
|
+
const top = {};
|
|
60
|
+
// Stages from metadata
|
|
61
|
+
const meta = (ir.metadata ?? {});
|
|
62
|
+
if (Array.isArray(meta.stages) && meta.stages.length > 0) {
|
|
63
|
+
top.stages = meta.stages;
|
|
64
|
+
}
|
|
65
|
+
// Top-level variables from metadata.variables
|
|
66
|
+
if (meta.variables && typeof meta.variables === "object" && Object.keys(meta.variables).length > 0) {
|
|
67
|
+
top.variables = meta.variables;
|
|
68
|
+
}
|
|
69
|
+
// Workflow / Default / Include resources go into reserved top-level keys
|
|
70
|
+
for (const r of ir.resources) {
|
|
71
|
+
if (r.type === "GitLab::CI::Workflow") {
|
|
72
|
+
top.workflow = r.properties;
|
|
73
|
+
}
|
|
74
|
+
else if (r.type === "GitLab::CI::Default") {
|
|
75
|
+
top.default = r.properties;
|
|
76
|
+
}
|
|
77
|
+
else if (r.type === "GitLab::CI::Include") {
|
|
78
|
+
top.include = r.properties;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Job resources go at the top level keyed by logicalId (kebab-cased)
|
|
82
|
+
const jobs = {};
|
|
83
|
+
for (const r of ir.resources) {
|
|
84
|
+
if (r.type === "GitLab::CI::Job") {
|
|
85
|
+
const key = r.metadata?.originalName ?? r.logicalId;
|
|
86
|
+
if (isReservedTopLevelKey(key)) {
|
|
87
|
+
// Avoid colliding with reserved keywords; prefix
|
|
88
|
+
jobs[`job-${key}`] = r.properties;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
jobs[key] = r.properties;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Emit ordered top-level keys (reserved first, then jobs)
|
|
96
|
+
const lines = [];
|
|
97
|
+
for (const key of RESERVED_TOP_LEVEL_ORDER) {
|
|
98
|
+
if (top[key] !== undefined) {
|
|
99
|
+
lines.push(emitKeyValue(key, top[key], 0));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Migration banner comment
|
|
103
|
+
if (meta.migration && typeof meta.migration === "object") {
|
|
104
|
+
const m = meta.migration;
|
|
105
|
+
if (lines.length === 0) {
|
|
106
|
+
lines.push(`# Generated by chant migrate from ${m.sourceFile ?? "(unknown)"}`);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
lines.unshift("");
|
|
111
|
+
lines.unshift(`# Generated by chant migrate from ${m.sourceFile ?? "(unknown)"}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Jobs (sorted by logicalId for determinism)
|
|
115
|
+
const jobKeys = Object.keys(jobs).sort();
|
|
116
|
+
for (const key of jobKeys) {
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(emitKeyValue(key, jobs[key], 0));
|
|
119
|
+
}
|
|
120
|
+
return lines.join("\n") + "\n";
|
|
121
|
+
}
|