@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,4 @@
1
+ /**
2
+ * Runtime factory constructors — re-exported from core.
3
+ */
4
+ export { createResource, createProperty } from "@intentius/chant/runtime";
@@ -0,0 +1,125 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { GcpGenerator } from "./generator";
3
+
4
+ const generator = new GcpGenerator();
5
+
6
+ function makeIR(resources: any[]) {
7
+ return { resources, parameters: [], outputs: [] };
8
+ }
9
+
10
+ describe("GcpGenerator", () => {
11
+ test("generates valid TypeScript from IR", () => {
12
+ const ir = makeIR([
13
+ {
14
+ logicalName: "my-bucket",
15
+ type: "GCP::Storage::Bucket",
16
+ properties: {
17
+ metadata: { name: "my-bucket" },
18
+ location: "US",
19
+ },
20
+ },
21
+ ]);
22
+ const result = generator.generate(ir);
23
+ expect(result).toContain("import");
24
+ expect(result).toContain("Bucket");
25
+ });
26
+
27
+ test("correct import source (@intentius/chant-lexicon-gcp)", () => {
28
+ const ir = makeIR([
29
+ {
30
+ logicalName: "my-bucket",
31
+ type: "GCP::Storage::Bucket",
32
+ properties: { location: "US" },
33
+ },
34
+ ]);
35
+ const result = generator.generate(ir);
36
+ expect(result).toContain('from "@intentius/chant-lexicon-gcp"');
37
+ });
38
+
39
+ test("multiple resources produce multiple exports", () => {
40
+ const ir = makeIR([
41
+ {
42
+ logicalName: "my-bucket",
43
+ type: "GCP::Storage::Bucket",
44
+ properties: { location: "US" },
45
+ },
46
+ {
47
+ logicalName: "my-vm",
48
+ type: "GCP::Compute::Instance",
49
+ properties: { machineType: "e2-medium" },
50
+ },
51
+ ]);
52
+ const result = generator.generate(ir);
53
+ expect(result).toContain("export const myBucket");
54
+ expect(result).toContain("export const myVm");
55
+ });
56
+
57
+ test("camelCase variable names from kebab-case logical names", () => {
58
+ const ir = makeIR([
59
+ {
60
+ logicalName: "my-data-bucket",
61
+ type: "GCP::Storage::Bucket",
62
+ properties: { location: "US" },
63
+ },
64
+ ]);
65
+ const result = generator.generate(ir);
66
+ expect(result).toContain("export const myDataBucket");
67
+ });
68
+
69
+ test("empty IR produces minimal output", () => {
70
+ const ir = makeIR([]);
71
+ const result = generator.generate(ir);
72
+ // Should still produce valid TypeScript, even if no resources
73
+ expect(typeof result).toBe("string");
74
+ });
75
+
76
+ test("nested object formatting", () => {
77
+ const ir = makeIR([
78
+ {
79
+ logicalName: "my-bucket",
80
+ type: "GCP::Storage::Bucket",
81
+ properties: {
82
+ location: "US",
83
+ versioning: { enabled: true },
84
+ },
85
+ },
86
+ ]);
87
+ const result = generator.generate(ir);
88
+ expect(result).toContain("versioning:");
89
+ expect(result).toContain("enabled: true");
90
+ });
91
+
92
+ test("uses new Constructor() syntax", () => {
93
+ const ir = makeIR([
94
+ {
95
+ logicalName: "my-bucket",
96
+ type: "GCP::Storage::Bucket",
97
+ properties: { location: "US" },
98
+ },
99
+ ]);
100
+ const result = generator.generate(ir);
101
+ expect(result).toContain("new Bucket(");
102
+ });
103
+
104
+ test("sorts imports alphabetically", () => {
105
+ const ir = makeIR([
106
+ {
107
+ logicalName: "vm",
108
+ type: "GCP::Compute::Instance",
109
+ properties: { machineType: "e2-medium" },
110
+ },
111
+ {
112
+ logicalName: "bucket",
113
+ type: "GCP::Storage::Bucket",
114
+ properties: { location: "US" },
115
+ },
116
+ ]);
117
+ const result = generator.generate(ir);
118
+ const importLine = result.split("\n").find((l: string) => l.startsWith("import"));
119
+ expect(importLine).toBeDefined();
120
+ // Bucket should come before Instance alphabetically
121
+ const bucketIdx = importLine!.indexOf("Bucket");
122
+ const instanceIdx = importLine!.indexOf("Instance");
123
+ expect(bucketIdx).toBeLessThan(instanceIdx);
124
+ });
125
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Config Connector TypeScript generator.
3
+ *
4
+ * Converts import IR from the parser into typed chant TypeScript code.
5
+ */
6
+
7
+ import type { TypeScriptGenerator, TemplateIR } from "@intentius/chant/import/generator";
8
+
9
+ export class GcpGenerator implements TypeScriptGenerator {
10
+ generate(ir: TemplateIR): string {
11
+ const lines: string[] = [];
12
+ const imports = new Set<string>();
13
+
14
+ // Collect imports
15
+ for (const resource of ir.resources) {
16
+ const parts = resource.type.split("::");
17
+ if (parts.length >= 3) {
18
+ // Use the short name for the import
19
+ imports.add(parts[2]);
20
+ }
21
+ }
22
+
23
+ if (imports.size > 0) {
24
+ lines.push(
25
+ `import { ${[...imports].sort().join(", ")} } from "@intentius/chant-lexicon-gcp";`,
26
+ );
27
+ lines.push("");
28
+ }
29
+
30
+ // Generate resource declarations
31
+ for (const resource of ir.resources) {
32
+ const parts = resource.type.split("::");
33
+ const className = parts.length >= 3 ? parts[2] : resource.type;
34
+ const varName = camelCase(resource.logicalName);
35
+
36
+ lines.push(`export const ${varName} = new ${className}(${formatProps(resource.properties, 0)});`);
37
+ lines.push("");
38
+ }
39
+
40
+ return lines.join("\n");
41
+ }
42
+ }
43
+
44
+ function camelCase(str: string): string {
45
+ return str
46
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
47
+ .replace(/^(.)/, (_, c) => c.toLowerCase());
48
+ }
49
+
50
+ function formatProps(props: Record<string, unknown>, indent: number): string {
51
+ const entries = Object.entries(props);
52
+ if (entries.length === 0) return "{}";
53
+
54
+ const pad = " ".repeat(indent + 1);
55
+ const closePad = " ".repeat(indent);
56
+
57
+ const lines = entries.map(([key, value]) => {
58
+ return `${pad}${key}: ${formatValue(value, indent + 1)},`;
59
+ });
60
+
61
+ return `{\n${lines.join("\n")}\n${closePad}}`;
62
+ }
63
+
64
+ function formatValue(value: unknown, indent: number): string {
65
+ if (value === null || value === undefined) return "undefined";
66
+ if (typeof value === "string") return JSON.stringify(value);
67
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
68
+
69
+ if (Array.isArray(value)) {
70
+ if (value.length === 0) return "[]";
71
+ const pad = " ".repeat(indent + 1);
72
+ const closePad = " ".repeat(indent);
73
+ const items = value.map((v) => `${pad}${formatValue(v, indent + 1)},`);
74
+ return `[\n${items.join("\n")}\n${closePad}]`;
75
+ }
76
+
77
+ if (typeof value === "object") {
78
+ return formatProps(value as Record<string, unknown>, indent);
79
+ }
80
+
81
+ return String(value);
82
+ }
@@ -0,0 +1,167 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { GcpParser } from "./parser";
3
+
4
+ const parser = new GcpParser();
5
+
6
+ describe("GcpParser", () => {
7
+ test("empty YAML returns empty resources", () => {
8
+ const ir = parser.parse("");
9
+ expect(ir.resources).toEqual([]);
10
+ expect(ir.parameters).toEqual([]);
11
+ });
12
+
13
+ test("single ComputeInstance parses correctly", () => {
14
+ const yaml = `
15
+ apiVersion: compute.cnrm.cloud.google.com/v1beta1
16
+ kind: ComputeInstance
17
+ metadata:
18
+ name: my-vm
19
+ spec:
20
+ machineType: e2-medium
21
+ zone: us-central1-a
22
+ `;
23
+ const ir = parser.parse(yaml);
24
+ expect(ir.resources.length).toBe(1);
25
+ const r = ir.resources[0];
26
+ expect(r.type).toBe("GCP::Compute::Instance");
27
+ expect(r.properties.machineType).toBe("e2-medium");
28
+ expect(r.properties.zone).toBe("us-central1-a");
29
+ });
30
+
31
+ test("multi-doc YAML produces multiple resources", () => {
32
+ const yaml = `
33
+ apiVersion: storage.cnrm.cloud.google.com/v1beta1
34
+ kind: StorageBucket
35
+ metadata:
36
+ name: bucket
37
+ spec:
38
+ location: US
39
+ ---
40
+ apiVersion: compute.cnrm.cloud.google.com/v1beta1
41
+ kind: ComputeInstance
42
+ metadata:
43
+ name: vm
44
+ spec:
45
+ machineType: e2-medium
46
+ `;
47
+ const ir = parser.parse(yaml);
48
+ expect(ir.resources.length).toBe(2);
49
+ });
50
+
51
+ test("apiVersion+kind maps to GCP type name", () => {
52
+ const yaml = `
53
+ apiVersion: storage.cnrm.cloud.google.com/v1beta1
54
+ kind: StorageBucket
55
+ metadata:
56
+ name: test
57
+ spec:
58
+ location: US
59
+ `;
60
+ const ir = parser.parse(yaml);
61
+ expect(ir.resources[0].type).toBe("GCP::Storage::Bucket");
62
+ });
63
+
64
+ test("IAM resource maps correctly", () => {
65
+ const yaml = `
66
+ apiVersion: iam.cnrm.cloud.google.com/v1beta1
67
+ kind: IAMPolicyMember
68
+ metadata:
69
+ name: binding
70
+ spec:
71
+ member: user:test@example.com
72
+ role: roles/viewer
73
+ `;
74
+ const ir = parser.parse(yaml);
75
+ expect(ir.resources[0].type).toBe("GCP::Iam::PolicyMember");
76
+ });
77
+
78
+ test("non-Config Connector resources ignored", () => {
79
+ const yaml = `
80
+ apiVersion: apps/v1
81
+ kind: Deployment
82
+ metadata:
83
+ name: my-app
84
+ spec:
85
+ replicas: 1
86
+ `;
87
+ const ir = parser.parse(yaml);
88
+ expect(ir.resources).toEqual([]);
89
+ });
90
+
91
+ test("metadata.name extracted as logicalName", () => {
92
+ const yaml = `
93
+ apiVersion: compute.cnrm.cloud.google.com/v1beta1
94
+ kind: ComputeNetwork
95
+ metadata:
96
+ name: my-network
97
+ spec:
98
+ autoCreateSubnetworks: false
99
+ `;
100
+ const ir = parser.parse(yaml);
101
+ expect((ir.resources[0] as any).logicalName).toBe("my-network");
102
+ });
103
+
104
+ test("properties include metadata and spec fields, exclude apiVersion/kind", () => {
105
+ const yaml = `
106
+ apiVersion: storage.cnrm.cloud.google.com/v1beta1
107
+ kind: StorageBucket
108
+ metadata:
109
+ name: test
110
+ annotations:
111
+ cnrm.cloud.google.com/project-id: my-project
112
+ spec:
113
+ location: US
114
+ storageClass: STANDARD
115
+ `;
116
+ const ir = parser.parse(yaml);
117
+ const props = ir.resources[0].properties;
118
+ expect((props as any).apiVersion).toBeUndefined();
119
+ expect((props as any).kind).toBeUndefined();
120
+ expect(props.metadata).toBeDefined();
121
+ expect(props.location).toBe("US");
122
+ expect(props.storageClass).toBe("STANDARD");
123
+ });
124
+
125
+ test("parameters are always empty (GCP has no template parameters)", () => {
126
+ const yaml = `
127
+ apiVersion: compute.cnrm.cloud.google.com/v1beta1
128
+ kind: ComputeInstance
129
+ metadata:
130
+ name: test
131
+ spec:
132
+ machineType: e2-medium
133
+ `;
134
+ const ir = parser.parse(yaml);
135
+ expect(ir.parameters).toEqual([]);
136
+ });
137
+
138
+ test("mixed Config Connector and non-CC resources filters correctly", () => {
139
+ const yaml = `
140
+ apiVersion: apps/v1
141
+ kind: Deployment
142
+ metadata:
143
+ name: app
144
+ spec:
145
+ replicas: 1
146
+ ---
147
+ apiVersion: storage.cnrm.cloud.google.com/v1beta1
148
+ kind: StorageBucket
149
+ metadata:
150
+ name: bucket
151
+ spec:
152
+ location: US
153
+ ---
154
+ apiVersion: v1
155
+ kind: Service
156
+ metadata:
157
+ name: svc
158
+ spec:
159
+ ports:
160
+ - port: 80
161
+ `;
162
+ const ir = parser.parse(yaml);
163
+ // Only the StorageBucket should be parsed
164
+ expect(ir.resources.length).toBe(1);
165
+ expect(ir.resources[0].type).toBe("GCP::Storage::Bucket");
166
+ });
167
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Config Connector YAML template parser.
3
+ *
4
+ * Parses Config Connector YAML manifests into the import IR
5
+ * for conversion to chant TypeScript.
6
+ */
7
+
8
+ import type {
9
+ TemplateParser,
10
+ TemplateIR,
11
+ ResourceIR,
12
+ } from "@intentius/chant/import/parser";
13
+ import { BaseValueParser } from "@intentius/chant/import/base-parser";
14
+ import { parseYAML } from "@intentius/chant/yaml";
15
+ import { gcpTypeName } from "../spec/parse";
16
+
17
+ /**
18
+ * Parser for Config Connector YAML manifests.
19
+ */
20
+ export class GcpParser extends BaseValueParser implements TemplateParser {
21
+ protected dispatchIntrinsic(
22
+ _key: string,
23
+ _value: unknown,
24
+ _obj: Record<string, unknown>,
25
+ ): unknown | null {
26
+ // Config Connector YAML has no intrinsic functions
27
+ return null;
28
+ }
29
+
30
+ parse(input: string): TemplateIR {
31
+ const resources: ResourceIR[] = [];
32
+ const documents = input
33
+ .split(/^---\s*$/m)
34
+ .map((d) => d.trim())
35
+ .filter((d) => d.length > 0);
36
+
37
+ for (const docStr of documents) {
38
+ const doc = parseYAML(docStr) as Record<string, unknown>;
39
+ if (!doc) continue;
40
+
41
+ const apiVersion = doc.apiVersion as string | undefined;
42
+ const kind = doc.kind as string | undefined;
43
+ if (!apiVersion || !kind) continue;
44
+
45
+ // Only handle Config Connector resources
46
+ if (!apiVersion.includes("cnrm.cloud.google.com")) continue;
47
+
48
+ const group = apiVersion.split("/")[0];
49
+ const typeName = gcpTypeName(group, kind);
50
+
51
+ const metadata = doc.metadata as Record<string, unknown> | undefined;
52
+ const spec = doc.spec as Record<string, unknown> | undefined;
53
+
54
+ const logicalName = (metadata?.name as string) ?? kind;
55
+
56
+ // Build properties from spec
57
+ const properties: Record<string, unknown> = {};
58
+ if (metadata) {
59
+ properties.metadata = this.parseValue(metadata);
60
+ }
61
+ if (spec) {
62
+ for (const [key, value] of Object.entries(spec)) {
63
+ properties[key] = this.parseValue(value);
64
+ }
65
+ }
66
+
67
+ resources.push({
68
+ logicalName,
69
+ type: typeName,
70
+ properties,
71
+ });
72
+ }
73
+
74
+ return {
75
+ resources,
76
+ parameters: [],
77
+ outputs: [],
78
+ };
79
+ }
80
+ }
@@ -0,0 +1,66 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { readFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { GcpParser } from "./parser";
6
+ import { GcpGenerator } from "./generator";
7
+
8
+ const testdataDir = join(
9
+ dirname(dirname(fileURLToPath(import.meta.url))),
10
+ "testdata",
11
+ "manifests",
12
+ );
13
+
14
+ const parser = new GcpParser();
15
+ const generator = new GcpGenerator();
16
+
17
+ describe("roundtrip: parse YAML → generate TypeScript", () => {
18
+ test("StorageBucket roundtrip", () => {
19
+ const yaml = readFileSync(join(testdataDir, "storage-bucket.yaml"), "utf-8");
20
+ const ir = parser.parse(yaml);
21
+ const ts = generator.generate(ir);
22
+
23
+ expect(ir.resources.length).toBe(1);
24
+ expect(ts).toContain("new Bucket");
25
+ expect(ts).toContain("export const");
26
+ });
27
+
28
+ test("ComputeInstance roundtrip", () => {
29
+ const yaml = readFileSync(join(testdataDir, "compute-instance.yaml"), "utf-8");
30
+ const ir = parser.parse(yaml);
31
+ const ts = generator.generate(ir);
32
+
33
+ expect(ir.resources.length).toBe(1);
34
+ expect(ts).toContain("new Instance");
35
+ expect(ts).toContain("export const");
36
+ expect(ts).toContain("e2-medium");
37
+ });
38
+
39
+ test("multi-doc full-app roundtrip", () => {
40
+ const yaml = readFileSync(join(testdataDir, "full-app.yaml"), "utf-8");
41
+ const ir = parser.parse(yaml);
42
+ expect(ir.resources.length).toBe(3); // StorageBucket + IAMPolicyMember + ComputeNetwork
43
+
44
+ const ts = generator.generate(ir);
45
+ expect(ts).toContain("Bucket");
46
+ expect(ts).toContain("PolicyMember");
47
+ expect(ts).toContain("Network");
48
+ });
49
+
50
+ test("inline YAML roundtrip", () => {
51
+ const yaml = `
52
+ apiVersion: storage.cnrm.cloud.google.com/v1beta1
53
+ kind: StorageBucket
54
+ metadata:
55
+ name: inline-test
56
+ spec:
57
+ location: EU
58
+ storageClass: NEARLINE
59
+ `;
60
+ const ir = parser.parse(yaml);
61
+ const ts = generator.generate(ir);
62
+
63
+ expect(ts).toContain("new Bucket");
64
+ expect(ts).toContain("export const");
65
+ });
66
+ });
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ // Serializer
2
+ export { gcpSerializer } from "./serializer";
3
+
4
+ // Plugin
5
+ export { gcpPlugin } from "./plugin";
6
+
7
+ // Default labels & annotations
8
+ export { defaultLabels, defaultAnnotations, isDefaultLabels, isDefaultAnnotations } from "./default-labels";
9
+ export { DEFAULT_LABELS_MARKER, DEFAULT_ANNOTATIONS_MARKER } from "./default-labels";
10
+
11
+ // Pseudo-parameters
12
+ export { GCP, ProjectId, Region, Zone } from "./pseudo";
13
+
14
+ // Variables / annotation constants
15
+ export { GcpAnnotations } from "./variables";
16
+
17
+ // Generated entities — export everything from generated index
18
+ // After running `bun run generate`, this re-exports all Config Connector resource classes
19
+ export * from "./generated/index";
20
+
21
+ // Composites
22
+ export {
23
+ GkeCluster, CloudRunService, CloudSqlInstance, GcsBucket, VpcNetwork,
24
+ PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject,
25
+ } from "./composites/index";
26
+ export type {
27
+ GkeClusterProps, GkeClusterResult,
28
+ CloudRunServiceProps, CloudRunServiceResult,
29
+ CloudSqlInstanceProps, CloudSqlInstanceResult,
30
+ GcsBucketProps, GcsBucketResult,
31
+ VpcNetworkProps, VpcNetworkResult, VpcSubnet,
32
+ PubSubPipelineProps, PubSubPipelineResult,
33
+ CloudFunctionWithTriggerProps, CloudFunctionWithTriggerResult,
34
+ PrivateServiceProps, PrivateServiceResult,
35
+ ManagedCertificateProps, ManagedCertificateResult,
36
+ SecureProjectProps, SecureProjectResult,
37
+ } from "./composites/index";
38
+
39
+ // IAM role constants
40
+ export { StorageRoles, ComputeRoles, ContainerRoles, IAMRoles, SQLRoles, RunRoles, PubSubRoles } from "./actions/index";
41
+
42
+ // Spec utilities (for tooling)
43
+ export { fetchCRDBundle, getCachePath, clearCache, KCC_VERSION } from "./spec/fetch";
44
+ export { parseGcpCRD, gcpServiceName, gcpShortName, gcpTypeName, stripServicePrefix } from "./spec/parse";
45
+ export type { GcpParseResult, ParsedResource, ParsedProperty, ParsedPropertyType, ParsedEnum, GroupVersionKind } from "./spec/parse";
46
+
47
+ // Code generation pipeline
48
+ export { generate, writeGeneratedFiles } from "./codegen/generate";
49
+ export { packageLexicon } from "./codegen/package";
50
+ export type { PackageOptions, PackageResult } from "./codegen/package";
51
+
52
+ // LSP providers
53
+ export { gcpCompletions } from "./lsp/completions";
54
+ export { gcpHover } from "./lsp/hover";
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Shared helpers for GCP Config Connector post-synthesis lint rules.
3
+ *
4
+ * Provides YAML parsing for multi-document Config Connector manifests
5
+ * and accessor utilities for common manifest fields.
6
+ */
7
+
8
+ import { parseYAML } from "@intentius/chant/yaml";
9
+ export { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
10
+
11
+ /**
12
+ * A parsed Config Connector manifest (loosely typed).
13
+ */
14
+ export interface GcpManifest {
15
+ apiVersion?: string;
16
+ kind?: string;
17
+ metadata?: {
18
+ name?: string;
19
+ namespace?: string;
20
+ labels?: Record<string, string>;
21
+ annotations?: Record<string, string>;
22
+ [key: string]: unknown;
23
+ };
24
+ spec?: Record<string, unknown>;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ /**
29
+ * Split a multi-document YAML string on `---` boundaries and parse each
30
+ * document into a GcpManifest.
31
+ */
32
+ export function parseGcpManifests(yaml: string): GcpManifest[] {
33
+ const documents = yaml.split(/^---\s*$/m);
34
+ const manifests: GcpManifest[] = [];
35
+
36
+ for (const doc of documents) {
37
+ const trimmed = doc.trim();
38
+ if (trimmed === "" || trimmed === "---") continue;
39
+ try {
40
+ const parsed = parseYAML(trimmed);
41
+ if (typeof parsed === "object" && parsed !== null) {
42
+ manifests.push(parsed as GcpManifest);
43
+ }
44
+ } catch {
45
+ // Skip unparseable documents
46
+ }
47
+ }
48
+
49
+ return manifests;
50
+ }
51
+
52
+ /**
53
+ * Check if a manifest is a Config Connector resource
54
+ * (apiVersion contains cnrm.cloud.google.com).
55
+ */
56
+ export function isConfigConnectorResource(manifest: GcpManifest): boolean {
57
+ return typeof manifest.apiVersion === "string" &&
58
+ manifest.apiVersion.includes("cnrm.cloud.google.com");
59
+ }
60
+
61
+ /**
62
+ * Safely extract the spec from a manifest.
63
+ */
64
+ export function getSpec(manifest: GcpManifest): Record<string, unknown> | undefined {
65
+ return manifest.spec ?? undefined;
66
+ }
67
+
68
+ /**
69
+ * Safely extract annotations from a manifest's metadata.
70
+ */
71
+ export function getAnnotations(manifest: GcpManifest): Record<string, string> | undefined {
72
+ return manifest.metadata?.annotations ?? undefined;
73
+ }
74
+
75
+ /**
76
+ * Get the resource name from metadata.
77
+ */
78
+ export function getResourceName(manifest: GcpManifest): string {
79
+ return manifest.metadata?.name ?? "unknown";
80
+ }
81
+
82
+ /**
83
+ * Recursively walk a spec object looking for keys ending in `Ref`
84
+ * (e.g. `networkRef`, `topicRef`, `clusterRef`) that have a `name`
85
+ * sub-field. Returns the set of referenced names.
86
+ *
87
+ * Skips `external` refs (cross-project references outside the template).
88
+ */
89
+ export function findResourceRefs(obj: unknown): Set<string> {
90
+ const refs = new Set<string>();
91
+ walkForRefs(obj, refs);
92
+ return refs;
93
+ }
94
+
95
+ function walkForRefs(value: unknown, refs: Set<string>): void {
96
+ if (value === null || value === undefined) return;
97
+
98
+ if (Array.isArray(value)) {
99
+ for (const item of value) {
100
+ walkForRefs(item, refs);
101
+ }
102
+ return;
103
+ }
104
+
105
+ if (typeof value === "object") {
106
+ const obj = value as Record<string, unknown>;
107
+ for (const [key, val] of Object.entries(obj)) {
108
+ if (key.endsWith("Ref") && typeof val === "object" && val !== null) {
109
+ const refObj = val as Record<string, unknown>;
110
+ if (typeof refObj.name === "string" && !refObj.external) {
111
+ refs.add(refObj.name);
112
+ }
113
+ }
114
+ walkForRefs(val, refs);
115
+ }
116
+ }
117
+ }