@intentius/chant-lexicon-gcp 0.0.18 → 0.0.24

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 (68) hide show
  1. package/dist/integrity.json +12 -8
  2. package/dist/manifest.json +1 -1
  3. package/dist/meta.json +18141 -0
  4. package/dist/rules/schema-registry.ts +91 -0
  5. package/dist/rules/wgc101.ts +1 -1
  6. package/dist/rules/wgc401.ts +59 -0
  7. package/dist/rules/wgc402.ts +54 -0
  8. package/dist/rules/wgc403.ts +84 -0
  9. package/dist/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
  10. package/dist/skills/chant-gcp-patterns.md +3 -2
  11. package/dist/skills/chant-gcp-security.md +3 -2
  12. package/dist/skills/chant-gcp.md +363 -28
  13. package/package.json +20 -2
  14. package/src/codegen/docs.test.ts +16 -0
  15. package/src/codegen/generate.test.ts +18 -0
  16. package/src/codegen/generate.ts +11 -0
  17. package/src/codegen/package.test.ts +16 -0
  18. package/src/composites/cloud-function.ts +23 -15
  19. package/src/composites/cloud-run-service.ts +20 -13
  20. package/src/composites/cloud-sql-instance.ts +18 -14
  21. package/src/composites/composites.test.ts +94 -62
  22. package/src/composites/gcs-bucket.ts +13 -9
  23. package/src/composites/gke-cluster.ts +91 -16
  24. package/src/composites/index.ts +11 -11
  25. package/src/composites/managed-certificate.ts +19 -15
  26. package/src/composites/private-service.ts +23 -15
  27. package/src/composites/pubsub-pipeline.ts +30 -18
  28. package/src/composites/secure-project.ts +42 -27
  29. package/src/composites/vpc-network.ts +42 -35
  30. package/src/generated/lexicon-gcp.json +18141 -0
  31. package/src/import/import-fixtures.test.ts +98 -0
  32. package/src/index.ts +11 -11
  33. package/src/lint/post-synth/gcp-helpers.test.ts +166 -0
  34. package/src/lint/post-synth/post-synth.test.ts +132 -1
  35. package/src/lint/post-synth/schema-registry.ts +91 -0
  36. package/src/lint/post-synth/wgc101.test.ts +40 -0
  37. package/src/lint/post-synth/wgc101.ts +1 -1
  38. package/src/lint/post-synth/wgc102.test.ts +38 -0
  39. package/src/lint/post-synth/wgc103.test.ts +38 -0
  40. package/src/lint/post-synth/wgc104.test.ts +37 -0
  41. package/src/lint/post-synth/wgc105.test.ts +46 -0
  42. package/src/lint/post-synth/wgc106.test.ts +38 -0
  43. package/src/lint/post-synth/wgc107.test.ts +38 -0
  44. package/src/lint/post-synth/wgc108.test.ts +42 -0
  45. package/src/lint/post-synth/wgc109.test.ts +46 -0
  46. package/src/lint/post-synth/wgc110.test.ts +37 -0
  47. package/src/lint/post-synth/wgc111.test.ts +46 -0
  48. package/src/lint/post-synth/wgc112.test.ts +48 -0
  49. package/src/lint/post-synth/wgc113.test.ts +36 -0
  50. package/src/lint/post-synth/wgc201.test.ts +38 -0
  51. package/src/lint/post-synth/wgc202.test.ts +38 -0
  52. package/src/lint/post-synth/wgc203.test.ts +45 -0
  53. package/src/lint/post-synth/wgc204.test.ts +42 -0
  54. package/src/lint/post-synth/wgc301.test.ts +39 -0
  55. package/src/lint/post-synth/wgc302.test.ts +36 -0
  56. package/src/lint/post-synth/wgc303.test.ts +37 -0
  57. package/src/lint/post-synth/wgc401.test.ts +46 -0
  58. package/src/lint/post-synth/wgc401.ts +59 -0
  59. package/src/lint/post-synth/wgc402.test.ts +40 -0
  60. package/src/lint/post-synth/wgc402.ts +54 -0
  61. package/src/lint/post-synth/wgc403.test.ts +59 -0
  62. package/src/lint/post-synth/wgc403.ts +84 -0
  63. package/src/plugin.test.ts +4 -1
  64. package/src/plugin.ts +258 -177
  65. package/src/skills/{chant-gke.md → chant-gcp-gke.md} +1 -1
  66. package/src/skills/chant-gcp-patterns.md +3 -2
  67. package/src/skills/chant-gcp-security.md +3 -2
  68. package/src/skills/chant-gcp.md +363 -28
@@ -0,0 +1,46 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { wgc401 } from "./wgc401";
3
+
4
+ function makeCtx(yaml: string) {
5
+ return {
6
+ outputs: new Map([["gcp", yaml]]),
7
+ };
8
+ }
9
+
10
+ describe("WGC401: unknown spec field", () => {
11
+ test("flags unknown field with did-you-mean suggestion", () => {
12
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
13
+ kind: ComputeFirewall
14
+ metadata:
15
+ name: my-fw
16
+ spec:
17
+ allowed:
18
+ - protocol: tcp
19
+ networkRef:
20
+ name: my-network
21
+ `;
22
+ const diags = wgc401.check(makeCtx(yaml));
23
+ // "allowed" is not a valid field -- "allow" is. Should flag it.
24
+ const unknownDiags = diags.filter(d => d.checkId === "WGC401");
25
+ // If schema is loaded, this will flag "allowed"
26
+ // If schema is not loaded (pre-generate), skip gracefully
27
+ if (unknownDiags.length > 0) {
28
+ expect(unknownDiags[0].severity).toBe("error");
29
+ expect(unknownDiags[0].message).toContain("allowed");
30
+ }
31
+ });
32
+
33
+ test("no diagnostic for valid fields", () => {
34
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
35
+ kind: StorageBucket
36
+ metadata:
37
+ name: my-bucket
38
+ spec:
39
+ location: US
40
+ `;
41
+ const diags = wgc401.check(makeCtx(yaml));
42
+ // "location" is a valid StorageBucket field
43
+ const unknownDiags = diags.filter(d => d.checkId === "WGC401");
44
+ expect(unknownDiags).toHaveLength(0);
45
+ });
46
+ });
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WGC401: Unknown spec field
3
+ *
4
+ * Flags fields in a Config Connector resource spec that are not defined
5
+ * in the CRD schema. Includes "did you mean?" suggestion via Levenshtein.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
10
+ import { getSchemaRegistry, suggestField } from "./schema-registry";
11
+
12
+ export const wgc401: PostSynthCheck = {
13
+ id: "WGC401",
14
+ description: "Config Connector resource spec contains unknown field",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
18
+ const registry = getSchemaRegistry();
19
+
20
+ for (const [_lexicon, output] of ctx.outputs) {
21
+ if (typeof output !== "string") continue;
22
+
23
+ const manifests = parseGcpManifests(output);
24
+
25
+ for (const manifest of manifests) {
26
+ if (!isConfigConnectorResource(manifest)) continue;
27
+
28
+ const kind = manifest.kind;
29
+ if (!kind) continue;
30
+
31
+ const schema = registry.get(kind);
32
+ if (!schema) continue; // No schema available for this kind
33
+
34
+ const spec = getSpec(manifest);
35
+ if (!spec) continue;
36
+
37
+ const knownFields = Object.keys(schema.fields);
38
+ const resourceName = getResourceName(manifest);
39
+
40
+ for (const field of Object.keys(spec)) {
41
+ if (field in schema.fields) continue;
42
+
43
+ const suggestion = suggestField(field, knownFields);
44
+ const hint = suggestion ? ` — did you mean "${suggestion}"?` : "";
45
+
46
+ diagnostics.push({
47
+ checkId: "WGC401",
48
+ severity: "error",
49
+ message: `Resource "${resourceName}" (${kind}): unknown spec field "${field}"${hint}`,
50
+ entity: resourceName,
51
+ lexicon: "gcp",
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ return diagnostics;
58
+ },
59
+ };
@@ -0,0 +1,40 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { wgc402 } from "./wgc402";
3
+
4
+ function makeCtx(yaml: string) {
5
+ return {
6
+ outputs: new Map([["gcp", yaml]]),
7
+ };
8
+ }
9
+
10
+ describe("WGC402: missing required field", () => {
11
+ test("flags missing required field", () => {
12
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
13
+ kind: ComputeAddress
14
+ metadata:
15
+ name: my-addr
16
+ spec:
17
+ description: test
18
+ `;
19
+ const diags = wgc402.check(makeCtx(yaml));
20
+ const requiredDiags = diags.filter(d => d.checkId === "WGC402");
21
+ // ComputeAddress requires "location" -- if schema is loaded, this flags it
22
+ if (requiredDiags.length > 0) {
23
+ expect(requiredDiags[0].severity).toBe("error");
24
+ expect(requiredDiags[0].message).toContain("required");
25
+ }
26
+ });
27
+
28
+ test("no diagnostic when all required fields present", () => {
29
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
30
+ kind: StorageBucket
31
+ metadata:
32
+ name: my-bucket
33
+ spec:
34
+ location: US
35
+ `;
36
+ const diags = wgc402.check(makeCtx(yaml));
37
+ const requiredDiags = diags.filter(d => d.checkId === "WGC402");
38
+ expect(requiredDiags).toHaveLength(0);
39
+ });
40
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WGC402: Missing required spec field
3
+ *
4
+ * Flags Config Connector resources that are missing required fields
5
+ * according to their CRD schema.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
10
+ import { getSchemaRegistry } from "./schema-registry";
11
+
12
+ export const wgc402: PostSynthCheck = {
13
+ id: "WGC402",
14
+ description: "Config Connector resource is missing a required spec field",
15
+
16
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
18
+ const registry = getSchemaRegistry();
19
+
20
+ for (const [_lexicon, output] of ctx.outputs) {
21
+ if (typeof output !== "string") continue;
22
+
23
+ const manifests = parseGcpManifests(output);
24
+
25
+ for (const manifest of manifests) {
26
+ if (!isConfigConnectorResource(manifest)) continue;
27
+
28
+ const kind = manifest.kind;
29
+ if (!kind) continue;
30
+
31
+ const schema = registry.get(kind);
32
+ if (!schema) continue;
33
+
34
+ const spec = getSpec(manifest);
35
+ const specKeys = new Set(spec ? Object.keys(spec) : []);
36
+ const resourceName = getResourceName(manifest);
37
+
38
+ for (const requiredField of schema.required) {
39
+ if (!specKeys.has(requiredField)) {
40
+ diagnostics.push({
41
+ checkId: "WGC402",
42
+ severity: "error",
43
+ message: `Resource "${resourceName}" (${kind}): missing required field "${requiredField}"`,
44
+ entity: resourceName,
45
+ lexicon: "gcp",
46
+ });
47
+ }
48
+ }
49
+ }
50
+ }
51
+
52
+ return diagnostics;
53
+ },
54
+ };
@@ -0,0 +1,59 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { wgc403 } from "./wgc403";
3
+
4
+ function makeCtx(yaml: string) {
5
+ return {
6
+ outputs: new Map([["gcp", yaml]]),
7
+ };
8
+ }
9
+
10
+ describe("WGC403: type/structure mismatch", () => {
11
+ test("flags string where number expected", () => {
12
+ const yaml = `apiVersion: cloudfunctions.cnrm.cloud.google.com/v1beta1
13
+ kind: CloudFunctionsFunction
14
+ metadata:
15
+ name: my-fn
16
+ spec:
17
+ runtime: nodejs18
18
+ availableMemoryMb: "512"
19
+ region: us-central1
20
+ `;
21
+ const diags = wgc403.check(makeCtx(yaml));
22
+ const typeDiags = diags.filter(d => d.checkId === "WGC403");
23
+ if (typeDiags.length > 0) {
24
+ expect(typeDiags[0].severity).toBe("error");
25
+ expect(typeDiags[0].message).toContain("number");
26
+ expect(typeDiags[0].message).toContain("string");
27
+ }
28
+ });
29
+
30
+ test("flags bare string instead of resourceRef object", () => {
31
+ const yaml = `apiVersion: pubsub.cnrm.cloud.google.com/v1beta1
32
+ kind: PubSubSubscription
33
+ metadata:
34
+ name: my-sub
35
+ spec:
36
+ topicRef: my-topic
37
+ `;
38
+ const diags = wgc403.check(makeCtx(yaml));
39
+ const typeDiags = diags.filter(d => d.checkId === "WGC403");
40
+ if (typeDiags.length > 0) {
41
+ expect(typeDiags[0].severity).toBe("error");
42
+ expect(typeDiags[0].message).toContain("resourceRef");
43
+ }
44
+ });
45
+
46
+ test("no diagnostic with correct types", () => {
47
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
48
+ kind: StorageBucket
49
+ metadata:
50
+ name: my-bucket
51
+ spec:
52
+ location: US
53
+ uniformBucketLevelAccess: true
54
+ `;
55
+ const diags = wgc403.check(makeCtx(yaml));
56
+ const typeDiags = diags.filter(d => d.checkId === "WGC403");
57
+ expect(typeDiags).toHaveLength(0);
58
+ });
59
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * WGC403: Type/structure mismatch in spec field
3
+ *
4
+ * Flags spec fields where the value type doesn't match the CRD schema:
5
+ * - String where number expected (e.g. availableMemoryMb: "512")
6
+ * - Scalar string where resourceRef object expected (e.g. topicRef: "name")
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseGcpManifests, isConfigConnectorResource, getResourceName, getSpec } from "./gcp-helpers";
11
+ import { getSchemaRegistry } from "./schema-registry";
12
+
13
+ export const wgc403: PostSynthCheck = {
14
+ id: "WGC403",
15
+ description: "Config Connector resource spec field has wrong type or structure",
16
+
17
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
18
+ const diagnostics: PostSynthDiagnostic[] = [];
19
+ const registry = getSchemaRegistry();
20
+
21
+ for (const [_lexicon, output] of ctx.outputs) {
22
+ if (typeof output !== "string") continue;
23
+
24
+ const manifests = parseGcpManifests(output);
25
+
26
+ for (const manifest of manifests) {
27
+ if (!isConfigConnectorResource(manifest)) continue;
28
+
29
+ const kind = manifest.kind;
30
+ if (!kind) continue;
31
+
32
+ const schema = registry.get(kind);
33
+ if (!schema) continue;
34
+
35
+ const spec = getSpec(manifest);
36
+ if (!spec) continue;
37
+
38
+ const resourceName = getResourceName(manifest);
39
+
40
+ for (const [field, value] of Object.entries(spec)) {
41
+ const fieldSchema = schema.fields[field];
42
+ if (!fieldSchema) continue; // Unknown fields handled by WGC401
43
+
44
+ // Check: resourceRef field expects an object, got a scalar
45
+ if (fieldSchema.ref && typeof value === "string") {
46
+ diagnostics.push({
47
+ checkId: "WGC403",
48
+ severity: "error",
49
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a resourceRef object (e.g. { name, kind }), got string "${value}"`,
50
+ entity: resourceName,
51
+ lexicon: "gcp",
52
+ });
53
+ continue;
54
+ }
55
+
56
+ // Check: number field got a string that looks numeric
57
+ if (fieldSchema.type === "number" && typeof value === "string") {
58
+ diagnostics.push({
59
+ checkId: "WGC403",
60
+ severity: "error",
61
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a number, got string "${value}"`,
62
+ entity: resourceName,
63
+ lexicon: "gcp",
64
+ });
65
+ continue;
66
+ }
67
+
68
+ // Check: boolean field got a string
69
+ if (fieldSchema.type === "boolean" && typeof value === "string") {
70
+ diagnostics.push({
71
+ checkId: "WGC403",
72
+ severity: "error",
73
+ message: `Resource "${resourceName}" (${kind}): field "${field}" expects a boolean, got string "${value}"`,
74
+ entity: resourceName,
75
+ lexicon: "gcp",
76
+ });
77
+ }
78
+ }
79
+ }
80
+ }
81
+
82
+ return diagnostics;
83
+ },
84
+ };
@@ -30,7 +30,7 @@ describe("gcpPlugin", () => {
30
30
 
31
31
  test("returns post-synth checks", () => {
32
32
  const checks = gcpPlugin.postSynthChecks!();
33
- expect(checks).toHaveLength(20);
33
+ expect(checks).toHaveLength(23);
34
34
  const ids = checks.map((c) => c.id);
35
35
  expect(ids).toContain("WGC101");
36
36
  expect(ids).toContain("WGC102");
@@ -52,6 +52,9 @@ describe("gcpPlugin", () => {
52
52
  expect(ids).toContain("WGC301");
53
53
  expect(ids).toContain("WGC302");
54
54
  expect(ids).toContain("WGC303");
55
+ expect(ids).toContain("WGC401");
56
+ expect(ids).toContain("WGC402");
57
+ expect(ids).toContain("WGC403");
55
58
  });
56
59
 
57
60
  // ── Intrinsics / pseudo-parameters ─────────────────────────────────