@crossdelta/infrastructure 0.2.25 → 0.2.27

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.
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Generate environment variables for local development.
4
+ *
5
+ * This script:
6
+ * 1. Loads secrets from Pulumi config (dev stack) if available
7
+ * 2. Discovers services from infra/services/*.ts (parses files without importing)
8
+ * 3. Generates service URLs and ports for localhost
9
+ * 4. Writes .env.local to the workspace root
10
+ *
11
+ * Usage: generate-env [--no-pulumi]
12
+ */
13
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
14
+ import { join } from 'node:path'
15
+
16
+ type PulumiConfigEntry = {
17
+ value?: string
18
+ secret?: boolean
19
+ }
20
+
21
+ interface MinimalServiceConfig {
22
+ name: string
23
+ containerPort?: number
24
+ httpPort?: number
25
+ internalPorts?: number[]
26
+ internalUrl?: string
27
+ }
28
+
29
+ const toEnvKey = (name: string): string => name.toUpperCase().replace(/-/g, '_')
30
+
31
+ const resolveNumber = (value: string, fileContent: string): number | undefined => {
32
+ const literal = Number.parseInt(value, 10)
33
+ if (!Number.isNaN(literal)) return literal
34
+
35
+ const varMatch = fileContent.match(new RegExp(`(?:const|let|var)\\s+${value}\\s*=\\s*(\\d+)`))
36
+ return varMatch?.[1] ? Number.parseInt(varMatch[1], 10) : undefined
37
+ }
38
+
39
+ const extract = {
40
+ string: (content: string, pattern: RegExp): string | undefined =>
41
+ content.match(pattern)?.[1],
42
+
43
+ number: (content: string, pattern: RegExp): number | undefined => {
44
+ const match = content.match(pattern)?.[1]
45
+ return match ? resolveNumber(match, content) : undefined
46
+ },
47
+
48
+ numberArray: (content: string, pattern: RegExp): number[] | undefined => {
49
+ const match = content.match(pattern)?.[1]
50
+ if (!match) return undefined
51
+
52
+ const numbers = match
53
+ .split(',')
54
+ .map((v) => resolveNumber(v.trim(), content))
55
+ .filter((n): n is number => n !== undefined)
56
+
57
+ return numbers.length > 0 ? numbers : undefined
58
+ },
59
+ }
60
+
61
+ const findWorkspaceRoot = (): string => {
62
+ let dir = process.cwd()
63
+ while (!existsSync(join(dir, 'bun.lock')) && !existsSync(join(dir, 'package-lock.json'))) {
64
+ const parent = join(dir, '..')
65
+ if (parent === dir) throw new Error('Could not locate workspace root')
66
+ dir = parent
67
+ }
68
+ return dir
69
+ }
70
+
71
+ const loadPulumiConfig = async (infraDir: string, stack: string): Promise<string[]> => {
72
+ try {
73
+ const result = Bun.spawnSync(['pulumi', 'config', '--show-secrets', '--json', '--stack', stack, '--cwd', infraDir])
74
+ if (result.exitCode !== 0) return []
75
+
76
+ const config = JSON.parse(result.stdout.toString()) as Record<string, PulumiConfigEntry>
77
+
78
+ return Object.entries(config)
79
+ .filter(([key]) => key.includes(':'))
80
+ .filter(([, entry]) => entry.value !== undefined && entry.value !== null && entry.value !== 'undefined')
81
+ .map(([fullKey, entry]) => {
82
+ const [, rawKey] = fullKey.split(':')
83
+ return `${rawKey}=${entry.value}`
84
+ })
85
+ } catch {
86
+ return []
87
+ }
88
+ }
89
+
90
+ const discoverServices = (servicesDir: string): MinimalServiceConfig[] => {
91
+ if (!existsSync(servicesDir)) return []
92
+
93
+ const files = readdirSync(servicesDir).filter(
94
+ (file) => file.endsWith('.ts') && file !== 'index.ts'
95
+ )
96
+
97
+ return files.map((file) => {
98
+ const content = readFileSync(join(servicesDir, file), 'utf-8')
99
+
100
+ return {
101
+ name: extract.string(content, /name:\s*['"]([^'"]+)['"]/) ?? file.replace('.ts', ''),
102
+ containerPort: extract.number(content, /containerPort:\s*(\w+)/),
103
+ httpPort: extract.number(content, /httpPort:\s*(\w+)/),
104
+ internalPorts: extract.numberArray(content, /internalPorts:\s*\[([^\]]+)\]/),
105
+ internalUrl: extract.string(content, /internalUrl:\s*[`'"]([^`'"]+)[`'"]/),
106
+ }
107
+ })
108
+ }
109
+
110
+ const getServicePort = (config: MinimalServiceConfig): number | undefined => {
111
+ if (config.containerPort) return config.containerPort
112
+ if (config.httpPort) return config.httpPort
113
+ if (config.internalPorts?.[0]) return config.internalPorts[0]
114
+ return undefined
115
+ }
116
+
117
+ const getLocalUrl = (config: MinimalServiceConfig): string | undefined => {
118
+ const port = getServicePort(config)
119
+ if (!port) return undefined
120
+
121
+ if (config.internalUrl) {
122
+ const protocolMatch = config.internalUrl.match(/^(\w+):\/\//)
123
+ if (protocolMatch) {
124
+ return `${protocolMatch[1]}://localhost:${port}`
125
+ }
126
+ }
127
+
128
+ return `http://localhost:${port}`
129
+ }
130
+
131
+ const main = async () => {
132
+ const noPulumi = process.argv.includes('--no-pulumi')
133
+ const workspaceRootDir = findWorkspaceRoot()
134
+ const infraDir = join(workspaceRootDir, 'infra')
135
+ const servicesDir = join(infraDir, 'services')
136
+
137
+ const envLines: string[] = ['# Generated .env.local']
138
+
139
+ // Load Pulumi secrets if available and not disabled
140
+ if (!noPulumi && existsSync(join(infraDir, 'Pulumi.yaml'))) {
141
+ const pulumiEnvs = await loadPulumiConfig(infraDir, 'dev')
142
+ if (pulumiEnvs.length > 0) {
143
+ envLines.push('', '# Pulumi Secrets')
144
+ envLines.push(...pulumiEnvs)
145
+ }
146
+ }
147
+
148
+ // Discover services
149
+ const serviceConfigs = discoverServices(servicesDir)
150
+
151
+ if (serviceConfigs.length > 0) {
152
+ envLines.push('', '# Service URLs')
153
+ for (const config of serviceConfigs) {
154
+ const url = getLocalUrl(config)
155
+ if (url) {
156
+ envLines.push(`${toEnvKey(config.name)}_URL=${url}`)
157
+ }
158
+ }
159
+
160
+ envLines.push('', '# Service Ports')
161
+ for (const config of serviceConfigs) {
162
+ const port = getServicePort(config)
163
+ if (port) {
164
+ envLines.push(`${toEnvKey(config.name)}_PORT=${port}`)
165
+ }
166
+ }
167
+
168
+ console.log(`✅ Discovered ${serviceConfigs.length} services`)
169
+ }
170
+
171
+ writeFileSync(join(workspaceRootDir, '.env.local'), `${envLines.join('\n')}\n`)
172
+ console.log(`✅ .env.local generated at ${workspaceRootDir}`)
173
+ }
174
+
175
+ main().catch((err) => {
176
+ console.error('❌', err.message)
177
+ process.exit(1)
178
+ })
package/dist/index.cjs CHANGED
@@ -334344,10 +334344,37 @@ function buildServicePortEnvs(serviceConfigs) {
334344
334344
  }
334345
334345
  // lib/runtimes/doks/cert-manager.ts
334346
334346
  var k8s = __toESM(require_kubernetes(), 1);
334347
+ var CERT_MANAGER_DEFAULTS = {
334348
+ resources: {
334349
+ requests: { cpu: "10m", memory: "32Mi" },
334350
+ limits: { cpu: "100m", memory: "128Mi" }
334351
+ },
334352
+ webhook: {
334353
+ timeoutSeconds: 25,
334354
+ resources: {
334355
+ requests: { cpu: "10m", memory: "32Mi" },
334356
+ limits: { cpu: "50m", memory: "64Mi" }
334357
+ }
334358
+ },
334359
+ cainjector: {
334360
+ resources: {
334361
+ requests: { cpu: "10m", memory: "32Mi" },
334362
+ limits: { cpu: "50m", memory: "128Mi" }
334363
+ }
334364
+ }
334365
+ };
334347
334366
  function deployCertManager(provider, config2) {
334348
334367
  const namespace = "cert-manager";
334349
334368
  const isStaging = config2.staging ?? false;
334350
334369
  const issuerName = isStaging ? "letsencrypt-staging" : "letsencrypt-production";
334370
+ const resources = config2.resources ?? CERT_MANAGER_DEFAULTS.resources;
334371
+ const webhook = {
334372
+ timeoutSeconds: config2.webhook?.timeoutSeconds ?? CERT_MANAGER_DEFAULTS.webhook.timeoutSeconds,
334373
+ resources: config2.webhook?.resources ?? CERT_MANAGER_DEFAULTS.webhook.resources
334374
+ };
334375
+ const cainjector = {
334376
+ resources: config2.cainjector?.resources ?? CERT_MANAGER_DEFAULTS.cainjector.resources
334377
+ };
334351
334378
  const release = new k8s.helm.v3.Release("cert-manager", {
334352
334379
  name: "cert-manager",
334353
334380
  namespace,
@@ -334362,19 +334389,20 @@ function deployCertManager(provider, config2) {
334362
334389
  enabled: true
334363
334390
  },
334364
334391
  resources: {
334365
- requests: { cpu: "10m", memory: "32Mi" },
334366
- limits: { cpu: "100m", memory: "128Mi" }
334392
+ requests: resources.requests,
334393
+ limits: resources.limits
334367
334394
  },
334368
334395
  webhook: {
334396
+ timeoutSeconds: webhook.timeoutSeconds,
334369
334397
  resources: {
334370
- requests: { cpu: "10m", memory: "32Mi" },
334371
- limits: { cpu: "50m", memory: "64Mi" }
334398
+ requests: webhook.resources.requests,
334399
+ limits: webhook.resources.limits
334372
334400
  }
334373
334401
  },
334374
334402
  cainjector: {
334375
334403
  resources: {
334376
- requests: { cpu: "10m", memory: "32Mi" },
334377
- limits: { cpu: "50m", memory: "128Mi" }
334404
+ requests: cainjector.resources.requests,
334405
+ limits: cainjector.resources.limits
334378
334406
  }
334379
334407
  }
334380
334408
  },
@@ -334539,6 +334567,20 @@ var NATS_DEFAULTS = {
334539
334567
  resources: {
334540
334568
  requests: { cpu: "100m", memory: "256Mi" },
334541
334569
  limits: { cpu: "500m", memory: "512Mi" }
334570
+ },
334571
+ reloader: {
334572
+ enabled: true,
334573
+ resources: {
334574
+ requests: { cpu: "5m", memory: "16Mi" },
334575
+ limits: { cpu: "50m", memory: "64Mi" }
334576
+ }
334577
+ },
334578
+ natsBox: {
334579
+ enabled: true,
334580
+ resources: {
334581
+ requests: { cpu: "5m", memory: "16Mi" },
334582
+ limits: { cpu: "50m", memory: "64Mi" }
334583
+ }
334542
334584
  }
334543
334585
  };
334544
334586
  function deployNats(provider, namespace, config2 = {}) {
@@ -334550,6 +334592,14 @@ function deployNats(provider, namespace, config2 = {}) {
334550
334592
  memoryStorageSize: config2.jetstream?.memoryStorageSize ?? NATS_DEFAULTS.jetstream.memoryStorageSize
334551
334593
  };
334552
334594
  const resources = config2.resources ?? NATS_DEFAULTS.resources;
334595
+ const reloader = {
334596
+ enabled: config2.reloader?.enabled ?? NATS_DEFAULTS.reloader.enabled,
334597
+ resources: config2.reloader?.resources ?? NATS_DEFAULTS.reloader.resources
334598
+ };
334599
+ const natsBox = {
334600
+ enabled: config2.natsBox?.enabled ?? NATS_DEFAULTS.natsBox.enabled,
334601
+ resources: config2.natsBox?.resources ?? NATS_DEFAULTS.natsBox.resources
334602
+ };
334553
334603
  const helmValues = {
334554
334604
  config: {
334555
334605
  cluster: {
@@ -334574,9 +334624,31 @@ function deployNats(provider, namespace, config2 = {}) {
334574
334624
  }
334575
334625
  },
334576
334626
  container: {
334577
- resources: {
334578
- requests: resources.requests,
334579
- limits: resources.limits
334627
+ merge: {
334628
+ resources: {
334629
+ requests: resources.requests,
334630
+ limits: resources.limits
334631
+ }
334632
+ }
334633
+ },
334634
+ reloader: {
334635
+ enabled: reloader.enabled,
334636
+ merge: {
334637
+ resources: {
334638
+ requests: reloader.resources.requests,
334639
+ limits: reloader.resources.limits
334640
+ }
334641
+ }
334642
+ },
334643
+ natsBox: {
334644
+ enabled: natsBox.enabled,
334645
+ container: {
334646
+ merge: {
334647
+ resources: {
334648
+ requests: natsBox.resources.requests,
334649
+ limits: natsBox.resources.limits
334650
+ }
334651
+ }
334580
334652
  }
334581
334653
  },
334582
334654
  service: {
package/dist/index.js CHANGED
@@ -334266,10 +334266,37 @@ function buildServicePortEnvs(serviceConfigs) {
334266
334266
  }
334267
334267
  // lib/runtimes/doks/cert-manager.ts
334268
334268
  var k8s = __toESM(require_kubernetes(), 1);
334269
+ var CERT_MANAGER_DEFAULTS = {
334270
+ resources: {
334271
+ requests: { cpu: "10m", memory: "32Mi" },
334272
+ limits: { cpu: "100m", memory: "128Mi" }
334273
+ },
334274
+ webhook: {
334275
+ timeoutSeconds: 25,
334276
+ resources: {
334277
+ requests: { cpu: "10m", memory: "32Mi" },
334278
+ limits: { cpu: "50m", memory: "64Mi" }
334279
+ }
334280
+ },
334281
+ cainjector: {
334282
+ resources: {
334283
+ requests: { cpu: "10m", memory: "32Mi" },
334284
+ limits: { cpu: "50m", memory: "128Mi" }
334285
+ }
334286
+ }
334287
+ };
334269
334288
  function deployCertManager(provider, config2) {
334270
334289
  const namespace = "cert-manager";
334271
334290
  const isStaging = config2.staging ?? false;
334272
334291
  const issuerName = isStaging ? "letsencrypt-staging" : "letsencrypt-production";
334292
+ const resources = config2.resources ?? CERT_MANAGER_DEFAULTS.resources;
334293
+ const webhook = {
334294
+ timeoutSeconds: config2.webhook?.timeoutSeconds ?? CERT_MANAGER_DEFAULTS.webhook.timeoutSeconds,
334295
+ resources: config2.webhook?.resources ?? CERT_MANAGER_DEFAULTS.webhook.resources
334296
+ };
334297
+ const cainjector = {
334298
+ resources: config2.cainjector?.resources ?? CERT_MANAGER_DEFAULTS.cainjector.resources
334299
+ };
334273
334300
  const release = new k8s.helm.v3.Release("cert-manager", {
334274
334301
  name: "cert-manager",
334275
334302
  namespace,
@@ -334284,19 +334311,20 @@ function deployCertManager(provider, config2) {
334284
334311
  enabled: true
334285
334312
  },
334286
334313
  resources: {
334287
- requests: { cpu: "10m", memory: "32Mi" },
334288
- limits: { cpu: "100m", memory: "128Mi" }
334314
+ requests: resources.requests,
334315
+ limits: resources.limits
334289
334316
  },
334290
334317
  webhook: {
334318
+ timeoutSeconds: webhook.timeoutSeconds,
334291
334319
  resources: {
334292
- requests: { cpu: "10m", memory: "32Mi" },
334293
- limits: { cpu: "50m", memory: "64Mi" }
334320
+ requests: webhook.resources.requests,
334321
+ limits: webhook.resources.limits
334294
334322
  }
334295
334323
  },
334296
334324
  cainjector: {
334297
334325
  resources: {
334298
- requests: { cpu: "10m", memory: "32Mi" },
334299
- limits: { cpu: "50m", memory: "128Mi" }
334326
+ requests: cainjector.resources.requests,
334327
+ limits: cainjector.resources.limits
334300
334328
  }
334301
334329
  }
334302
334330
  },
@@ -334461,6 +334489,20 @@ var NATS_DEFAULTS = {
334461
334489
  resources: {
334462
334490
  requests: { cpu: "100m", memory: "256Mi" },
334463
334491
  limits: { cpu: "500m", memory: "512Mi" }
334492
+ },
334493
+ reloader: {
334494
+ enabled: true,
334495
+ resources: {
334496
+ requests: { cpu: "5m", memory: "16Mi" },
334497
+ limits: { cpu: "50m", memory: "64Mi" }
334498
+ }
334499
+ },
334500
+ natsBox: {
334501
+ enabled: true,
334502
+ resources: {
334503
+ requests: { cpu: "5m", memory: "16Mi" },
334504
+ limits: { cpu: "50m", memory: "64Mi" }
334505
+ }
334464
334506
  }
334465
334507
  };
334466
334508
  function deployNats(provider, namespace, config2 = {}) {
@@ -334472,6 +334514,14 @@ function deployNats(provider, namespace, config2 = {}) {
334472
334514
  memoryStorageSize: config2.jetstream?.memoryStorageSize ?? NATS_DEFAULTS.jetstream.memoryStorageSize
334473
334515
  };
334474
334516
  const resources = config2.resources ?? NATS_DEFAULTS.resources;
334517
+ const reloader = {
334518
+ enabled: config2.reloader?.enabled ?? NATS_DEFAULTS.reloader.enabled,
334519
+ resources: config2.reloader?.resources ?? NATS_DEFAULTS.reloader.resources
334520
+ };
334521
+ const natsBox = {
334522
+ enabled: config2.natsBox?.enabled ?? NATS_DEFAULTS.natsBox.enabled,
334523
+ resources: config2.natsBox?.resources ?? NATS_DEFAULTS.natsBox.resources
334524
+ };
334475
334525
  const helmValues = {
334476
334526
  config: {
334477
334527
  cluster: {
@@ -334496,9 +334546,31 @@ function deployNats(provider, namespace, config2 = {}) {
334496
334546
  }
334497
334547
  },
334498
334548
  container: {
334499
- resources: {
334500
- requests: resources.requests,
334501
- limits: resources.limits
334549
+ merge: {
334550
+ resources: {
334551
+ requests: resources.requests,
334552
+ limits: resources.limits
334553
+ }
334554
+ }
334555
+ },
334556
+ reloader: {
334557
+ enabled: reloader.enabled,
334558
+ merge: {
334559
+ resources: {
334560
+ requests: reloader.resources.requests,
334561
+ limits: reloader.resources.limits
334562
+ }
334563
+ }
334564
+ },
334565
+ natsBox: {
334566
+ enabled: natsBox.enabled,
334567
+ container: {
334568
+ merge: {
334569
+ resources: {
334570
+ requests: natsBox.resources.requests,
334571
+ limits: natsBox.resources.limits
334572
+ }
334573
+ }
334502
334574
  }
334503
334575
  },
334504
334576
  service: {
@@ -5,11 +5,26 @@
5
5
  * with Let's Encrypt.
6
6
  */
7
7
  import * as k8s from '@pulumi/kubernetes';
8
+ import type { K8sResourceConfig } from './types';
8
9
  export interface CertManagerConfig {
9
10
  /** Email for Let's Encrypt notifications */
10
11
  email: string;
11
12
  /** Use Let's Encrypt staging (for testing) or production */
12
13
  staging?: boolean;
14
+ /** Resource configuration for main cert-manager controller */
15
+ resources?: K8sResourceConfig;
16
+ /** Webhook configuration */
17
+ webhook?: {
18
+ /** Timeout in seconds (must be 1-29 for DOKS compatibility, defaults to 25) */
19
+ timeoutSeconds?: number;
20
+ /** Resource configuration */
21
+ resources?: K8sResourceConfig;
22
+ };
23
+ /** CA Injector configuration */
24
+ cainjector?: {
25
+ /** Resource configuration */
26
+ resources?: K8sResourceConfig;
27
+ };
13
28
  }
14
29
  export interface CertManagerResult {
15
30
  /** The Helm release */
@@ -291,8 +291,22 @@ export interface NatsConfig {
291
291
  /** Password for client connections */
292
292
  password?: pulumi.Input<string>;
293
293
  };
294
- /** Resource configuration */
294
+ /** Resource configuration for main NATS container */
295
295
  resources?: K8sResourceConfig;
296
+ /** Reloader sidecar configuration */
297
+ reloader?: {
298
+ /** Enable reloader sidecar (defaults to true) */
299
+ enabled?: boolean;
300
+ /** Resource configuration */
301
+ resources?: K8sResourceConfig;
302
+ };
303
+ /** NATS Box configuration (debugging tool) */
304
+ natsBox?: {
305
+ /** Enable NATS Box (defaults to true) */
306
+ enabled?: boolean;
307
+ /** Resource configuration */
308
+ resources?: K8sResourceConfig;
309
+ };
296
310
  }
297
311
  /**
298
312
  * Deployment result for a K8s service.
package/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "@crossdelta/infrastructure",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
+ "bin": {
10
+ "generate-env": "./bin/generate-env.ts"
11
+ },
9
12
  "main": "dist/index.cjs",
10
13
  "types": "dist/index.d.ts",
11
14
  "files": [
12
- "dist"
15
+ "dist",
16
+ "bin"
13
17
  ],
14
18
  "exports": {
15
19
  ".": {