@intentius/chant-lexicon-gcp 0.0.15

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 (122) hide show
  1. package/dist/integrity.json +36 -0
  2. package/dist/manifest.json +12 -0
  3. package/dist/meta.json +10919 -0
  4. package/dist/rules/gcp-helpers.ts +117 -0
  5. package/dist/rules/hardcoded-project.ts +58 -0
  6. package/dist/rules/hardcoded-region.ts +56 -0
  7. package/dist/rules/public-iam.ts +43 -0
  8. package/dist/rules/wgc101.ts +56 -0
  9. package/dist/rules/wgc102.ts +35 -0
  10. package/dist/rules/wgc103.ts +45 -0
  11. package/dist/rules/wgc104.ts +42 -0
  12. package/dist/rules/wgc105.ts +46 -0
  13. package/dist/rules/wgc106.ts +36 -0
  14. package/dist/rules/wgc107.ts +39 -0
  15. package/dist/rules/wgc108.ts +41 -0
  16. package/dist/rules/wgc109.ts +39 -0
  17. package/dist/rules/wgc110.ts +38 -0
  18. package/dist/rules/wgc111.ts +54 -0
  19. package/dist/rules/wgc112.ts +56 -0
  20. package/dist/rules/wgc113.ts +42 -0
  21. package/dist/rules/wgc201.ts +36 -0
  22. package/dist/rules/wgc202.ts +39 -0
  23. package/dist/rules/wgc203.ts +44 -0
  24. package/dist/rules/wgc204.ts +39 -0
  25. package/dist/rules/wgc301.ts +34 -0
  26. package/dist/rules/wgc302.ts +34 -0
  27. package/dist/rules/wgc303.ts +37 -0
  28. package/dist/skills/chant-gcp-patterns.md +367 -0
  29. package/dist/skills/chant-gcp-security.md +276 -0
  30. package/dist/skills/chant-gcp.md +108 -0
  31. package/dist/types/index.d.ts +26529 -0
  32. package/package.json +35 -0
  33. package/src/actions/index.ts +52 -0
  34. package/src/codegen/docs-cli.ts +7 -0
  35. package/src/codegen/docs.ts +820 -0
  36. package/src/codegen/generate-cli.ts +24 -0
  37. package/src/codegen/generate.ts +252 -0
  38. package/src/codegen/naming.test.ts +49 -0
  39. package/src/codegen/naming.ts +132 -0
  40. package/src/codegen/package.ts +66 -0
  41. package/src/composites/cloud-function.ts +117 -0
  42. package/src/composites/cloud-run-service.ts +124 -0
  43. package/src/composites/cloud-sql-instance.ts +126 -0
  44. package/src/composites/composites.test.ts +432 -0
  45. package/src/composites/gcs-bucket.ts +111 -0
  46. package/src/composites/gke-cluster.ts +125 -0
  47. package/src/composites/index.ts +20 -0
  48. package/src/composites/managed-certificate.ts +79 -0
  49. package/src/composites/private-service.ts +95 -0
  50. package/src/composites/pubsub-pipeline.ts +102 -0
  51. package/src/composites/secure-project.ts +128 -0
  52. package/src/composites/vpc-network.ts +165 -0
  53. package/src/coverage.test.ts +27 -0
  54. package/src/coverage.ts +51 -0
  55. package/src/default-labels.test.ts +111 -0
  56. package/src/default-labels.ts +93 -0
  57. package/src/generated/index.d.ts +26529 -0
  58. package/src/generated/index.ts +1723 -0
  59. package/src/generated/lexicon-gcp.json +10919 -0
  60. package/src/generated/runtime.ts +4 -0
  61. package/src/import/generator.test.ts +125 -0
  62. package/src/import/generator.ts +82 -0
  63. package/src/import/parser.test.ts +167 -0
  64. package/src/import/parser.ts +80 -0
  65. package/src/import/roundtrip.test.ts +66 -0
  66. package/src/index.ts +54 -0
  67. package/src/lint/post-synth/gcp-helpers.ts +117 -0
  68. package/src/lint/post-synth/index.ts +20 -0
  69. package/src/lint/post-synth/post-synth.test.ts +693 -0
  70. package/src/lint/post-synth/wgc101.ts +56 -0
  71. package/src/lint/post-synth/wgc102.ts +35 -0
  72. package/src/lint/post-synth/wgc103.ts +45 -0
  73. package/src/lint/post-synth/wgc104.ts +42 -0
  74. package/src/lint/post-synth/wgc105.ts +46 -0
  75. package/src/lint/post-synth/wgc106.ts +36 -0
  76. package/src/lint/post-synth/wgc107.ts +39 -0
  77. package/src/lint/post-synth/wgc108.ts +41 -0
  78. package/src/lint/post-synth/wgc109.ts +39 -0
  79. package/src/lint/post-synth/wgc110.ts +38 -0
  80. package/src/lint/post-synth/wgc111.ts +54 -0
  81. package/src/lint/post-synth/wgc112.ts +56 -0
  82. package/src/lint/post-synth/wgc113.ts +42 -0
  83. package/src/lint/post-synth/wgc201.ts +36 -0
  84. package/src/lint/post-synth/wgc202.ts +39 -0
  85. package/src/lint/post-synth/wgc203.ts +44 -0
  86. package/src/lint/post-synth/wgc204.ts +39 -0
  87. package/src/lint/post-synth/wgc301.ts +34 -0
  88. package/src/lint/post-synth/wgc302.ts +34 -0
  89. package/src/lint/post-synth/wgc303.ts +37 -0
  90. package/src/lint/rules/hardcoded-project.ts +58 -0
  91. package/src/lint/rules/hardcoded-region.ts +56 -0
  92. package/src/lint/rules/index.ts +3 -0
  93. package/src/lint/rules/public-iam.ts +43 -0
  94. package/src/lint/rules/rules.test.ts +63 -0
  95. package/src/lsp/completions.test.ts +67 -0
  96. package/src/lsp/completions.ts +17 -0
  97. package/src/lsp/hover.test.ts +66 -0
  98. package/src/lsp/hover.ts +54 -0
  99. package/src/package-cli.ts +24 -0
  100. package/src/plugin.test.ts +250 -0
  101. package/src/plugin.ts +405 -0
  102. package/src/pseudo.test.ts +40 -0
  103. package/src/pseudo.ts +19 -0
  104. package/src/serializer.test.ts +250 -0
  105. package/src/serializer.ts +232 -0
  106. package/src/skills/chant-gcp-patterns.md +367 -0
  107. package/src/skills/chant-gcp-security.md +276 -0
  108. package/src/skills/chant-gcp.md +108 -0
  109. package/src/spec/fetch.test.ts +16 -0
  110. package/src/spec/fetch.ts +121 -0
  111. package/src/spec/parse.test.ts +163 -0
  112. package/src/spec/parse.ts +432 -0
  113. package/src/testdata/compute-instance.yaml +93 -0
  114. package/src/testdata/iam-policy-member.yaml +66 -0
  115. package/src/testdata/manifests/compute-instance.yaml +18 -0
  116. package/src/testdata/manifests/full-app.yaml +34 -0
  117. package/src/testdata/manifests/storage-bucket.yaml +12 -0
  118. package/src/testdata/storage-bucket.yaml +100 -0
  119. package/src/validate-cli.ts +13 -0
  120. package/src/validate.test.ts +38 -0
  121. package/src/validate.ts +30 -0
  122. package/src/variables.ts +15 -0
@@ -0,0 +1,250 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { gcpPlugin } from "./plugin";
3
+ import { isLexiconPlugin } from "@intentius/chant/lexicon";
4
+
5
+ describe("gcpPlugin", () => {
6
+ // ── Basic interface ────────────────────────────────────────────────
7
+
8
+ test("satisfies isLexiconPlugin type guard", () => {
9
+ expect(isLexiconPlugin(gcpPlugin)).toBe(true);
10
+ });
11
+
12
+ test("has correct name and serializer", () => {
13
+ expect(gcpPlugin.name).toBe("gcp");
14
+ expect(gcpPlugin.serializer.name).toBe("gcp");
15
+ expect(gcpPlugin.serializer.rulePrefix).toBe("WGC");
16
+ });
17
+
18
+ // ── Lint rules ─────────────────────────────────────────────────────
19
+
20
+ test("returns lint rules", () => {
21
+ const rules = gcpPlugin.lintRules!();
22
+ expect(rules).toHaveLength(3);
23
+ const ids = rules.map((r) => r.id);
24
+ expect(ids).toContain("WGC001");
25
+ expect(ids).toContain("WGC002");
26
+ expect(ids).toContain("WGC003");
27
+ });
28
+
29
+ // ── Post-synth checks ─────────────────────────────────────────────
30
+
31
+ test("returns post-synth checks", () => {
32
+ const checks = gcpPlugin.postSynthChecks!();
33
+ expect(checks).toHaveLength(20);
34
+ const ids = checks.map((c) => c.id);
35
+ expect(ids).toContain("WGC101");
36
+ expect(ids).toContain("WGC102");
37
+ expect(ids).toContain("WGC103");
38
+ expect(ids).toContain("WGC104");
39
+ expect(ids).toContain("WGC105");
40
+ expect(ids).toContain("WGC106");
41
+ expect(ids).toContain("WGC107");
42
+ expect(ids).toContain("WGC108");
43
+ expect(ids).toContain("WGC109");
44
+ expect(ids).toContain("WGC110");
45
+ expect(ids).toContain("WGC111");
46
+ expect(ids).toContain("WGC112");
47
+ expect(ids).toContain("WGC113");
48
+ expect(ids).toContain("WGC201");
49
+ expect(ids).toContain("WGC202");
50
+ expect(ids).toContain("WGC203");
51
+ expect(ids).toContain("WGC204");
52
+ expect(ids).toContain("WGC301");
53
+ expect(ids).toContain("WGC302");
54
+ expect(ids).toContain("WGC303");
55
+ });
56
+
57
+ // ── Intrinsics / pseudo-parameters ─────────────────────────────────
58
+
59
+ test("returns no intrinsics (Config Connector has none)", () => {
60
+ const intrinsics = gcpPlugin.intrinsics!();
61
+ expect(intrinsics).toHaveLength(0);
62
+ });
63
+
64
+ test("returns pseudo-parameters", () => {
65
+ const params = gcpPlugin.pseudoParameters!();
66
+ expect(params).toContain("GCP::ProjectId");
67
+ expect(params).toContain("GCP::Region");
68
+ expect(params).toContain("GCP::Zone");
69
+ });
70
+
71
+ // ── Template detection ─────────────────────────────────────────────
72
+
73
+ test("detects Config Connector templates", () => {
74
+ expect(
75
+ gcpPlugin.detectTemplate!({
76
+ apiVersion: "compute.cnrm.cloud.google.com/v1beta1",
77
+ kind: "ComputeInstance",
78
+ }),
79
+ ).toBe(true);
80
+
81
+ expect(gcpPlugin.detectTemplate!({})).toBe(false);
82
+ expect(gcpPlugin.detectTemplate!(null)).toBe(false);
83
+ });
84
+
85
+ // ── Template parsing / generation ──────────────────────────────────
86
+
87
+ test("returns a template parser", () => {
88
+ const parser = gcpPlugin.templateParser!();
89
+ expect(parser).toBeDefined();
90
+ expect(typeof parser.parse).toBe("function");
91
+ });
92
+
93
+ test("returns a template generator", () => {
94
+ const generator = gcpPlugin.templateGenerator!();
95
+ expect(generator).toBeDefined();
96
+ expect(typeof generator.generate).toBe("function");
97
+ });
98
+
99
+ // ── Init templates ─────────────────────────────────────────────────
100
+
101
+ test("returns default init template", () => {
102
+ const templates = gcpPlugin.initTemplates!();
103
+ expect(templates.src).toBeDefined();
104
+ expect(templates.src["infra.ts"]).toContain("StorageBucket");
105
+ });
106
+
107
+ test("returns GKE init template", () => {
108
+ const templates = gcpPlugin.initTemplates!("gke");
109
+ expect(templates.src).toBeDefined();
110
+ expect(templates.src["infra.ts"]).toContain("GKECluster");
111
+ });
112
+
113
+ // ── Skills ─────────────────────────────────────────────────────────
114
+
115
+ describe("skills", () => {
116
+ test("returns at least three skills", () => {
117
+ const skills = gcpPlugin.skills!();
118
+ expect(skills.length).toBeGreaterThanOrEqual(3);
119
+ });
120
+
121
+ test("chant-gcp skill has required fields", () => {
122
+ const skills = gcpPlugin.skills!();
123
+ const gcpSkill = skills.find((s) => s.name === "chant-gcp");
124
+ expect(gcpSkill).toBeDefined();
125
+ expect(gcpSkill!.description.length).toBeGreaterThan(0);
126
+ expect(gcpSkill!.content.length).toBeGreaterThan(0);
127
+ });
128
+
129
+ test("chant-gcp skill has triggers", () => {
130
+ const skills = gcpPlugin.skills!();
131
+ const gcpSkill = skills.find((s) => s.name === "chant-gcp")!;
132
+ expect(gcpSkill.triggers).toBeDefined();
133
+ expect(gcpSkill.triggers!.length).toBeGreaterThanOrEqual(1);
134
+
135
+ const filePatternTrigger = gcpSkill.triggers!.find((t) => t.type === "file-pattern");
136
+ expect(filePatternTrigger).toBeDefined();
137
+ expect(filePatternTrigger!.value).toContain("*.gcp.ts");
138
+
139
+ const contextTrigger = gcpSkill.triggers!.find((t) => t.type === "context");
140
+ expect(contextTrigger).toBeDefined();
141
+ expect(contextTrigger!.value).toBe("gcp");
142
+ });
143
+
144
+ test("chant-gcp skill has parameters", () => {
145
+ const skills = gcpPlugin.skills!();
146
+ const gcpSkill = skills.find((s) => s.name === "chant-gcp")!;
147
+ expect(gcpSkill.parameters).toBeDefined();
148
+ expect(gcpSkill.parameters!.length).toBeGreaterThanOrEqual(1);
149
+ });
150
+
151
+ test("chant-gcp skill has examples", () => {
152
+ const skills = gcpPlugin.skills!();
153
+ const gcpSkill = skills.find((s) => s.name === "chant-gcp")!;
154
+ expect(gcpSkill.examples).toBeDefined();
155
+ expect(gcpSkill.examples!.length).toBeGreaterThanOrEqual(1);
156
+ });
157
+
158
+ test("skill content is valid markdown with frontmatter", () => {
159
+ const skills = gcpPlugin.skills!();
160
+ const gcpSkill = skills.find((s) => s.name === "chant-gcp")!;
161
+ expect(gcpSkill.content).toContain("---");
162
+ expect(gcpSkill.content).toContain("skill: chant-gcp");
163
+ expect(gcpSkill.content).toContain("user-invocable: true");
164
+ expect(gcpSkill.content).toContain("chant build");
165
+ });
166
+
167
+ test("chant-gcp-security skill is present with content", () => {
168
+ const skills = gcpPlugin.skills!();
169
+ const securitySkill = skills.find((s) => s.name === "chant-gcp-security");
170
+ expect(securitySkill).toBeDefined();
171
+ expect(securitySkill!.content.length).toBeGreaterThan(0);
172
+ expect(securitySkill!.content).toContain("Security");
173
+ });
174
+
175
+ test("chant-gcp-patterns skill is present with content", () => {
176
+ const skills = gcpPlugin.skills!();
177
+ const patternsSkill = skills.find((s) => s.name === "chant-gcp-patterns");
178
+ expect(patternsSkill).toBeDefined();
179
+ expect(patternsSkill!.content.length).toBeGreaterThan(0);
180
+ expect(patternsSkill!.content).toContain("Composite");
181
+ });
182
+ });
183
+
184
+ // ── Lifecycle methods ──────────────────────────────────────────────
185
+
186
+ describe("lifecycle methods", () => {
187
+ test("generate is a function", () => {
188
+ expect(typeof gcpPlugin.generate).toBe("function");
189
+ });
190
+
191
+ test("validate is a function", () => {
192
+ expect(typeof gcpPlugin.validate).toBe("function");
193
+ });
194
+
195
+ test("coverage is a function", () => {
196
+ expect(typeof gcpPlugin.coverage).toBe("function");
197
+ });
198
+
199
+ test("package is a function", () => {
200
+ expect(typeof gcpPlugin.package).toBe("function");
201
+ });
202
+ });
203
+
204
+ // ── MCP ────────────────────────────────────────────────────────────
205
+
206
+ describe("mcpTools", () => {
207
+ test("returns at least one tool", () => {
208
+ const tools = gcpPlugin.mcpTools!();
209
+ expect(tools.length).toBeGreaterThanOrEqual(1);
210
+ });
211
+
212
+ test("diff tool has correct structure", () => {
213
+ const tools = gcpPlugin.mcpTools!();
214
+ const diffTool = tools.find((t) => t.name === "diff");
215
+ expect(diffTool).toBeDefined();
216
+ expect(diffTool!.description.length).toBeGreaterThan(0);
217
+ expect(diffTool!.inputSchema.type).toBe("object");
218
+ expect(typeof diffTool!.handler).toBe("function");
219
+ });
220
+ });
221
+
222
+ describe("mcpResources", () => {
223
+ test("returns at least one resource", () => {
224
+ const resources = gcpPlugin.mcpResources!();
225
+ expect(resources.length).toBeGreaterThanOrEqual(1);
226
+ });
227
+
228
+ test("resource-catalog has correct structure", () => {
229
+ const resources = gcpPlugin.mcpResources!();
230
+ const catalog = resources.find((r) => r.uri === "resource-catalog");
231
+ expect(catalog).toBeDefined();
232
+ expect(catalog!.mimeType).toBe("application/json");
233
+ expect(typeof catalog!.handler).toBe("function");
234
+ });
235
+ });
236
+
237
+ // ── LSP ────────────────────────────────────────────────────────────
238
+
239
+ describe("completionProvider", () => {
240
+ test("is defined", () => {
241
+ expect(gcpPlugin.completionProvider).toBeDefined();
242
+ });
243
+ });
244
+
245
+ describe("hoverProvider", () => {
246
+ test("is defined", () => {
247
+ expect(gcpPlugin.hoverProvider).toBeDefined();
248
+ });
249
+ });
250
+ });
package/src/plugin.ts ADDED
@@ -0,0 +1,405 @@
1
+ /**
2
+ * GCP Config Connector lexicon plugin.
3
+ *
4
+ * Provides serializer, lint rules, template detection,
5
+ * import parsing, and code generation for GCP Config Connector resources.
6
+ */
7
+
8
+ import { createRequire } from "module";
9
+ import type { LexiconPlugin, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
10
+ const require = createRequire(import.meta.url);
11
+ import type { LintRule } from "@intentius/chant/lint/rule";
12
+ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
13
+ import type { TemplateParser } from "@intentius/chant/import/parser";
14
+ import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
15
+ import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
16
+ import type { McpToolContribution, McpResourceContribution } from "@intentius/chant/mcp/types";
17
+ import { gcpSerializer } from "./serializer";
18
+
19
+ export const gcpPlugin: LexiconPlugin = {
20
+ name: "gcp",
21
+ serializer: gcpSerializer,
22
+
23
+ lintRules(): LintRule[] {
24
+ const { hardcodedProjectRule } = require("./lint/rules/hardcoded-project");
25
+ const { hardcodedRegionRule } = require("./lint/rules/hardcoded-region");
26
+ const { publicIamRule } = require("./lint/rules/public-iam");
27
+ return [hardcodedProjectRule, hardcodedRegionRule, publicIamRule];
28
+ },
29
+
30
+ postSynthChecks(): PostSynthCheck[] {
31
+ const { wgc101 } = require("./lint/post-synth/wgc101");
32
+ const { wgc102 } = require("./lint/post-synth/wgc102");
33
+ const { wgc103 } = require("./lint/post-synth/wgc103");
34
+ const { wgc104 } = require("./lint/post-synth/wgc104");
35
+ const { wgc105 } = require("./lint/post-synth/wgc105");
36
+ const { wgc106 } = require("./lint/post-synth/wgc106");
37
+ const { wgc107 } = require("./lint/post-synth/wgc107");
38
+ const { wgc108 } = require("./lint/post-synth/wgc108");
39
+ const { wgc109 } = require("./lint/post-synth/wgc109");
40
+ const { wgc110 } = require("./lint/post-synth/wgc110");
41
+ const { wgc201 } = require("./lint/post-synth/wgc201");
42
+ const { wgc202 } = require("./lint/post-synth/wgc202");
43
+ const { wgc203 } = require("./lint/post-synth/wgc203");
44
+ const { wgc204 } = require("./lint/post-synth/wgc204");
45
+ const { wgc301 } = require("./lint/post-synth/wgc301");
46
+ const { wgc302 } = require("./lint/post-synth/wgc302");
47
+ const { wgc303 } = require("./lint/post-synth/wgc303");
48
+ const { wgc111 } = require("./lint/post-synth/wgc111");
49
+ const { wgc112 } = require("./lint/post-synth/wgc112");
50
+ const { wgc113 } = require("./lint/post-synth/wgc113");
51
+ return [
52
+ wgc101, wgc102, wgc103, wgc104, wgc105, wgc106, wgc107, wgc108, wgc109, wgc110,
53
+ wgc111, wgc112, wgc113,
54
+ wgc201, wgc202, wgc203, wgc204,
55
+ wgc301, wgc302, wgc303,
56
+ ];
57
+ },
58
+
59
+ intrinsics() {
60
+ // Config Connector YAML has no intrinsic template functions
61
+ return [];
62
+ },
63
+
64
+ pseudoParameters(): string[] {
65
+ return [
66
+ "GCP::ProjectId",
67
+ "GCP::Region",
68
+ "GCP::Zone",
69
+ ];
70
+ },
71
+
72
+ initTemplates(template?: string): InitTemplateSet {
73
+ if (template === "gke") {
74
+ return {
75
+ src: {
76
+ "infra.ts": `/**
77
+ * GKE Cluster + Node Pool + Workload Identity
78
+ */
79
+
80
+ import { GKECluster, NodePool, GCPServiceAccount, IAMPolicyMember, GCP } from "@intentius/chant-lexicon-gcp";
81
+ import { defaultAnnotations } from "@intentius/chant-lexicon-gcp";
82
+
83
+ export const annotations = defaultAnnotations({
84
+ "cnrm.cloud.google.com/project-id": GCP.ProjectId,
85
+ });
86
+
87
+ export const cluster = new GKECluster({
88
+ location: GCP.Region,
89
+ initialNodeCount: 1,
90
+ removeDefaultNodePool: true,
91
+ releaseChannel: { channel: "REGULAR" },
92
+ workloadIdentityConfig: {
93
+ workloadPool: "PROJECT_ID.svc.id.goog",
94
+ },
95
+ });
96
+
97
+ export const nodePool = new NodePool({
98
+ clusterRef: { name: cluster },
99
+ initialNodeCount: 2,
100
+ autoscaling: { minNodeCount: 1, maxNodeCount: 10 },
101
+ nodeConfig: {
102
+ machineType: "e2-medium",
103
+ diskSizeGb: 100,
104
+ oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"],
105
+ workloadMetadataConfig: { mode: "GKE_METADATA" },
106
+ },
107
+ management: { autoRepair: true, autoUpgrade: true },
108
+ });
109
+ `,
110
+ },
111
+ };
112
+ }
113
+
114
+ // Default: StorageBucket + IAMPolicyMember
115
+ return {
116
+ src: {
117
+ "infra.ts": `/**
118
+ * GCS Bucket with encryption and IAM binding
119
+ */
120
+
121
+ import { StorageBucket, IAMPolicyMember, GCP } from "@intentius/chant-lexicon-gcp";
122
+ import { defaultAnnotations } from "@intentius/chant-lexicon-gcp";
123
+
124
+ export const annotations = defaultAnnotations({
125
+ "cnrm.cloud.google.com/project-id": GCP.ProjectId,
126
+ });
127
+
128
+ export const dataBucket = new StorageBucket({
129
+ location: "US",
130
+ storageClass: "STANDARD",
131
+ uniformBucketLevelAccess: true,
132
+ versioning: { enabled: true },
133
+ });
134
+
135
+ export const bucketReader = new IAMPolicyMember({
136
+ member: "serviceAccount:my-app@PROJECT_ID.iam.gserviceaccount.com",
137
+ role: "roles/storage.objectViewer",
138
+ resourceRef: {
139
+ apiVersion: "storage.cnrm.cloud.google.com/v1beta1",
140
+ kind: "StorageBucket",
141
+ name: dataBucket,
142
+ },
143
+ });
144
+ `,
145
+ },
146
+ };
147
+ },
148
+
149
+ detectTemplate(data: unknown): boolean {
150
+ if (typeof data !== "object" || data === null) return false;
151
+
152
+ // Handle parsed YAML objects
153
+ const obj = data as Record<string, unknown>;
154
+ const apiVersion = obj.apiVersion;
155
+ if (typeof apiVersion === "string" && apiVersion.includes("cnrm.cloud.google.com")) {
156
+ return true;
157
+ }
158
+
159
+ // Handle string input
160
+ if (typeof data === "string") {
161
+ return data.includes("cnrm.cloud.google.com");
162
+ }
163
+
164
+ return false;
165
+ },
166
+
167
+ templateParser(): TemplateParser {
168
+ const { GcpParser } = require("./import/parser");
169
+ return new GcpParser();
170
+ },
171
+
172
+ templateGenerator(): TypeScriptGenerator {
173
+ const { GcpGenerator } = require("./import/generator");
174
+ return new GcpGenerator();
175
+ },
176
+
177
+ completionProvider(ctx: CompletionContext): CompletionItem[] {
178
+ const { gcpCompletions } = require("./lsp/completions");
179
+ return gcpCompletions(ctx);
180
+ },
181
+
182
+ hoverProvider(ctx: HoverContext): HoverInfo | undefined {
183
+ const { gcpHover } = require("./lsp/hover");
184
+ return gcpHover(ctx);
185
+ },
186
+
187
+ mcpTools(): McpToolContribution[] {
188
+ return [
189
+ {
190
+ name: "diff",
191
+ description: "Compare current build output against previous output for GCP Config Connector manifests",
192
+ inputSchema: {
193
+ type: "object" as const,
194
+ properties: {
195
+ path: {
196
+ type: "string",
197
+ description: "Path to the infrastructure project directory",
198
+ },
199
+ },
200
+ required: ["path"],
201
+ },
202
+ async handler(params: Record<string, unknown>): Promise<unknown> {
203
+ const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
204
+ const result = await diffCommand({
205
+ path: (params.path as string) ?? ".",
206
+ serializers: [gcpSerializer],
207
+ });
208
+ return result;
209
+ },
210
+ },
211
+ ];
212
+ },
213
+
214
+ mcpResources(): McpResourceContribution[] {
215
+ return [
216
+ {
217
+ uri: "resource-catalog",
218
+ name: "GCP Config Connector Resource Catalog",
219
+ description: "JSON list of all supported GCP Config Connector resource types",
220
+ mimeType: "application/json",
221
+ async handler(): Promise<string> {
222
+ const lexicon = require("./generated/lexicon-gcp.json") as Record<string, { resourceType: string; kind: string }>;
223
+ const entries = Object.entries(lexicon).map(([className, entry]) => ({
224
+ className,
225
+ resourceType: entry.resourceType,
226
+ kind: entry.kind,
227
+ }));
228
+ return JSON.stringify(entries);
229
+ },
230
+ },
231
+ {
232
+ uri: "examples/basic-bucket",
233
+ name: "Basic GCS Bucket Example",
234
+ description: "A GCS bucket with encryption and IAM binding",
235
+ mimeType: "text/typescript",
236
+ async handler(): Promise<string> {
237
+ return `import { StorageBucket, IAMPolicyMember, GCP } from "@intentius/chant-lexicon-gcp";
238
+ import { defaultAnnotations } from "@intentius/chant-lexicon-gcp";
239
+
240
+ export const annotations = defaultAnnotations({
241
+ "cnrm.cloud.google.com/project-id": GCP.ProjectId,
242
+ });
243
+
244
+ export const bucket = new StorageBucket({
245
+ location: "US",
246
+ storageClass: "STANDARD",
247
+ uniformBucketLevelAccess: true,
248
+ versioning: { enabled: true },
249
+ });
250
+ `;
251
+ },
252
+ },
253
+ ];
254
+ },
255
+
256
+ skills(): SkillDefinition[] {
257
+ const { readFileSync } = require("fs");
258
+ const { join, dirname } = require("path");
259
+ const { fileURLToPath } = require("url");
260
+ const dir = dirname(fileURLToPath(import.meta.url));
261
+
262
+ const skills: SkillDefinition[] = [];
263
+
264
+ const skillFiles = [
265
+ {
266
+ file: "chant-gcp.md",
267
+ name: "chant-gcp",
268
+ description: "GCP Config Connector lifecycle — build, lint, apply, and troubleshoot from a chant project",
269
+ triggers: [
270
+ { type: "file-pattern" as const, value: "*.gcp.ts" },
271
+ { type: "context" as const, value: "gcp" },
272
+ ],
273
+ parameters: [
274
+ {
275
+ name: "resourceType",
276
+ type: "string",
277
+ description: "GCP Config Connector resource type to work with",
278
+ },
279
+ ],
280
+ examples: [
281
+ {
282
+ title: "Create a Storage Bucket",
283
+ output: "new StorageBucket({ location: \"US\", storageClass: \"STANDARD\" })",
284
+ },
285
+ {
286
+ title: "Create a GKE Cluster",
287
+ output: "new GKECluster({ location: GCP.Region, releaseChannel: { channel: \"REGULAR\" } })",
288
+ },
289
+ ],
290
+ },
291
+ {
292
+ file: "chant-gcp-security.md",
293
+ name: "chant-gcp-security",
294
+ description: "GCP security best practices for infrastructure",
295
+ triggers: [
296
+ { type: "context" as const, value: "gcp security" },
297
+ { type: "context" as const, value: "gcp iam" },
298
+ ],
299
+ parameters: [],
300
+ examples: [
301
+ {
302
+ title: "Secure Storage Bucket",
303
+ input: "Create a storage bucket with encryption and uniform access",
304
+ output: "import { GcsBucket } from \"@intentius/chant-lexicon-gcp\";\n\nconst { bucket } = GcsBucket({ name: \"my-bucket\", kmsKeyName: \"...\" });",
305
+ },
306
+ ],
307
+ },
308
+ {
309
+ file: "chant-gcp-patterns.md",
310
+ name: "chant-gcp-patterns",
311
+ description: "Advanced GCP Config Connector patterns",
312
+ triggers: [
313
+ { type: "context" as const, value: "gcp patterns" },
314
+ { type: "context" as const, value: "gcp composites" },
315
+ ],
316
+ parameters: [],
317
+ examples: [
318
+ {
319
+ title: "VPC with Subnets",
320
+ input: "Create a VPC network with private subnets",
321
+ output: "import { VpcNetwork } from \"@intentius/chant-lexicon-gcp\";\n\nconst { network, subnets } = VpcNetwork({ name: \"my-vpc\", subnets: [...] });",
322
+ },
323
+ ],
324
+ },
325
+ ];
326
+
327
+ for (const skill of skillFiles) {
328
+ try {
329
+ const content = readFileSync(join(dir, "skills", skill.file), "utf-8");
330
+ skills.push({
331
+ name: skill.name,
332
+ description: skill.description,
333
+ content,
334
+ triggers: skill.triggers,
335
+ parameters: skill.parameters,
336
+ examples: skill.examples,
337
+ });
338
+ } catch { /* skip missing skills */ }
339
+ }
340
+
341
+ return skills;
342
+ },
343
+
344
+ async generate(options?: { verbose?: boolean }): Promise<void> {
345
+ const { generate, writeGeneratedFiles } = await import("./codegen/generate");
346
+ const { dirname } = await import("path");
347
+ const { fileURLToPath } = await import("url");
348
+
349
+ const result = await generate({ verbose: options?.verbose ?? true });
350
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
351
+ writeGeneratedFiles(result, pkgDir);
352
+
353
+ console.error(`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`);
354
+ if (result.warnings.length > 0) {
355
+ console.error(`${result.warnings.length} warnings`);
356
+ }
357
+ },
358
+
359
+ async validate(options?: { verbose?: boolean }): Promise<void> {
360
+ const { validate } = await import("./validate");
361
+ const { printValidationResult } = await import("@intentius/chant/codegen/validate");
362
+ const result = await validate();
363
+ printValidationResult(result);
364
+ },
365
+
366
+ async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
367
+ const { analyzeGcpCoverage } = await import("./coverage");
368
+ await analyzeGcpCoverage({
369
+ verbose: options?.verbose,
370
+ minOverall: options?.minOverall,
371
+ });
372
+ },
373
+
374
+ async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
375
+ const { packageLexicon } = await import("./codegen/package");
376
+ const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
377
+ const { join, dirname } = await import("path");
378
+ const { fileURLToPath } = await import("url");
379
+
380
+ const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
381
+
382
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
383
+ const distDir = join(pkgDir, "dist");
384
+ writeBundleSpec(spec, distDir);
385
+
386
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
387
+
388
+ const { getRuntime } = await import("@intentius/chant/runtime-adapter");
389
+ const rt = getRuntime();
390
+ const { stdout: packOut, stderr: packErr, exitCode: packExit } = await rt.spawn(
391
+ rt.commands.packCmd,
392
+ { cwd: pkgDir },
393
+ );
394
+ if (packExit === 0) {
395
+ console.error(`Tarball: ${packOut.trim()}`);
396
+ } else {
397
+ console.error(`${rt.commands.packCmd.join(" ")} failed: ${packErr}`);
398
+ }
399
+ },
400
+
401
+ async docs(options?: { verbose?: boolean }): Promise<void> {
402
+ const { generateDocs } = await import("./codegen/docs");
403
+ await generateDocs(options);
404
+ },
405
+ };
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { GCP, ProjectId, Region, Zone, PseudoParameter } from "./pseudo";
3
+
4
+ describe("GCP pseudo-parameters", () => {
5
+ it("ProjectId toJSON returns Ref object", () => {
6
+ expect(ProjectId.toJSON()).toEqual({ Ref: "GCP::ProjectId" });
7
+ });
8
+
9
+ it("Region toJSON returns Ref object", () => {
10
+ expect(Region.toJSON()).toEqual({ Ref: "GCP::Region" });
11
+ });
12
+
13
+ it("Zone toJSON returns Ref object", () => {
14
+ expect(Zone.toJSON()).toEqual({ Ref: "GCP::Zone" });
15
+ });
16
+
17
+ it("ProjectId toString returns interpolation syntax", () => {
18
+ expect(ProjectId.toString()).toBe("${GCP::ProjectId}");
19
+ });
20
+
21
+ it("Region toString returns interpolation syntax", () => {
22
+ expect(Region.toString()).toBe("${GCP::Region}");
23
+ });
24
+
25
+ it("Zone toString returns interpolation syntax", () => {
26
+ expect(Zone.toString()).toBe("${GCP::Zone}");
27
+ });
28
+
29
+ it("all pseudo-parameters are instances of PseudoParameter", () => {
30
+ expect(ProjectId).toBeInstanceOf(PseudoParameter);
31
+ expect(Region).toBeInstanceOf(PseudoParameter);
32
+ expect(Zone).toBeInstanceOf(PseudoParameter);
33
+ });
34
+
35
+ it("GCP namespace contains all pseudo-parameters", () => {
36
+ expect(GCP.ProjectId).toBe(ProjectId);
37
+ expect(GCP.Region).toBe(Region);
38
+ expect(GCP.Zone).toBe(Zone);
39
+ });
40
+ });