@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.
Files changed (56) hide show
  1. package/package.json +27 -0
  2. package/src/codegen/__snapshots__/snapshot.test.ts.snap +33 -0
  3. package/src/codegen/docs-cli.ts +3 -0
  4. package/src/codegen/docs.ts +962 -0
  5. package/src/codegen/fetch.ts +73 -0
  6. package/src/codegen/generate-cli.ts +41 -0
  7. package/src/codegen/generate-lexicon.ts +53 -0
  8. package/src/codegen/generate-typescript.ts +144 -0
  9. package/src/codegen/generate.ts +166 -0
  10. package/src/codegen/naming.ts +52 -0
  11. package/src/codegen/package.ts +64 -0
  12. package/src/codegen/parse.test.ts +195 -0
  13. package/src/codegen/parse.ts +531 -0
  14. package/src/codegen/patches.test.ts +99 -0
  15. package/src/codegen/patches.ts +100 -0
  16. package/src/codegen/rollback.ts +26 -0
  17. package/src/codegen/snapshot.test.ts +109 -0
  18. package/src/coverage.test.ts +39 -0
  19. package/src/coverage.ts +52 -0
  20. package/src/generated/index.d.ts +248 -0
  21. package/src/generated/index.ts +23 -0
  22. package/src/generated/lexicon-gitlab.json +77 -0
  23. package/src/generated/runtime.ts +4 -0
  24. package/src/import/generator.test.ts +151 -0
  25. package/src/import/generator.ts +173 -0
  26. package/src/import/parser.test.ts +160 -0
  27. package/src/import/parser.ts +282 -0
  28. package/src/import/roundtrip.test.ts +89 -0
  29. package/src/index.ts +25 -0
  30. package/src/intrinsics.test.ts +42 -0
  31. package/src/intrinsics.ts +40 -0
  32. package/src/lint/post-synth/post-synth.test.ts +155 -0
  33. package/src/lint/post-synth/wgl010.ts +41 -0
  34. package/src/lint/post-synth/wgl011.ts +54 -0
  35. package/src/lint/post-synth/yaml-helpers.ts +88 -0
  36. package/src/lint/rules/artifact-no-expiry.ts +62 -0
  37. package/src/lint/rules/deprecated-only-except.ts +53 -0
  38. package/src/lint/rules/index.ts +8 -0
  39. package/src/lint/rules/missing-script.ts +65 -0
  40. package/src/lint/rules/missing-stage.ts +62 -0
  41. package/src/lint/rules/rules.test.ts +146 -0
  42. package/src/lsp/completions.test.ts +85 -0
  43. package/src/lsp/completions.ts +18 -0
  44. package/src/lsp/hover.test.ts +60 -0
  45. package/src/lsp/hover.ts +36 -0
  46. package/src/plugin.test.ts +228 -0
  47. package/src/plugin.ts +380 -0
  48. package/src/serializer.test.ts +309 -0
  49. package/src/serializer.ts +226 -0
  50. package/src/testdata/ci-schema-fixture.json +2184 -0
  51. package/src/testdata/create-fixture.ts +46 -0
  52. package/src/testdata/load-fixtures.ts +23 -0
  53. package/src/validate-cli.ts +19 -0
  54. package/src/validate.test.ts +43 -0
  55. package/src/validate.ts +125 -0
  56. package/src/variables.ts +27 -0
@@ -0,0 +1,60 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { gitlabHover } from "./hover";
3
+ import type { HoverContext } from "@intentius/chant/lsp/types";
4
+
5
+ function makeCtx(overrides: Partial<HoverContext>): HoverContext {
6
+ return {
7
+ uri: "file:///test.ts",
8
+ content: "",
9
+ position: { line: 0, character: 0 },
10
+ word: "",
11
+ lineText: "",
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ describe("gitlabHover", () => {
17
+ test("returns hover for Job class", () => {
18
+ const ctx = makeCtx({ word: "Job" });
19
+ const hover = gitlabHover(ctx);
20
+ expect(hover).toBeDefined();
21
+ expect(hover!.contents).toContain("**Job**");
22
+ expect(hover!.contents).toContain("GitLab::CI::Job");
23
+ expect(hover!.contents).toContain("Resource entity");
24
+ });
25
+
26
+ test("returns hover for property entity", () => {
27
+ const ctx = makeCtx({ word: "Cache" });
28
+ const hover = gitlabHover(ctx);
29
+ expect(hover).toBeDefined();
30
+ expect(hover!.contents).toContain("**Cache**");
31
+ expect(hover!.contents).toContain("GitLab::CI::Cache");
32
+ expect(hover!.contents).toContain("Property entity");
33
+ });
34
+
35
+ test("returns hover for Default", () => {
36
+ const ctx = makeCtx({ word: "Default" });
37
+ const hover = gitlabHover(ctx);
38
+ expect(hover).toBeDefined();
39
+ expect(hover!.contents).toContain("GitLab::CI::Default");
40
+ });
41
+
42
+ test("returns hover for Artifacts", () => {
43
+ const ctx = makeCtx({ word: "Artifacts" });
44
+ const hover = gitlabHover(ctx);
45
+ expect(hover).toBeDefined();
46
+ expect(hover!.contents).toContain("GitLab::CI::Artifacts");
47
+ });
48
+
49
+ test("returns undefined for unknown word", () => {
50
+ const ctx = makeCtx({ word: "UnknownEntity" });
51
+ const hover = gitlabHover(ctx);
52
+ expect(hover).toBeUndefined();
53
+ });
54
+
55
+ test("returns undefined for empty word", () => {
56
+ const ctx = makeCtx({ word: "" });
57
+ const hover = gitlabHover(ctx);
58
+ expect(hover).toBeUndefined();
59
+ });
60
+ });
@@ -0,0 +1,36 @@
1
+ import type { HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
2
+ import { LexiconIndex, lexiconHover, type LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
3
+
4
+ let cachedIndex: LexiconIndex | null = null;
5
+
6
+ function getIndex(): LexiconIndex {
7
+ if (cachedIndex) return cachedIndex;
8
+ const data = require("../generated/lexicon-gitlab.json") as Record<string, LexiconEntry>;
9
+ cachedIndex = new LexiconIndex(data);
10
+ return cachedIndex;
11
+ }
12
+
13
+ /**
14
+ * Provide hover information for GitLab CI entity types.
15
+ */
16
+ export function gitlabHover(ctx: HoverContext): HoverInfo | undefined {
17
+ return lexiconHover(ctx, getIndex(), resourceHover);
18
+ }
19
+
20
+ function resourceHover(className: string, entry: LexiconEntry): HoverInfo | undefined {
21
+ const lines: string[] = [];
22
+
23
+ lines.push(`**${className}**`);
24
+ lines.push("");
25
+ lines.push(`GitLab CI type: \`${entry.resourceType}\``);
26
+
27
+ if (entry.kind === "resource") {
28
+ lines.push("");
29
+ lines.push("*Resource entity — serialized as a top-level CI key*");
30
+ } else {
31
+ lines.push("");
32
+ lines.push("*Property entity — used as a nested value in resources*");
33
+ }
34
+
35
+ return { contents: lines.join("\n") };
36
+ }
@@ -0,0 +1,228 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { gitlabPlugin } from "./plugin";
3
+ import { isLexiconPlugin } from "@intentius/chant/lexicon";
4
+
5
+ describe("gitlabPlugin", () => {
6
+ // -----------------------------------------------------------------------
7
+ // Basic interface
8
+ // -----------------------------------------------------------------------
9
+
10
+ test("satisfies isLexiconPlugin type guard", () => {
11
+ expect(isLexiconPlugin(gitlabPlugin)).toBe(true);
12
+ });
13
+
14
+ test("has correct name and serializer", () => {
15
+ expect(gitlabPlugin.name).toBe("gitlab");
16
+ expect(gitlabPlugin.serializer.name).toBe("gitlab");
17
+ expect(gitlabPlugin.serializer.rulePrefix).toBe("WGL");
18
+ });
19
+
20
+ // -----------------------------------------------------------------------
21
+ // Intrinsics
22
+ // -----------------------------------------------------------------------
23
+
24
+ test("returns intrinsics", () => {
25
+ const intrinsics = gitlabPlugin.intrinsics!();
26
+ expect(intrinsics).toHaveLength(1);
27
+ expect(intrinsics[0].name).toBe("reference");
28
+ expect(intrinsics[0].outputKey).toBe("!reference");
29
+ expect(intrinsics[0].isTag).toBe(true);
30
+ });
31
+
32
+ // -----------------------------------------------------------------------
33
+ // Template detection
34
+ // -----------------------------------------------------------------------
35
+
36
+ test("detectTemplate returns true for stages array", () => {
37
+ expect(gitlabPlugin.detectTemplate!({ stages: ["build", "test"] })).toBe(true);
38
+ });
39
+
40
+ test("detectTemplate returns true for image + script", () => {
41
+ expect(gitlabPlugin.detectTemplate!({ image: "node:20", script: ["test"] })).toBe(true);
42
+ });
43
+
44
+ test("detectTemplate returns true for job-like entries", () => {
45
+ expect(
46
+ gitlabPlugin.detectTemplate!({
47
+ test_job: { stage: "test", script: ["npm test"] },
48
+ }),
49
+ ).toBe(true);
50
+ });
51
+
52
+ test("detectTemplate returns false for empty object", () => {
53
+ expect(gitlabPlugin.detectTemplate!({})).toBe(false);
54
+ });
55
+
56
+ test("detectTemplate returns false for non-object", () => {
57
+ expect(gitlabPlugin.detectTemplate!("string")).toBe(false);
58
+ expect(gitlabPlugin.detectTemplate!(null)).toBe(false);
59
+ expect(gitlabPlugin.detectTemplate!(42)).toBe(false);
60
+ });
61
+
62
+ test("detectTemplate returns false for unrelated object", () => {
63
+ expect(gitlabPlugin.detectTemplate!({ AWSTemplateFormatVersion: "2010-09-09" })).toBe(false);
64
+ });
65
+
66
+ // -----------------------------------------------------------------------
67
+ // Lint rules
68
+ // -----------------------------------------------------------------------
69
+
70
+ test("returns lint rules", () => {
71
+ const rules = gitlabPlugin.lintRules!();
72
+ expect(rules).toHaveLength(4);
73
+ const ids = rules.map((r) => r.id);
74
+ expect(ids).toContain("WGL001");
75
+ expect(ids).toContain("WGL002");
76
+ expect(ids).toContain("WGL003");
77
+ expect(ids).toContain("WGL004");
78
+ });
79
+
80
+ // -----------------------------------------------------------------------
81
+ // Post-synth checks
82
+ // -----------------------------------------------------------------------
83
+
84
+ test("returns post-synth checks", () => {
85
+ const checks = gitlabPlugin.postSynthChecks!();
86
+ expect(checks).toHaveLength(2);
87
+ const ids = checks.map((c) => c.id);
88
+ expect(ids).toContain("WGL010");
89
+ expect(ids).toContain("WGL011");
90
+ });
91
+
92
+ // -----------------------------------------------------------------------
93
+ // Init templates
94
+ // -----------------------------------------------------------------------
95
+
96
+ test("returns init templates", () => {
97
+ const templates = gitlabPlugin.initTemplates!();
98
+ expect(templates).toBeDefined();
99
+ expect(templates["_.ts"]).toBeDefined();
100
+ expect(templates["config.ts"]).toBeDefined();
101
+ expect(templates["test.ts"]).toBeDefined();
102
+ });
103
+
104
+ test("init templates import from gitlab lexicon", () => {
105
+ const templates = gitlabPlugin.initTemplates!();
106
+ expect(templates["config.ts"]).toContain("@intentius/chant-lexicon-gitlab");
107
+ expect(templates["test.ts"]).toContain("@intentius/chant-lexicon-gitlab");
108
+ });
109
+
110
+ // -----------------------------------------------------------------------
111
+ // LSP
112
+ // -----------------------------------------------------------------------
113
+
114
+ test("completionProvider returns results for new prefix", () => {
115
+ const items = gitlabPlugin.completionProvider!({
116
+ uri: "file:///a.ts",
117
+ content: "const j = new Job",
118
+ position: { line: 0, character: 17 },
119
+ wordAtCursor: "Job",
120
+ linePrefix: "const j = new Job",
121
+ });
122
+ expect(items.length).toBeGreaterThan(0);
123
+ const job = items.find((i) => i.label === "Job");
124
+ expect(job).toBeDefined();
125
+ expect(job!.kind).toBe("resource");
126
+ });
127
+
128
+ test("hoverProvider returns info for known entity", () => {
129
+ const hover = gitlabPlugin.hoverProvider!({
130
+ uri: "file:///a.ts",
131
+ content: "new Job({})",
132
+ position: { line: 0, character: 4 },
133
+ word: "Job",
134
+ lineText: "new Job({})",
135
+ });
136
+ expect(hover).toBeDefined();
137
+ expect(hover!.contents).toContain("Job");
138
+ });
139
+
140
+ // -----------------------------------------------------------------------
141
+ // Template import
142
+ // -----------------------------------------------------------------------
143
+
144
+ test("has templateParser method", () => {
145
+ expect(typeof gitlabPlugin.templateParser).toBe("function");
146
+ const parser = gitlabPlugin.templateParser!();
147
+ expect(typeof parser.parse).toBe("function");
148
+ });
149
+
150
+ test("has templateGenerator method", () => {
151
+ expect(typeof gitlabPlugin.templateGenerator).toBe("function");
152
+ const gen = gitlabPlugin.templateGenerator!();
153
+ expect(typeof gen.generate).toBe("function");
154
+ });
155
+
156
+ // -----------------------------------------------------------------------
157
+ // Skills
158
+ // -----------------------------------------------------------------------
159
+
160
+ test("returns skills", () => {
161
+ const skills = gitlabPlugin.skills!();
162
+ expect(skills).toHaveLength(1);
163
+ expect(skills[0].name).toBe("gitlab-ci");
164
+ expect(skills[0].description).toBeDefined();
165
+ expect(skills[0].content).toContain("GitLab CI/CD");
166
+ expect(skills[0].triggers).toHaveLength(2);
167
+ expect(skills[0].examples).toHaveLength(1);
168
+ });
169
+
170
+ // -----------------------------------------------------------------------
171
+ // MCP
172
+ // -----------------------------------------------------------------------
173
+
174
+ test("returns MCP tools", () => {
175
+ const tools = gitlabPlugin.mcpTools!();
176
+ expect(tools).toHaveLength(1);
177
+ expect(tools[0].name).toBe("diff");
178
+ expect(typeof tools[0].handler).toBe("function");
179
+ });
180
+
181
+ test("returns MCP resources", () => {
182
+ const resources = gitlabPlugin.mcpResources!();
183
+ expect(resources.length).toBeGreaterThan(0);
184
+ const uris = resources.map((r) => r.uri);
185
+ expect(uris).toContain("resource-catalog");
186
+ expect(uris).toContain("examples/basic-pipeline");
187
+ });
188
+
189
+ test("MCP resource-catalog handler returns JSON", async () => {
190
+ const resources = gitlabPlugin.mcpResources!();
191
+ const catalog = resources.find((r) => r.uri === "resource-catalog")!;
192
+ const result = await catalog.handler();
193
+ const parsed = JSON.parse(result);
194
+ expect(Array.isArray(parsed)).toBe(true);
195
+ expect(parsed.length).toBe(15);
196
+ const job = parsed.find((e: { className: string }) => e.className === "Job");
197
+ expect(job).toBeDefined();
198
+ expect(job.kind).toBe("resource");
199
+ });
200
+
201
+ // -----------------------------------------------------------------------
202
+ // Methods exist
203
+ // -----------------------------------------------------------------------
204
+
205
+ test("has generate method", () => {
206
+ expect(typeof gitlabPlugin.generate).toBe("function");
207
+ });
208
+
209
+ test("has validate method", () => {
210
+ expect(typeof gitlabPlugin.validate).toBe("function");
211
+ });
212
+
213
+ test("has package method", () => {
214
+ expect(typeof gitlabPlugin.package).toBe("function");
215
+ });
216
+
217
+ test("has coverage method", () => {
218
+ expect(typeof gitlabPlugin.coverage).toBe("function");
219
+ });
220
+
221
+ test("has rollback method", () => {
222
+ expect(typeof gitlabPlugin.rollback).toBe("function");
223
+ });
224
+
225
+ test("has docs method", () => {
226
+ expect(typeof gitlabPlugin.docs).toBe("function");
227
+ });
228
+ });