@intentius/chant-lexicon-gitlab 0.0.1
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/package.json +27 -0
- package/src/codegen/__snapshots__/snapshot.test.ts.snap +33 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +962 -0
- package/src/codegen/fetch.ts +73 -0
- package/src/codegen/generate-cli.ts +41 -0
- package/src/codegen/generate-lexicon.ts +53 -0
- package/src/codegen/generate-typescript.ts +144 -0
- package/src/codegen/generate.ts +166 -0
- package/src/codegen/naming.ts +52 -0
- package/src/codegen/package.ts +64 -0
- package/src/codegen/parse.test.ts +195 -0
- package/src/codegen/parse.ts +531 -0
- package/src/codegen/patches.test.ts +99 -0
- package/src/codegen/patches.ts +100 -0
- package/src/codegen/rollback.ts +26 -0
- package/src/codegen/snapshot.test.ts +109 -0
- package/src/coverage.test.ts +39 -0
- package/src/coverage.ts +52 -0
- package/src/generated/index.d.ts +248 -0
- package/src/generated/index.ts +23 -0
- package/src/generated/lexicon-gitlab.json +77 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +151 -0
- package/src/import/generator.ts +173 -0
- package/src/import/parser.test.ts +160 -0
- package/src/import/parser.ts +282 -0
- package/src/import/roundtrip.test.ts +89 -0
- package/src/index.ts +25 -0
- package/src/intrinsics.test.ts +42 -0
- package/src/intrinsics.ts +40 -0
- package/src/lint/post-synth/post-synth.test.ts +155 -0
- package/src/lint/post-synth/wgl010.ts +41 -0
- package/src/lint/post-synth/wgl011.ts +54 -0
- package/src/lint/post-synth/yaml-helpers.ts +88 -0
- package/src/lint/rules/artifact-no-expiry.ts +62 -0
- package/src/lint/rules/deprecated-only-except.ts +53 -0
- package/src/lint/rules/index.ts +8 -0
- package/src/lint/rules/missing-script.ts +65 -0
- package/src/lint/rules/missing-stage.ts +62 -0
- package/src/lint/rules/rules.test.ts +146 -0
- package/src/lsp/completions.test.ts +85 -0
- package/src/lsp/completions.ts +18 -0
- package/src/lsp/hover.test.ts +60 -0
- package/src/lsp/hover.ts +36 -0
- package/src/plugin.test.ts +228 -0
- package/src/plugin.ts +380 -0
- package/src/serializer.test.ts +309 -0
- package/src/serializer.ts +226 -0
- package/src/testdata/ci-schema-fixture.json +2184 -0
- package/src/testdata/create-fixture.ts +46 -0
- package/src/testdata/load-fixtures.ts +23 -0
- package/src/validate-cli.ts +19 -0
- package/src/validate.test.ts +43 -0
- package/src/validate.ts +125 -0
- package/src/variables.ts +27 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { GitLabGenerator } from "./generator";
|
|
3
|
+
import type { TemplateIR } from "@intentius/chant/import/parser";
|
|
4
|
+
|
|
5
|
+
const generator = new GitLabGenerator();
|
|
6
|
+
|
|
7
|
+
describe("GitLabGenerator", () => {
|
|
8
|
+
test("generates import statement", () => {
|
|
9
|
+
const ir: TemplateIR = {
|
|
10
|
+
resources: [
|
|
11
|
+
{ logicalId: "testJob", type: "GitLab::CI::Job", properties: { script: ["test"] } },
|
|
12
|
+
],
|
|
13
|
+
parameters: [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const files = generator.generate(ir);
|
|
17
|
+
expect(files).toHaveLength(1);
|
|
18
|
+
expect(files[0].path).toBe("main.ts");
|
|
19
|
+
expect(files[0].content).toContain('from "@intentius/chant-lexicon-gitlab"');
|
|
20
|
+
expect(files[0].content).toContain("Job");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("generates Job constructor", () => {
|
|
24
|
+
const ir: TemplateIR = {
|
|
25
|
+
resources: [
|
|
26
|
+
{
|
|
27
|
+
logicalId: "buildJob",
|
|
28
|
+
type: "GitLab::CI::Job",
|
|
29
|
+
properties: {
|
|
30
|
+
stage: "build",
|
|
31
|
+
script: ["npm run build"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
parameters: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const files = generator.generate(ir);
|
|
39
|
+
const content = files[0].content;
|
|
40
|
+
expect(content).toContain("export const buildJob = new Job(");
|
|
41
|
+
expect(content).toContain('stage: "build"');
|
|
42
|
+
expect(content).toContain('script: ["npm run build"]');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("generates Default constructor", () => {
|
|
46
|
+
const ir: TemplateIR = {
|
|
47
|
+
resources: [
|
|
48
|
+
{
|
|
49
|
+
logicalId: "defaults",
|
|
50
|
+
type: "GitLab::CI::Default",
|
|
51
|
+
properties: { interruptible: true },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
parameters: [],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const files = generator.generate(ir);
|
|
58
|
+
expect(files[0].content).toContain("export const defaults = new Default(");
|
|
59
|
+
expect(files[0].content).toContain("interruptible: true");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("generates Workflow constructor", () => {
|
|
63
|
+
const ir: TemplateIR = {
|
|
64
|
+
resources: [
|
|
65
|
+
{
|
|
66
|
+
logicalId: "workflow",
|
|
67
|
+
type: "GitLab::CI::Workflow",
|
|
68
|
+
properties: { name: "CI Pipeline" },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
parameters: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const files = generator.generate(ir);
|
|
75
|
+
expect(files[0].content).toContain("export const workflow = new Workflow(");
|
|
76
|
+
expect(files[0].content).toContain('"CI Pipeline"');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("wraps nested property types in constructors", () => {
|
|
80
|
+
const ir: TemplateIR = {
|
|
81
|
+
resources: [
|
|
82
|
+
{
|
|
83
|
+
logicalId: "testJob",
|
|
84
|
+
type: "GitLab::CI::Job",
|
|
85
|
+
properties: {
|
|
86
|
+
script: ["test"],
|
|
87
|
+
artifacts: { paths: ["dist/"], expireIn: "1 week" },
|
|
88
|
+
cache: { key: "node", paths: ["node_modules/"] },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
parameters: [],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const files = generator.generate(ir);
|
|
96
|
+
const content = files[0].content;
|
|
97
|
+
expect(content).toContain("new Artifacts(");
|
|
98
|
+
expect(content).toContain("new Cache(");
|
|
99
|
+
expect(content).toContain("Artifacts");
|
|
100
|
+
expect(content).toContain("Cache");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("wraps rules in Rule constructors", () => {
|
|
104
|
+
const ir: TemplateIR = {
|
|
105
|
+
resources: [
|
|
106
|
+
{
|
|
107
|
+
logicalId: "testJob",
|
|
108
|
+
type: "GitLab::CI::Job",
|
|
109
|
+
properties: {
|
|
110
|
+
script: ["test"],
|
|
111
|
+
rules: [{ if: "$CI_COMMIT_BRANCH", when: "always" }],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
parameters: [],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const files = generator.generate(ir);
|
|
119
|
+
const content = files[0].content;
|
|
120
|
+
expect(content).toContain("new Rule(");
|
|
121
|
+
expect(content).toContain("Rule");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("generates multiple resources", () => {
|
|
125
|
+
const ir: TemplateIR = {
|
|
126
|
+
resources: [
|
|
127
|
+
{ logicalId: "buildJob", type: "GitLab::CI::Job", properties: { stage: "build", script: ["build"] } },
|
|
128
|
+
{ logicalId: "testJob", type: "GitLab::CI::Job", properties: { stage: "test", script: ["test"] } },
|
|
129
|
+
],
|
|
130
|
+
parameters: [],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const files = generator.generate(ir);
|
|
134
|
+
const content = files[0].content;
|
|
135
|
+
expect(content).toContain("export const buildJob");
|
|
136
|
+
expect(content).toContain("export const testJob");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("includes stages comment from metadata", () => {
|
|
140
|
+
const ir: TemplateIR = {
|
|
141
|
+
resources: [
|
|
142
|
+
{ logicalId: "testJob", type: "GitLab::CI::Job", properties: { script: ["test"] } },
|
|
143
|
+
],
|
|
144
|
+
parameters: [],
|
|
145
|
+
metadata: { stages: ["build", "test", "deploy"] },
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const files = generator.generate(ir);
|
|
149
|
+
expect(files[0].content).toContain("Pipeline stages: build, test, deploy");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript code generator for GitLab CI import.
|
|
3
|
+
*
|
|
4
|
+
* Converts a TemplateIR (from parsed .gitlab-ci.yml) into TypeScript
|
|
5
|
+
* source code using the @intentius/chant-lexicon-gitlab constructors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TypeScriptGenerator, GeneratedFile } from "@intentius/chant/import/generator";
|
|
9
|
+
import type { TemplateIR, ResourceIR } from "@intentius/chant/import/parser";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Map GitLab CI entity types to their constructor names.
|
|
13
|
+
*/
|
|
14
|
+
const TYPE_TO_CLASS: Record<string, string> = {
|
|
15
|
+
"GitLab::CI::Job": "Job",
|
|
16
|
+
"GitLab::CI::Default": "Default",
|
|
17
|
+
"GitLab::CI::Workflow": "Workflow",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Properties that reference known property entities.
|
|
22
|
+
*/
|
|
23
|
+
const PROPERTY_CONSTRUCTORS: Record<string, string> = {
|
|
24
|
+
artifacts: "Artifacts",
|
|
25
|
+
cache: "Cache",
|
|
26
|
+
image: "Image",
|
|
27
|
+
retry: "Retry",
|
|
28
|
+
allowFailure: "AllowFailure",
|
|
29
|
+
parallel: "Parallel",
|
|
30
|
+
environment: "Environment",
|
|
31
|
+
trigger: "Trigger",
|
|
32
|
+
autoCancel: "AutoCancel",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate TypeScript source code from a GitLab CI IR.
|
|
37
|
+
*/
|
|
38
|
+
export class GitLabGenerator implements TypeScriptGenerator {
|
|
39
|
+
generate(ir: TemplateIR): GeneratedFile[] {
|
|
40
|
+
const lines: string[] = [];
|
|
41
|
+
|
|
42
|
+
// Collect which constructors are needed
|
|
43
|
+
const usedConstructors = new Set<string>();
|
|
44
|
+
for (const resource of ir.resources) {
|
|
45
|
+
const cls = TYPE_TO_CLASS[resource.type];
|
|
46
|
+
if (cls) usedConstructors.add(cls);
|
|
47
|
+
|
|
48
|
+
// Check properties for nested constructors
|
|
49
|
+
this.collectNestedConstructors(resource.properties, usedConstructors);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Import statement
|
|
53
|
+
const imports = [...usedConstructors].sort().join(", ");
|
|
54
|
+
lines.push(`import { ${imports} } from "@intentius/chant-lexicon-gitlab";`);
|
|
55
|
+
lines.push("");
|
|
56
|
+
|
|
57
|
+
// Emit stages if present in metadata
|
|
58
|
+
if (ir.metadata?.stages && Array.isArray(ir.metadata.stages)) {
|
|
59
|
+
lines.push(`// Pipeline stages: ${(ir.metadata.stages as string[]).join(", ")}`);
|
|
60
|
+
lines.push("");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Emit includes as comments
|
|
64
|
+
if (ir.metadata?.include) {
|
|
65
|
+
lines.push("// Imported includes (not converted):");
|
|
66
|
+
const includes = Array.isArray(ir.metadata.include) ? ir.metadata.include : [ir.metadata.include];
|
|
67
|
+
for (const inc of includes) {
|
|
68
|
+
if (typeof inc === "string") {
|
|
69
|
+
lines.push(`// - ${inc}`);
|
|
70
|
+
} else if (typeof inc === "object" && inc !== null) {
|
|
71
|
+
lines.push(`// - ${JSON.stringify(inc)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
lines.push("");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Emit resources
|
|
78
|
+
for (const resource of ir.resources) {
|
|
79
|
+
const cls = TYPE_TO_CLASS[resource.type];
|
|
80
|
+
if (!cls) continue;
|
|
81
|
+
|
|
82
|
+
const varName = resource.logicalId;
|
|
83
|
+
const propsStr = this.emitProps(resource.properties, 1);
|
|
84
|
+
|
|
85
|
+
lines.push(`export const ${varName} = new ${cls}(${propsStr});`);
|
|
86
|
+
lines.push("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [{ path: "main.ts", content: lines.join("\n") }];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private collectNestedConstructors(props: Record<string, unknown>, used: Set<string>): void {
|
|
93
|
+
for (const [key, value] of Object.entries(props)) {
|
|
94
|
+
const constructor = PROPERTY_CONSTRUCTORS[key];
|
|
95
|
+
if (constructor && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
96
|
+
used.add(constructor);
|
|
97
|
+
}
|
|
98
|
+
if (key === "rules" && Array.isArray(value)) {
|
|
99
|
+
used.add("Rule");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private emitProps(props: Record<string, unknown>, depth: number): string {
|
|
105
|
+
const indent = " ".repeat(depth);
|
|
106
|
+
const innerIndent = " ".repeat(depth + 1);
|
|
107
|
+
const entries: string[] = [];
|
|
108
|
+
|
|
109
|
+
for (const [key, value] of Object.entries(props)) {
|
|
110
|
+
if (value === undefined || value === null) continue;
|
|
111
|
+
const emitted = this.emitValue(key, value, depth + 1);
|
|
112
|
+
entries.push(`${innerIndent}${key}: ${emitted},`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (entries.length === 0) return "{}";
|
|
116
|
+
return `{\n${entries.join("\n")}\n${indent}}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private emitValue(key: string, value: unknown, depth: number): string {
|
|
120
|
+
if (value === null || value === undefined) return "undefined";
|
|
121
|
+
|
|
122
|
+
// Check if this key maps to a property constructor
|
|
123
|
+
const constructor = PROPERTY_CONSTRUCTORS[key];
|
|
124
|
+
if (constructor && typeof value === "object" && !Array.isArray(value)) {
|
|
125
|
+
const propsStr = this.emitProps(value as Record<string, unknown>, depth);
|
|
126
|
+
return `new ${constructor}(${propsStr})`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Rules array — wrap each item in Rule constructor
|
|
130
|
+
if (key === "rules" && Array.isArray(value)) {
|
|
131
|
+
const items = value.map((item) => {
|
|
132
|
+
if (typeof item === "object" && item !== null) {
|
|
133
|
+
const propsStr = this.emitProps(item as Record<string, unknown>, depth + 1);
|
|
134
|
+
return `new Rule(${propsStr})`;
|
|
135
|
+
}
|
|
136
|
+
return JSON.stringify(item);
|
|
137
|
+
});
|
|
138
|
+
const indent = " ".repeat(depth);
|
|
139
|
+
const innerIndent = " ".repeat(depth + 1);
|
|
140
|
+
return `[\n${items.map((i) => `${innerIndent}${i},`).join("\n")}\n${indent}]`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return this.emitLiteral(value, depth);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private emitLiteral(value: unknown, depth: number): string {
|
|
147
|
+
if (value === null || value === undefined) return "undefined";
|
|
148
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
149
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
150
|
+
|
|
151
|
+
if (Array.isArray(value)) {
|
|
152
|
+
if (value.length === 0) return "[]";
|
|
153
|
+
const items = value.map((item) => this.emitLiteral(item, depth + 1));
|
|
154
|
+
// Short arrays on one line
|
|
155
|
+
const oneLine = `[${items.join(", ")}]`;
|
|
156
|
+
if (oneLine.length < 80) return oneLine;
|
|
157
|
+
const indent = " ".repeat(depth);
|
|
158
|
+
const innerIndent = " ".repeat(depth + 1);
|
|
159
|
+
return `[\n${items.map((i) => `${innerIndent}${i},`).join("\n")}\n${indent}]`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof value === "object") {
|
|
163
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
164
|
+
if (entries.length === 0) return "{}";
|
|
165
|
+
const indent = " ".repeat(depth);
|
|
166
|
+
const innerIndent = " ".repeat(depth + 1);
|
|
167
|
+
const items = entries.map(([k, v]) => `${innerIndent}${k}: ${this.emitLiteral(v, depth + 1)},`);
|
|
168
|
+
return `{\n${items.join("\n")}\n${indent}}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return String(value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { GitLabParser } from "./parser";
|
|
3
|
+
|
|
4
|
+
const parser = new GitLabParser();
|
|
5
|
+
|
|
6
|
+
describe("GitLabParser", () => {
|
|
7
|
+
test("parses simple pipeline with one job", () => {
|
|
8
|
+
const yaml = `
|
|
9
|
+
stages:
|
|
10
|
+
- test
|
|
11
|
+
|
|
12
|
+
test-job:
|
|
13
|
+
stage: test
|
|
14
|
+
script:
|
|
15
|
+
- npm test
|
|
16
|
+
`;
|
|
17
|
+
const ir = parser.parse(yaml);
|
|
18
|
+
expect(ir.resources).toHaveLength(1);
|
|
19
|
+
expect(ir.resources[0].type).toBe("GitLab::CI::Job");
|
|
20
|
+
expect(ir.resources[0].properties.stage).toBe("test");
|
|
21
|
+
expect(ir.resources[0].properties.script).toEqual(["npm test"]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("parses multiple jobs", () => {
|
|
25
|
+
const yaml = `
|
|
26
|
+
stages:
|
|
27
|
+
- build
|
|
28
|
+
- test
|
|
29
|
+
|
|
30
|
+
build-job:
|
|
31
|
+
stage: build
|
|
32
|
+
script:
|
|
33
|
+
- make build
|
|
34
|
+
|
|
35
|
+
test-job:
|
|
36
|
+
stage: test
|
|
37
|
+
script:
|
|
38
|
+
- make test
|
|
39
|
+
`;
|
|
40
|
+
const ir = parser.parse(yaml);
|
|
41
|
+
const jobs = ir.resources.filter((r) => r.type === "GitLab::CI::Job");
|
|
42
|
+
expect(jobs).toHaveLength(2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("parses default settings", () => {
|
|
46
|
+
const yaml = `
|
|
47
|
+
default:
|
|
48
|
+
interruptible: true
|
|
49
|
+
timeout: 30 minutes
|
|
50
|
+
`;
|
|
51
|
+
const ir = parser.parse(yaml);
|
|
52
|
+
const defaults = ir.resources.find((r) => r.type === "GitLab::CI::Default");
|
|
53
|
+
expect(defaults).toBeDefined();
|
|
54
|
+
expect(defaults!.properties.interruptible).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("parses workflow", () => {
|
|
58
|
+
const yaml = `
|
|
59
|
+
workflow:
|
|
60
|
+
name: My Pipeline
|
|
61
|
+
`;
|
|
62
|
+
const ir = parser.parse(yaml);
|
|
63
|
+
const workflow = ir.resources.find((r) => r.type === "GitLab::CI::Workflow");
|
|
64
|
+
expect(workflow).toBeDefined();
|
|
65
|
+
expect(workflow!.properties.name).toBe("My Pipeline");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("converts snake_case keys to camelCase", () => {
|
|
69
|
+
const yaml = `
|
|
70
|
+
test-job:
|
|
71
|
+
stage: test
|
|
72
|
+
before_script:
|
|
73
|
+
- echo setup
|
|
74
|
+
after_script:
|
|
75
|
+
- echo done
|
|
76
|
+
script:
|
|
77
|
+
- npm test
|
|
78
|
+
`;
|
|
79
|
+
const ir = parser.parse(yaml);
|
|
80
|
+
expect(ir.resources[0].properties.beforeScript).toEqual(["echo setup"]);
|
|
81
|
+
expect(ir.resources[0].properties.afterScript).toEqual(["echo done"]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("converts kebab-case job names to camelCase", () => {
|
|
85
|
+
const yaml = `
|
|
86
|
+
my-test-job:
|
|
87
|
+
stage: test
|
|
88
|
+
script:
|
|
89
|
+
- test
|
|
90
|
+
`;
|
|
91
|
+
const ir = parser.parse(yaml);
|
|
92
|
+
expect(ir.resources[0].logicalId).toBe("myTestJob");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("preserves original name in metadata", () => {
|
|
96
|
+
const yaml = `
|
|
97
|
+
deploy-prod:
|
|
98
|
+
stage: deploy
|
|
99
|
+
script:
|
|
100
|
+
- deploy.sh
|
|
101
|
+
`;
|
|
102
|
+
const ir = parser.parse(yaml);
|
|
103
|
+
expect(ir.resources[0].metadata?.originalName).toBe("deploy-prod");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("records stages in metadata", () => {
|
|
107
|
+
const yaml = `
|
|
108
|
+
stages:
|
|
109
|
+
- build
|
|
110
|
+
- test
|
|
111
|
+
- deploy
|
|
112
|
+
|
|
113
|
+
test-job:
|
|
114
|
+
stage: test
|
|
115
|
+
script:
|
|
116
|
+
- npm test
|
|
117
|
+
`;
|
|
118
|
+
const ir = parser.parse(yaml);
|
|
119
|
+
expect(ir.metadata?.stages).toEqual(["build", "test", "deploy"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("skips reserved keys as jobs", () => {
|
|
123
|
+
const yaml = `
|
|
124
|
+
stages:
|
|
125
|
+
- test
|
|
126
|
+
variables:
|
|
127
|
+
NODE_ENV: production
|
|
128
|
+
include:
|
|
129
|
+
- local: shared.yml
|
|
130
|
+
|
|
131
|
+
test-job:
|
|
132
|
+
stage: test
|
|
133
|
+
script:
|
|
134
|
+
- npm test
|
|
135
|
+
`;
|
|
136
|
+
const ir = parser.parse(yaml);
|
|
137
|
+
// Only the job should be in resources (not stages, variables, include)
|
|
138
|
+
const jobs = ir.resources.filter((r) => r.type === "GitLab::CI::Job");
|
|
139
|
+
expect(jobs).toHaveLength(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("handles JSON input", () => {
|
|
143
|
+
const json = JSON.stringify({
|
|
144
|
+
stages: ["test"],
|
|
145
|
+
"test-job": { stage: "test", script: ["npm test"] },
|
|
146
|
+
});
|
|
147
|
+
const ir = parser.parse(json);
|
|
148
|
+
expect(ir.resources).toHaveLength(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("returns empty parameters (GitLab CI has no parameters)", () => {
|
|
152
|
+
const yaml = `
|
|
153
|
+
test-job:
|
|
154
|
+
script:
|
|
155
|
+
- test
|
|
156
|
+
`;
|
|
157
|
+
const ir = parser.parse(yaml);
|
|
158
|
+
expect(ir.parameters).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
});
|