@intentius/chant-lexicon-gcp 0.1.4 → 0.1.6

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 (59) hide show
  1. package/dist/integrity.json +37 -37
  2. package/dist/manifest.json +1 -1
  3. package/package.json +7 -7
  4. package/src/codegen/docs-cli.ts +1 -1
  5. package/src/codegen/docs.test.ts +1 -1
  6. package/src/codegen/docs.ts +5 -1
  7. package/src/codegen/generate-cli.ts +1 -1
  8. package/src/codegen/generate.test.ts +1 -1
  9. package/src/codegen/naming.test.ts +1 -1
  10. package/src/codegen/package.test.ts +1 -1
  11. package/src/codegen/package.ts +2 -7
  12. package/src/composites/composites.test.ts +1 -1
  13. package/src/composites/gke-crdb-region.ts +232 -0
  14. package/src/composites/index.ts +4 -0
  15. package/src/composites/multi-region-vpc.ts +157 -0
  16. package/src/coverage.test.ts +1 -1
  17. package/src/default-labels.test.ts +1 -1
  18. package/src/import/generator.test.ts +1 -1
  19. package/src/import/import-fixtures.test.ts +1 -1
  20. package/src/import/parser.test.ts +1 -1
  21. package/src/import/roundtrip.test.ts +1 -1
  22. package/src/index.ts +4 -2
  23. package/src/lint/post-synth/gcp-helpers.test.ts +1 -1
  24. package/src/lint/post-synth/post-synth.test.ts +1 -1
  25. package/src/lint/post-synth/wgc101.test.ts +1 -1
  26. package/src/lint/post-synth/wgc102.test.ts +1 -1
  27. package/src/lint/post-synth/wgc103.test.ts +1 -1
  28. package/src/lint/post-synth/wgc104.test.ts +1 -1
  29. package/src/lint/post-synth/wgc105.test.ts +1 -1
  30. package/src/lint/post-synth/wgc106.test.ts +1 -1
  31. package/src/lint/post-synth/wgc107.test.ts +1 -1
  32. package/src/lint/post-synth/wgc108.test.ts +1 -1
  33. package/src/lint/post-synth/wgc109.test.ts +1 -1
  34. package/src/lint/post-synth/wgc110.test.ts +1 -1
  35. package/src/lint/post-synth/wgc111.test.ts +1 -1
  36. package/src/lint/post-synth/wgc112.test.ts +1 -1
  37. package/src/lint/post-synth/wgc113.test.ts +1 -1
  38. package/src/lint/post-synth/wgc201.test.ts +1 -1
  39. package/src/lint/post-synth/wgc202.test.ts +1 -1
  40. package/src/lint/post-synth/wgc203.test.ts +1 -1
  41. package/src/lint/post-synth/wgc204.test.ts +1 -1
  42. package/src/lint/post-synth/wgc301.test.ts +1 -1
  43. package/src/lint/post-synth/wgc302.test.ts +1 -1
  44. package/src/lint/post-synth/wgc303.test.ts +1 -1
  45. package/src/lint/post-synth/wgc401.test.ts +1 -1
  46. package/src/lint/post-synth/wgc402.test.ts +1 -1
  47. package/src/lint/post-synth/wgc403.test.ts +1 -1
  48. package/src/lint/rules/rules.test.ts +1 -1
  49. package/src/lsp/completions.test.ts +1 -1
  50. package/src/lsp/hover.test.ts +1 -1
  51. package/src/package-cli.ts +2 -2
  52. package/src/plugin.test.ts +1 -1
  53. package/src/plugin.ts +7 -9
  54. package/src/pseudo.test.ts +1 -1
  55. package/src/serializer.test.ts +1 -1
  56. package/src/spec/fetch.test.ts +2 -2
  57. package/src/spec/parse.test.ts +1 -1
  58. package/src/validate-cli.ts +1 -1
  59. package/src/validate.test.ts +1 -1
@@ -1,41 +1,41 @@
1
1
  {
2
- "algorithm": "xxhash64",
2
+ "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "b2c3c0befed259f3",
5
- "meta.json": "4b53e87923a434ef",
6
- "types/index.d.ts": "c7cc45a739cefe9d",
7
- "rules/hardcoded-region.ts": "d230565c5cc6b600",
8
- "rules/public-iam.ts": "a569c09c0402dc12",
9
- "rules/hardcoded-project.ts": "8ce1e3f6f7f7d217",
10
- "rules/wgc203.ts": "40a8831e701d4882",
11
- "rules/wgc108.ts": "4ed90d7ccb04a93",
12
- "rules/wgc105.ts": "2e6a93ea14234e53",
13
- "rules/schema-registry.ts": "6327d2dc2fed726e",
14
- "rules/wgc109.ts": "58a4010d5a5e3cfc",
15
- "rules/wgc103.ts": "e0a3c0606cb1fe1",
16
- "rules/wgc110.ts": "2a9da9543e34d8de",
17
- "rules/wgc201.ts": "173c4cfb020c3593",
18
- "rules/wgc302.ts": "f2a5f2ea4efec1b",
19
- "rules/wgc111.ts": "474a4ab06596ecd0",
20
- "rules/wgc112.ts": "34b22234dfca1a17",
21
- "rules/wgc402.ts": "7f054e29d146290f",
22
- "rules/gcp-helpers.ts": "76fcd4774c99093c",
23
- "rules/wgc106.ts": "180629dfc4f2934",
24
- "rules/wgc113.ts": "677724b28a9dbd5c",
25
- "rules/wgc202.ts": "b6036fce2553d15b",
26
- "rules/wgc303.ts": "4be04d7faa6b2f1e",
27
- "rules/wgc107.ts": "ae6dcd327490da06",
28
- "rules/wgc101.ts": "170fc08848092171",
29
- "rules/wgc401.ts": "35fcebf42c7379c7",
30
- "rules/wgc301.ts": "7481759f8344dc46",
31
- "rules/wgc104.ts": "56b3c40af42e9302",
32
- "rules/wgc403.ts": "a873a8708f82f0a9",
33
- "rules/wgc102.ts": "ee34a5b1d58643b8",
34
- "rules/wgc204.ts": "bf9cb93ace50c4b0",
35
- "skills/chant-gcp.md": "13a559014514fdb0",
36
- "skills/chant-gcp-security.md": "9ed168e1fb54d31b",
37
- "skills/chant-gcp-patterns.md": "6cd0a4a78933323c",
38
- "skills/chant-gcp-gke.md": "f185f6c0de514ba0"
4
+ "manifest.json": "8939a54211882426c712c23fd6cdf36237dd92741ce3b3d18227f84225e2c50c",
5
+ "meta.json": "2bc3713e9e01e90832d18dedaf4bf544dd4d8fe32f1363f7e95dc711d3d76e9d",
6
+ "types/index.d.ts": "0554f7e883c6216ba735cbe79a8534ccad762bea28cc4d4c4884f8760a2f1dd3",
7
+ "rules/hardcoded-project.ts": "228631d3159e1ffcce2359c66073d4ae59bb0285f378e46e446f416aec50481c",
8
+ "rules/hardcoded-region.ts": "9135e337202d44787e93243811a942f2209627ce522f27a6f3c7c0ad0e2e3545",
9
+ "rules/public-iam.ts": "bf2fe502ad5bfb077bdb3039a6bb228a3214cff2769996d65362bc3f7c5bb730",
10
+ "rules/gcp-helpers.ts": "0420adb73527b29a288e219b24742f4f29ef83859228d61fdaff83b1fb800bf1",
11
+ "rules/schema-registry.ts": "825862c574f0291b07f46478018874556a190940a1653b24e9dec9f1427d8d8b",
12
+ "rules/wgc101.ts": "679f245ea4665d687848382ff1b934ae133a3c320cc051011cc4d95802e66b38",
13
+ "rules/wgc102.ts": "22cb997d40a59ce3e0ff833ce946a5571e13d4811b255d4b67db570d58575e07",
14
+ "rules/wgc103.ts": "589741dbe5828508b5424a74f63f0202ba8452237c366173af283073f81e0317",
15
+ "rules/wgc104.ts": "5e1a74ffae157296de4882e4d0693146a24d43f3f59f5db80f4b0faf2da230a3",
16
+ "rules/wgc105.ts": "7208632ce9bc264c597453f1bd5e62f3569207e4e2f988ebb8f0451f3d714184",
17
+ "rules/wgc106.ts": "916c199b2b1ce064f9e15bcdd0a052e9eb64cb79eb79d03aeae58708b1e894e3",
18
+ "rules/wgc107.ts": "022474be5f9150c6f352ebbfa5ee9bbe37d2389bfc23bd091c5bedaa7f28a7c3",
19
+ "rules/wgc108.ts": "170db62ee6af7d5c9f25e1bde9fb308e074178192291992360a77407edec7486",
20
+ "rules/wgc109.ts": "468b889c6d2bf804e0e214daa0007b5a2f9a8c0d614b57134ac5be9739b7a2a2",
21
+ "rules/wgc110.ts": "632f692c6e67efad14e24edd1f1cb41d11faefa8d77efcd4e4af93ff2605be3a",
22
+ "rules/wgc111.ts": "7bb8d5c4121d4038b2089728f3c8ae7f5306163101d157ceccc78a982a9be872",
23
+ "rules/wgc112.ts": "d6ff610929ee9c85b0f3c455de83ab1c1f984a5c6f9a0b2989ba2cd50e74e4f8",
24
+ "rules/wgc113.ts": "59d40d6f433b2e448ed895775d3dbbcfad22c3ac41b08c7a32d49474a3973c2a",
25
+ "rules/wgc201.ts": "96a4ab92c4d7f0ed4f33dc44b7ab64b5ec4ccdd48f24fd69374be79e33769781",
26
+ "rules/wgc202.ts": "44e4231a9e6ed7e5917506d8d60554cf0ec0e64f148ba7a7ad739cb8bb9e51a0",
27
+ "rules/wgc203.ts": "716b45cb305634927840623a5dfd8b6c0375420be25096902ab1e5bd0ff71671",
28
+ "rules/wgc204.ts": "6a0fea0a0e002065fbb5ce11f29dea183b98ec70134987fa1889731235ab4f70",
29
+ "rules/wgc301.ts": "a7b1ec58d4582911bfef7f82fd83a97706525c2173e2843958f2e62cdaa3fc2a",
30
+ "rules/wgc302.ts": "94423099c34e1c2193966920a441cb7af80b1048720bdca1263f444dc666b941",
31
+ "rules/wgc303.ts": "70cdf211020b6bcf3d62d46368d3e753911700ec76478d4a2bb788f75d608c20",
32
+ "rules/wgc401.ts": "287a28258a6987eacf4228df8e57cdc6d5357d5efcb35a732fb01e4b7b8ce797",
33
+ "rules/wgc402.ts": "4dacefea0ead25987ded5f8f5b83d02d2aa957f3816a7d0cc602cc9872257458",
34
+ "rules/wgc403.ts": "156a8af8f8e419d36e740f4fc6f2ed7576a1692e3286e4c9ca6f9e9ccf772965",
35
+ "skills/chant-gcp.md": "6fd732fb5df2fd6561b4b704379f6779ff4ac12e3069ea4f641d9b222e0c3c81",
36
+ "skills/chant-gcp-security.md": "39a1c97b7425ed43b081257132449d11ce9d12f2d4755fc30d8b14bbc38f60bd",
37
+ "skills/chant-gcp-patterns.md": "a7ef31c1eb2f7244d3f73952c300472ef94c1eb09bd7a1003281b89299b6b704",
38
+ "skills/chant-gcp-gke.md": "be277019da9a722c851e47cd2dfb9c9536668948c3535fb20db7697e934c4e2b"
39
39
  },
40
- "composite": "b99c7c6bb17a778c"
40
+ "composite": "a66d209a7b1a390307e1603ac89a5552e0bd6b095fe7c49ee968a4cb7640d8d6"
41
41
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GCP",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-gcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Google Cloud lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -36,18 +36,18 @@
36
36
  "./types": "./dist/types/index.d.ts"
37
37
  },
38
38
  "scripts": {
39
- "generate": "bun run src/codegen/generate-cli.ts",
40
- "bundle": "bun run src/package-cli.ts",
41
- "validate": "bun run src/validate-cli.ts",
42
- "docs": "bun run src/codegen/docs-cli.ts",
43
- "prepack": "bun run generate && bun run bundle && bun run validate"
39
+ "generate": "tsx src/codegen/generate-cli.ts",
40
+ "bundle": "tsx src/package-cli.ts",
41
+ "validate": "tsx src/validate-cli.ts",
42
+ "docs": "tsx src/codegen/docs-cli.ts",
43
+ "prepack": "npm run generate && npm run bundle && npm run validate"
44
44
  },
45
45
  "dependencies": {
46
46
  "fflate": "^0.8.2",
47
47
  "js-yaml": "^4.1.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@intentius/chant": "0.1.4",
50
+ "@intentius/chant": "*",
51
51
  "typescript": "^5.9.3"
52
52
  },
53
53
  "peerDependencies": {
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  import { generateDocs } from "./docs";
3
3
 
4
4
  generateDocs({ verbose: true }).catch((err) => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { generateDocs } from "./docs";
3
3
 
4
4
  describe("generateDocs", () => {
@@ -200,7 +200,9 @@ export const reader = new IAMPolicyMember({
200
200
  slug: "config-connector-concepts",
201
201
  title: "Config Connector Concepts",
202
202
  description: "Resource structure, resourceRef, project binding, reconciliation lifecycle",
203
- content: `Every exported resource declaration becomes a Config Connector manifest document in the generated YAML.
203
+ content: `import Diagram from '../../components/Diagram.astro';
204
+
205
+ Every exported resource declaration becomes a Config Connector manifest document in the generated YAML.
204
206
 
205
207
  ## Resource structure
206
208
 
@@ -257,6 +259,8 @@ This injects the project annotation into every resource. Without it, Config Conn
257
259
 
258
260
  Config Connector resources go through a reconciliation lifecycle:
259
261
 
262
+ <Diagram name="config-connector-lifecycle" alt="Config Connector state machine: YAML applied to kubectl, then Pending → Reconciling → UpToDate, UpdateFailed, or DependencyNotReady with retry paths" caption="Config Connector reconciliation lifecycle" />
263
+
260
264
  | Status | Meaning |
261
265
  |--------|---------|
262
266
  | UpToDate | Resource matches desired state |
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
3
  * CLI entry point for GCP lexicon generation.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { generate } from "./generate";
3
3
 
4
4
  describe("generate", () => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { NamingStrategy } from "./naming";
3
3
  import type { GcpParseResult } from "../spec/parse";
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { packageLexicon } from "./package";
3
3
 
4
4
  describe("packageLexicon", () => {
@@ -3,9 +3,8 @@
3
3
  * with GCP-specific manifest building and skill collection.
4
4
  */
5
5
 
6
- import { createRequire } from "module";
7
6
  import { readFileSync } from "fs";
8
- const require = createRequire(import.meta.url);
7
+ import { gcpPlugin } from "../plugin";
9
8
  import { dirname } from "path";
10
9
  import { fileURLToPath } from "url";
11
10
  import {
@@ -32,8 +31,6 @@ export async function packageLexicon(opts: PackageOptions = {}): Promise<Package
32
31
  generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
33
32
 
34
33
  buildManifest: (_genResult) => {
35
- const { gcpPlugin } = require("../plugin");
36
-
37
34
  const pseudoParams: string[] = gcpPlugin.pseudoParameters?.() ?? [];
38
35
  const pseudoParameters: Record<string, string> = {};
39
36
  for (const p of pseudoParams) {
@@ -53,9 +50,7 @@ export async function packageLexicon(opts: PackageOptions = {}): Promise<Package
53
50
 
54
51
  srcDir: pkgDir,
55
52
 
56
- collectSkills: () => {
57
- const { gcpPlugin } = require("../plugin");
58
- const skillDefs = gcpPlugin.skills?.() ?? [];
53
+ collectSkills: () => { const skillDefs = gcpPlugin.skills?.() ?? [];
59
54
  return collectSkills(skillDefs);
60
55
  },
61
56
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { GkeCluster } from "./gke-cluster";
3
3
  import { CloudRunServiceComposite } from "./cloud-run-service";
4
4
  import { CloudSqlInstance } from "./cloud-sql-instance";
@@ -0,0 +1,232 @@
1
+ /**
2
+ * GkeCrdbRegion composite — GKE cluster + public DNS zone + per-region IAM for a CockroachDB region.
3
+ *
4
+ * Replaces the 2-file per-region GCP infra pattern (infra/cluster.ts + infra/dns.ts) with
5
+ * a single composite call. Handles the GKE cluster, the public DNS zone, and all per-region
6
+ * Workload Identity IAM bindings for both ExternalDNS and CockroachDB pods.
7
+ */
8
+
9
+ import { Composite } from "@intentius/chant";
10
+ import { GkeCluster } from "./gke-cluster";
11
+ import { DNSManagedZone, GCPServiceAccount, IAMPolicyMember } from "../generated";
12
+
13
+ export interface GkeCrdbRegionNodeConfig {
14
+ /** Machine type (default: "n2-standard-2"). */
15
+ machineType?: string;
16
+ /** Disk size per node in GB (default: 100). */
17
+ diskSizeGb?: number;
18
+ /** Initial/min node count (default: 1). */
19
+ nodeCount?: number;
20
+ /** Maximum node count for autoscaling (default: 3). */
21
+ maxNodeCount?: number;
22
+ }
23
+
24
+ export interface GkeCrdbRegionConfig {
25
+ /**
26
+ * GCP region (e.g. "us-east4").
27
+ * Used as both the cluster location and for NAT router naming.
28
+ */
29
+ region: string;
30
+ /** GKE cluster name (e.g. "gke-crdb-east"). Used as prefix for child resources. */
31
+ clusterName: string;
32
+ /** VPC network resource name. */
33
+ network: string;
34
+ /** Node subnet resource name for the cluster. */
35
+ subnetwork: string;
36
+ /** Public DNS domain for this region (e.g. "east.crdb.example.com"). */
37
+ domain: string;
38
+ /** GCP project ID for Workload Identity pool and service account names. */
39
+ project: string;
40
+ /**
41
+ * K8s namespace where CockroachDB pods run (e.g. "crdb-east").
42
+ * Used in the Workload Identity binding subject for CRDB pods.
43
+ */
44
+ crdbNamespace: string;
45
+ /**
46
+ * K8s ServiceAccount name for CockroachDB pods (default: "cockroachdb").
47
+ * Used in the Workload Identity binding subject.
48
+ */
49
+ crdbK8sSa?: string;
50
+ /**
51
+ * CIDR block for the GKE master's private endpoint (e.g. "172.16.0.0/28").
52
+ * Must be /28, unique per cluster, not overlapping with node/pod CIDRs.
53
+ */
54
+ masterCidr?: string;
55
+ /** Node pool configuration. */
56
+ nodeConfig?: GkeCrdbRegionNodeConfig;
57
+ /** GCP release channel for cluster upgrades (default: "REGULAR"). */
58
+ releaseChannel?: "RAPID" | "REGULAR" | "STABLE";
59
+ /** Optional backup bucket name to grant CRDB GSA storage.objectAdmin access. */
60
+ backupBucket?: string;
61
+ /** Additional labels for all resources. */
62
+ labels?: Record<string, string>;
63
+ /** Namespace for Config Connector resources. */
64
+ namespace?: string;
65
+ }
66
+
67
+ /**
68
+ * Create a GkeCrdbRegion composite — returns all GCP resources for one CockroachDB region:
69
+ * GKE cluster, public DNS zone, per-region GSAs, and Workload Identity IAM bindings.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { GkeCrdbRegion } from "@intentius/chant-lexicon-gcp";
74
+ *
75
+ * export const east = GkeCrdbRegion({
76
+ * region: "us-east4",
77
+ * clusterName: "gke-crdb-east",
78
+ * network: "crdb-multi-region",
79
+ * subnetwork: "crdb-multi-region-east-nodes",
80
+ * domain: "east.crdb.example.com",
81
+ * project: GCP_PROJECT_ID,
82
+ * crdbNamespace: "crdb-east",
83
+ * masterCidr: "172.16.0.0/28",
84
+ * });
85
+ * ```
86
+ */
87
+ export const GkeCrdbRegion = Composite<GkeCrdbRegionConfig>((props) => {
88
+ const {
89
+ region,
90
+ clusterName,
91
+ network,
92
+ subnetwork,
93
+ domain,
94
+ project,
95
+ crdbNamespace,
96
+ crdbK8sSa = "cockroachdb",
97
+ masterCidr = "172.16.0.0/28",
98
+ nodeConfig = {},
99
+ releaseChannel = "REGULAR",
100
+ backupBucket,
101
+ labels: extraLabels = {},
102
+ namespace,
103
+ } = props;
104
+
105
+ const {
106
+ machineType = "n2-standard-2",
107
+ diskSizeGb = 100,
108
+ nodeCount = 1,
109
+ maxNodeCount = 3,
110
+ } = nodeConfig;
111
+
112
+ const commonLabels: Record<string, string> = {
113
+ "app.kubernetes.io/name": clusterName,
114
+ "app.kubernetes.io/managed-by": "chant",
115
+ ...extraLabels,
116
+ };
117
+
118
+ const meta = (component: string, resourceName: string) => ({
119
+ name: resourceName,
120
+ ...(namespace && { namespace }),
121
+ labels: { ...commonLabels, "app.kubernetes.io/component": component },
122
+ });
123
+
124
+ // ── GKE Cluster ──────────────────────────────────────────────────────────────
125
+
126
+ const { cluster, nodePool, defaultPool } = GkeCluster({
127
+ name: clusterName,
128
+ location: region,
129
+ machineType,
130
+ diskSizeGb,
131
+ initialNodeCount: nodeCount,
132
+ minNodeCount: nodeCount,
133
+ maxNodeCount,
134
+ network,
135
+ subnetwork,
136
+ workloadIdentity: true,
137
+ privateNodes: true,
138
+ masterCidr,
139
+ releaseChannel,
140
+ labels: extraLabels,
141
+ ...(namespace && { namespace }),
142
+ });
143
+
144
+ // ── Public DNS Zone ───────────────────────────────────────────────────────────
145
+
146
+ const dnsZoneName = `${clusterName}-zone`;
147
+
148
+ const dnsZone = new DNSManagedZone({
149
+ metadata: meta("dns", dnsZoneName),
150
+ dnsName: `${domain}.`,
151
+ description: `CockroachDB ${region} UI — managed by chant`,
152
+ } as Record<string, unknown>);
153
+
154
+ // ── ExternalDNS Service Account ───────────────────────────────────────────────
155
+
156
+ const dnsGsaName = `${clusterName}-dns`;
157
+
158
+ const dnsGsa = new GCPServiceAccount({
159
+ metadata: meta("iam", dnsGsaName),
160
+ displayName: `CockroachDB ${region} ExternalDNS workload identity`,
161
+ } as Record<string, unknown>);
162
+
163
+ const dnsWiBinding = new IAMPolicyMember({
164
+ metadata: meta("iam", `${dnsGsaName}-wi`),
165
+ member: `serviceAccount:${project}.svc.id.goog[kube-system/external-dns-sa]`,
166
+ role: "roles/iam.workloadIdentityUser",
167
+ resourceRef: {
168
+ apiVersion: "iam.cnrm.cloud.google.com/v1beta1",
169
+ kind: "IAMServiceAccount",
170
+ name: dnsGsaName,
171
+ },
172
+ } as Record<string, unknown>);
173
+
174
+ const dnsAdminBinding = new IAMPolicyMember({
175
+ metadata: meta("iam", `${dnsGsaName}-admin`),
176
+ member: `serviceAccount:${dnsGsaName}@${project}.iam.gserviceaccount.com`,
177
+ role: "roles/dns.admin",
178
+ resourceRef: {
179
+ apiVersion: "resourcemanager.cnrm.cloud.google.com/v1beta1",
180
+ kind: "Project",
181
+ external: `projects/${project}`,
182
+ },
183
+ } as Record<string, unknown>);
184
+
185
+ // ── CockroachDB Service Account ───────────────────────────────────────────────
186
+
187
+ const crdbGsaName = `${clusterName}-crdb`;
188
+
189
+ const crdbGsa = new GCPServiceAccount({
190
+ metadata: meta("iam", crdbGsaName),
191
+ displayName: `CockroachDB ${region} workload identity`,
192
+ } as Record<string, unknown>);
193
+
194
+ const crdbWiBinding = new IAMPolicyMember({
195
+ metadata: meta("iam", `${crdbGsaName}-wi`),
196
+ member: `serviceAccount:${project}.svc.id.goog[${crdbNamespace}/${crdbK8sSa}]`,
197
+ role: "roles/iam.workloadIdentityUser",
198
+ resourceRef: {
199
+ apiVersion: "iam.cnrm.cloud.google.com/v1beta1",
200
+ kind: "IAMServiceAccount",
201
+ name: crdbGsaName,
202
+ },
203
+ } as Record<string, unknown>);
204
+
205
+ const result: Record<string, any> = {
206
+ cluster,
207
+ nodePool,
208
+ defaultPool,
209
+ dnsZone,
210
+ dnsGsa,
211
+ dnsWiBinding,
212
+ dnsAdminBinding,
213
+ crdbGsa,
214
+ crdbWiBinding,
215
+ };
216
+
217
+ // Optional: GCS backup access for CRDB GSA
218
+ if (backupBucket) {
219
+ result.crdbBackupBinding = new IAMPolicyMember({
220
+ metadata: meta("iam", `${crdbGsaName}-backup`),
221
+ member: `serviceAccount:${crdbGsaName}@${project}.iam.gserviceaccount.com`,
222
+ role: "roles/storage.objectAdmin",
223
+ resourceRef: {
224
+ apiVersion: "storage.cnrm.cloud.google.com/v1beta1",
225
+ kind: "StorageBucket",
226
+ name: backupBucket,
227
+ },
228
+ } as Record<string, unknown>);
229
+ }
230
+
231
+ return result;
232
+ }, "GkeCrdbRegion");
@@ -20,3 +20,7 @@ export { SecureProject } from "./secure-project";
20
20
  export type { SecureProjectProps } from "./secure-project";
21
21
  export { MemorystoreRedis } from "./memorystore-redis";
22
22
  export type { MemorystoreRedisProps } from "./memorystore-redis";
23
+ export { MultiRegionVpc } from "./multi-region-vpc";
24
+ export type { MultiRegionVpcConfig, MultiRegionVpcRegion } from "./multi-region-vpc";
25
+ export { GkeCrdbRegion } from "./gke-crdb-region";
26
+ export type { GkeCrdbRegionConfig, GkeCrdbRegionNodeConfig } from "./gke-crdb-region";
@@ -0,0 +1,157 @@
1
+ /**
2
+ * MultiRegionVpc composite — VPC + 2 subnets per region + Router + RouterNAT per region + Firewall.
3
+ *
4
+ * Collapses the boilerplate of a multi-region GKE networking stack into a single call.
5
+ * Each region gets node and pod subnets, a Cloud Router, and a Cloud NAT gateway.
6
+ * A single allow-internal firewall rule covers all region CIDRs.
7
+ */
8
+
9
+ import { Composite } from "@intentius/chant";
10
+ import { VPCNetwork, Subnetwork, Router, RouterNAT, Firewall } from "../generated";
11
+
12
+ export interface MultiRegionVpcRegion {
13
+ /** GCP region name (e.g. "us-east4"). Used as the Router/NAT region. */
14
+ region: string;
15
+ /**
16
+ * Short alias used in resource names (e.g. "east").
17
+ * Defaults to the full region string. Use this to keep names concise.
18
+ */
19
+ regionAlias?: string;
20
+ /** IP CIDR for GKE node subnet (e.g. "10.1.0.0/20"). */
21
+ nodeSubnetCidr: string;
22
+ /** IP CIDR for GKE pod subnet (e.g. "10.1.16.0/20"). */
23
+ podSubnetCidr: string;
24
+ }
25
+
26
+ export interface MultiRegionVpcConfig {
27
+ /** VPC network name. Used as prefix for all sub-resources. */
28
+ name: string;
29
+ /** One entry per GCP region. */
30
+ regions: MultiRegionVpcRegion[];
31
+ /** Enable VPC flow logs on all subnets (default: false). */
32
+ enableFlowLogs?: boolean;
33
+ /** Additional labels for all resources. */
34
+ labels?: Record<string, string>;
35
+ /** Namespace for Config Connector resources. */
36
+ namespace?: string;
37
+ }
38
+
39
+ /**
40
+ * Create a MultiRegionVpc composite — one VPC with subnets, Cloud NAT, and an
41
+ * allow-internal firewall for every region in the array.
42
+ *
43
+ * The composite eliminates the per-region Router/NAT boilerplate that arises when
44
+ * `VpcNetwork` only handles a single NAT region and the rest must be wired manually.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { MultiRegionVpc } from "@intentius/chant-lexicon-gcp";
49
+ *
50
+ * export const network = MultiRegionVpc({
51
+ * name: "crdb-multi-region",
52
+ * regions: [
53
+ * { region: "us-east4", regionAlias: "east", nodeSubnetCidr: "10.1.0.0/20", podSubnetCidr: "10.1.16.0/20" },
54
+ * { region: "us-central1", regionAlias: "central", nodeSubnetCidr: "10.2.0.0/20", podSubnetCidr: "10.2.16.0/20" },
55
+ * { region: "us-west1", regionAlias: "west", nodeSubnetCidr: "10.3.0.0/20", podSubnetCidr: "10.3.16.0/20" },
56
+ * ],
57
+ * });
58
+ * ```
59
+ */
60
+ export const MultiRegionVpc = Composite<MultiRegionVpcConfig>((props) => {
61
+ const {
62
+ name,
63
+ regions,
64
+ enableFlowLogs = false,
65
+ labels: extraLabels = {},
66
+ namespace,
67
+ } = props;
68
+
69
+ const commonLabels: Record<string, string> = {
70
+ "app.kubernetes.io/name": name,
71
+ "app.kubernetes.io/managed-by": "chant",
72
+ ...extraLabels,
73
+ };
74
+
75
+ const meta = (component: string, resourceName?: string) => ({
76
+ metadata: {
77
+ name: resourceName ?? name,
78
+ ...(namespace && { namespace }),
79
+ labels: { ...commonLabels, "app.kubernetes.io/component": component },
80
+ },
81
+ });
82
+
83
+ const network = new VPCNetwork({
84
+ ...meta("network"),
85
+ autoCreateSubnetworks: false,
86
+ routingMode: "REGIONAL",
87
+ } as Record<string, unknown>);
88
+
89
+ const subnetEntries: Record<string, unknown> = {};
90
+ const routerEntries: Record<string, unknown> = {};
91
+ const natEntries: Record<string, unknown> = {};
92
+ const allCidrs: string[] = [];
93
+
94
+ for (const r of regions) {
95
+ const alias = r.regionAlias ?? r.region;
96
+
97
+ allCidrs.push(r.nodeSubnetCidr, r.podSubnetCidr);
98
+
99
+ const flowConfig = enableFlowLogs
100
+ ? { logConfig: { enable: true, aggregationInterval: "INTERVAL_5_SEC", flowSampling: 0.5 } }
101
+ : {};
102
+
103
+ subnetEntries[`subnet_${alias}_nodes`] = new Subnetwork({
104
+ ...meta("subnet", `${name}-${alias}-nodes`),
105
+ networkRef: { name },
106
+ ipCidrRange: r.nodeSubnetCidr,
107
+ region: r.region,
108
+ privateIpGoogleAccess: true,
109
+ ...flowConfig,
110
+ } as Record<string, unknown>);
111
+
112
+ subnetEntries[`subnet_${alias}_pods`] = new Subnetwork({
113
+ ...meta("subnet", `${name}-${alias}-pods`),
114
+ networkRef: { name },
115
+ ipCidrRange: r.podSubnetCidr,
116
+ region: r.region,
117
+ privateIpGoogleAccess: true,
118
+ ...flowConfig,
119
+ } as Record<string, unknown>);
120
+
121
+ const routerName = `${name}-${alias}`;
122
+
123
+ routerEntries[`router_${alias}`] = new Router({
124
+ ...meta("router", routerName),
125
+ networkRef: { name },
126
+ region: r.region,
127
+ } as Record<string, unknown>);
128
+
129
+ natEntries[`nat_${alias}`] = new RouterNAT({
130
+ ...meta("nat", routerName),
131
+ routerRef: { name: routerName },
132
+ region: r.region,
133
+ natIpAllocateOption: "AUTO_ONLY",
134
+ sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES",
135
+ } as Record<string, unknown>);
136
+ }
137
+
138
+ // Allow all TCP/UDP/ICMP between every subnet CIDR in the VPC.
139
+ const firewallInternal = new Firewall({
140
+ ...meta("firewall", `${name}-allow-internal`),
141
+ networkRef: { name },
142
+ allow: [
143
+ { protocol: "tcp", ports: ["0-65535"] },
144
+ { protocol: "udp", ports: ["0-65535"] },
145
+ { protocol: "icmp" },
146
+ ],
147
+ sourceRanges: allCidrs,
148
+ } as Record<string, unknown>);
149
+
150
+ return {
151
+ network,
152
+ ...subnetEntries,
153
+ ...routerEntries,
154
+ ...natEntries,
155
+ firewallInternal,
156
+ };
157
+ }, "MultiRegionVpc");
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { existsSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import {
3
3
  defaultLabels,
4
4
  defaultAnnotations,
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { GcpGenerator } from "./generator";
3
3
 
4
4
  const generator = new GcpGenerator();
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { GcpParser } from "./parser";
3
3
  import { GcpGenerator } from "./generator";
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { GcpParser } from "./parser";
3
3
 
4
4
  const parser = new GcpParser();
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
package/src/index.ts CHANGED
@@ -15,14 +15,14 @@ export { GCP, ProjectId, Region, Zone } from "./pseudo";
15
15
  export { GcpAnnotations } from "./variables";
16
16
 
17
17
  // Generated entities — export everything from generated index
18
- // After running `bun run generate`, this re-exports all Config Connector resource classes
18
+ // After running `npm run generate`, this re-exports all Config Connector resource classes
19
19
  export * from "./generated/index";
20
20
 
21
21
  // Composites
22
22
  export {
23
23
  GkeCluster, CloudRunServiceComposite, CloudSqlInstance, GcsBucket, VpcNetwork,
24
24
  PubSubPipeline, CloudFunctionWithTrigger, PrivateService, ManagedCertificate, SecureProject,
25
- MemorystoreRedis,
25
+ MemorystoreRedis, MultiRegionVpc, GkeCrdbRegion,
26
26
  } from "./composites/index";
27
27
  export type {
28
28
  GkeClusterProps,
@@ -36,6 +36,8 @@ export type {
36
36
  ManagedCertificateProps,
37
37
  SecureProjectProps,
38
38
  MemorystoreRedisProps,
39
+ MultiRegionVpcConfig, MultiRegionVpcRegion,
40
+ GkeCrdbRegionConfig, GkeCrdbRegionNodeConfig,
39
41
  } from "./composites/index";
40
42
 
41
43
  // IAM role constants
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import {
3
3
  parseGcpManifests,
4
4
  isConfigConnectorResource,
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc101 } from "./wgc101";
3
3
  import { wgc102 } from "./wgc102";
4
4
  import { wgc103 } from "./wgc103";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc101 } from "./wgc101";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc102 } from "./wgc102";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc103 } from "./wgc103";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc104 } from "./wgc104";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc105 } from "./wgc105";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc106 } from "./wgc106";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc107 } from "./wgc107";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc108 } from "./wgc108";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc109 } from "./wgc109";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc110 } from "./wgc110";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc111 } from "./wgc111";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc112 } from "./wgc112";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc113 } from "./wgc113";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc201 } from "./wgc201";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc202 } from "./wgc202";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc203 } from "./wgc203";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc204 } from "./wgc204";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc301 } from "./wgc301";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc302 } from "./wgc302";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc303 } from "./wgc303";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc401 } from "./wgc401";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc402 } from "./wgc402";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { wgc403 } from "./wgc403";
3
3
 
4
4
  function makeCtx(yaml: string) {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { hardcodedProjectRule } from "./hardcoded-project";
3
3
  import { hardcodedRegionRule } from "./hardcoded-region";
4
4
  import { publicIamRule } from "./public-iam";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -1,6 +1,6 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  /**
3
- * Thin entry point for `bun run bundle` in lexicon-gcp.
3
+ * Thin entry point for `npm run bundle` in lexicon-gcp.
4
4
  * Generates src/generated/ files and writes dist/ bundle.
5
5
  *
6
6
  * NOTE: Uses top-level await (matching AWS/Azure pattern) to avoid
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { gcpPlugin } from "./plugin";
3
3
  import { isLexiconPlugin } from "@intentius/chant/lexicon";
4
4
 
package/src/plugin.ts CHANGED
@@ -5,9 +5,7 @@
5
5
  * import parsing, and code generation for GCP Config Connector resources.
6
6
  */
7
7
 
8
- import { createRequire } from "module";
9
8
  import type { LexiconPlugin, InitTemplateSet, ResourceMetadata } from "@intentius/chant/lexicon";
10
- const require = createRequire(import.meta.url);
11
9
  import type { LintRule } from "@intentius/chant/lint/rule";
12
10
  import type { TemplateParser } from "@intentius/chant/import/parser";
13
11
  import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
@@ -17,15 +15,19 @@ import { createSkillsLoader, createDiffTool, createCatalogResource } from "@inte
17
15
  import { join, dirname } from "path";
18
16
  import { fileURLToPath } from "url";
19
17
  import { gcpSerializer } from "./serializer";
18
+ import { hardcodedProjectRule } from "./lint/rules/hardcoded-project";
19
+ import { hardcodedRegionRule } from "./lint/rules/hardcoded-region";
20
+ import { publicIamRule } from "./lint/rules/public-iam";
21
+ import { GcpParser } from "./import/parser";
22
+ import { GcpGenerator } from "./import/generator";
23
+ import { gcpCompletions } from "./lsp/completions";
24
+ import { gcpHover } from "./lsp/hover";
20
25
 
21
26
  export const gcpPlugin: LexiconPlugin = {
22
27
  name: "gcp",
23
28
  serializer: gcpSerializer,
24
29
 
25
30
  lintRules(): LintRule[] {
26
- const { hardcodedProjectRule } = require("./lint/rules/hardcoded-project");
27
- const { hardcodedRegionRule } = require("./lint/rules/hardcoded-region");
28
- const { publicIamRule } = require("./lint/rules/public-iam");
29
31
  return [hardcodedProjectRule, hardcodedRegionRule, publicIamRule];
30
32
  },
31
33
 
@@ -190,22 +192,18 @@ export const bucketReader = new IAMPolicyMember({
190
192
  },
191
193
 
192
194
  templateParser(): TemplateParser {
193
- const { GcpParser } = require("./import/parser");
194
195
  return new GcpParser();
195
196
  },
196
197
 
197
198
  templateGenerator(): TypeScriptGenerator {
198
- const { GcpGenerator } = require("./import/generator");
199
199
  return new GcpGenerator();
200
200
  },
201
201
 
202
202
  completionProvider(ctx: CompletionContext): CompletionItem[] {
203
- const { gcpCompletions } = require("./lsp/completions");
204
203
  return gcpCompletions(ctx);
205
204
  },
206
205
 
207
206
  hoverProvider(ctx: HoverContext): HoverInfo | undefined {
208
- const { gcpHover } = require("./lsp/hover");
209
207
  return gcpHover(ctx);
210
208
  },
211
209
 
@@ -1,4 +1,4 @@
1
- import { describe, it, expect } from "bun:test";
1
+ import { describe, it, expect } from "vitest";
2
2
  import { GCP, ProjectId, Region, Zone, PseudoParameter } from "./pseudo";
3
3
 
4
4
  describe("GCP pseudo-parameters", () => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { gcpSerializer } from "./serializer";
3
3
  import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
4
4
  import {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { getCachePath, KCC_VERSION } from "./fetch";
3
3
 
4
4
  describe("fetch", () => {
@@ -6,7 +6,7 @@ describe("fetch", () => {
6
6
  const path = getCachePath();
7
7
  expect(path).toContain(KCC_VERSION);
8
8
  expect(path).toContain(".chant");
9
- expect(path).toEndWith(".tar.gz");
9
+ expect(path.endsWith(".tar.gz")).toBe(true);
10
10
  });
11
11
 
12
12
  test("custom version in cache path", () => {
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env tsx
2
2
  import { validate } from "./validate";
3
3
  import { printValidationResult } from "@intentius/chant/codegen/validate";
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { existsSync } from "fs";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";