@crossdelta/infrastructure 0.11.0 → 0.11.2

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.
package/README.md CHANGED
@@ -3,23 +3,52 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@crossdelta/infrastructure.svg)](https://www.npmjs.com/package/@crossdelta/infrastructure)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Pulumi abstractions that turn per-service config objects into complete Kubernetes deployments. You describe **what** each service needs (ports, env, secrets, health checks), the package handles **how** (Deployments, Services, Ingress, TLS, probes, pull secrets).
6
+ Opinionated Pulumi library for deploying microservices to **DigitalOcean Kubernetes (DOKS)**. You describe each service as a typed config object, the package generates Deployments, Services, Ingress, Secrets, health probes, and rolling update strategies.
7
7
 
8
- **What you skip writing:**
9
- - Boilerplate K8s YAML / Pulumi resource declarations per service
10
- - NATS JetStream + cert-manager + NGINX Ingress setup
11
- - Docker registry secrets, health probes, rolling update strategies
12
- - Multi-environment concerns (shared cluster, per-stack namespaces)
8
+ Built for teams running TypeScript microservices on DOKS with NATS JetStream, GHCR, and Pulumi. If that's your stack, this saves real boilerplate. If not, you're probably better off with plain Pulumi or Helm.
9
+
10
+ ## When to use this
11
+
12
+ - You deploy multiple TypeScript services to a **DigitalOcean** Kubernetes cluster
13
+ - You use **Pulumi** (not Terraform, not Helm-only) for infrastructure
14
+ - You want one config object per service instead of ~150 lines of K8s resource declarations
15
+ - You need NATS JetStream, NGINX Ingress, cert-manager, or Caddy as a reverse proxy
16
+
17
+ ## When NOT to use this
18
+
19
+ - **AWS/GCP/Azure**: Only DOKS is implemented. EKS/AKS/GKE are not supported.
20
+ - **Terraform or plain Helm**: This is Pulumi-only.
21
+ - **Non-opinionated setups**: The package makes choices for you (Helm chart versions, deployment strategies, DO-specific annotations). If you need full control, use Pulumi directly.
22
+ - **Single-service projects**: The overhead isn't worth it for one service.
13
23
 
14
24
  ## Install
15
25
 
16
26
  ```bash
17
27
  npm install @crossdelta/infrastructure @pulumi/pulumi @pulumi/kubernetes
28
+ # For DOKS cluster creation:
29
+ npm install @pulumi/digitalocean
30
+ # For NATS stream deployment (optional):
31
+ npm install @crossdelta/cloudevents
18
32
  ```
19
33
 
20
- ## End-to-End Example
34
+ ## What's included
35
+
36
+ | Module | What it does |
37
+ |--------|-------------|
38
+ | **Cluster** | `createDOKSCluster()`, `createVPC()`, `createK8sProviderFromKubeconfig()` |
39
+ | **Workloads** | `deployK8sService()`, `deployK8sServices()`, `createNamespace()`, `createImagePullSecret()` |
40
+ | **NGINX Ingress** | `deployNginxIngress()` via Helm (chart v4.11.3) |
41
+ | **cert-manager** | `deployCertManager()` via Helm (chart v1.16.2) |
42
+ | **NATS** | `deployNats()` via Helm (chart v1.2.6), `buildNatsUrl()` |
43
+ | **Caddy** | `deployCaddy()`, `generateCaddyfile()` for reverse proxy with on-demand TLS |
44
+ | **Streams** | `collectStreamDefinitions()`, `deployStreams()`, `materializeStreams()` (NATS JetStream) |
45
+ | **Service Discovery** | `discoverServiceConfigs()` auto-discovers configs from `infra/services/*.ts` |
46
+ | **Local Dev** | `generateComposeYaml()`, k3d cluster scripts |
47
+ | **CLI** | `generate-env` generates `.env.local` from Pulumi config + service discovery |
48
+
49
+ ## Quick Start
21
50
 
22
- One `index.ts` that provisions a cluster, runtime, and all services:
51
+ One `index.ts` that provisions a cluster, runtime components, and all services:
23
52
 
24
53
  ```typescript
25
54
  import {
@@ -31,7 +60,6 @@ import {
31
60
  discoverServiceConfigs,
32
61
  } from '@crossdelta/infrastructure'
33
62
 
34
- // 1. Cluster
35
63
  const vpc = createVPC({ name: 'my-vpc', region: 'fra1' })
36
64
  const { provider } = createDOKSCluster({
37
65
  name: 'my-cluster',
@@ -40,21 +68,19 @@ const { provider } = createDOKSCluster({
40
68
  })
41
69
  createNamespace(provider, 'my-namespace')
42
70
 
43
- // 2. Runtime (toggle what you need)
44
71
  const runtime = deployRuntime(provider, 'my-namespace', {
45
72
  nats: { enabled: true, config: { replicas: 1, jetstream: { enabled: true } } },
46
73
  ingress: { enabled: true },
47
74
  certManager: { enabled: true, config: { email: 'ops@example.com' } },
48
75
  })
49
76
 
50
- // 3. Services (auto-discovered from infra/services/*.ts)
51
77
  const configs = discoverServiceConfigs('services')
52
78
  deployK8sServices(provider, 'my-namespace', configs)
53
79
  ```
54
80
 
55
81
  ## Service Config
56
82
 
57
- Each file in `infra/services/` exports one config. The package derives Deployment, Service, Ingress, Secret, and probes from it:
83
+ Each file in `infra/services/` exports one config object. The package derives all K8s resources from it:
58
84
 
59
85
  ```typescript
60
86
  import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
@@ -75,14 +101,14 @@ const config: K8sServiceConfig = {
75
101
  export default config
76
102
  ```
77
103
 
78
- See `K8sServiceConfig` type for all available fields (replicas, volumes, strategy, containerEnv, etc.).
104
+ See `K8sServiceConfig` for all fields: replicas, volumes, strategy, containerEnv, labels, annotations, serviceType, etc.
79
105
 
80
106
  ## Shared Cluster (Multi-Stack)
81
107
 
82
- When multiple Pulumi stacks share one cluster, use `clusterName`/`vpcName` to pin the DigitalOcean resource name (default appends the stack name):
108
+ Pin DigitalOcean resource names with `clusterName`/`vpcName` to share a cluster across Pulumi stacks:
83
109
 
84
110
  ```typescript
85
- // Stack A (stage) — owns the cluster
111
+ // Stage stack owns the cluster
86
112
  const { provider, kubeconfig } = createDOKSCluster({
87
113
  name: 'my-cluster',
88
114
  clusterName: 'my-cluster',
@@ -91,7 +117,7 @@ const { provider, kubeconfig } = createDOKSCluster({
91
117
  })
92
118
  export const clusterKubeconfig = kubeconfig
93
119
 
94
- // Stack B (production) — references the shared cluster
120
+ // Production stack references stage
95
121
  const stageStack = new StackReference('org/project/stage')
96
122
  const provider = createK8sProviderFromKubeconfig(
97
123
  'production',
@@ -99,9 +125,24 @@ const provider = createK8sProviderFromKubeconfig(
99
125
  )
100
126
  ```
101
127
 
102
- ## generate-env
128
+ ## generate-env CLI
129
+
130
+ Generates `.env.local` from Pulumi secrets and discovered service configs:
131
+
132
+ ```bash
133
+ npx generate-env # defaults to stage stack
134
+ npx generate-env --stack=production
135
+ npx generate-env --no-pulumi # skip Pulumi, only service discovery
136
+ ```
137
+
138
+ ## Limitations
139
+
140
+ - **DOKS only.** No other cloud provider is implemented.
141
+ - **Helm chart versions are pinned.** NGINX v4.11.3, cert-manager v1.16.2, NATS v1.2.6. No override option yet.
142
+ - **Service discovery assumes a convention:** `infra/services/*.ts` files with `export default config`.
143
+ - **`@crossdelta/cloudevents` is a hard dependency** even if you don't use NATS streams https://github.com/orderboss/platform/issues/TBD.
144
+ - **Caddy deployment uses Recreate strategy** (RWO PVC incompatible with rolling updates).
103
145
 
104
- Generates `.env.local` from Pulumi secrets (default: `stage` stack) and discovered services. Override with `--stack=production` or `--no-pulumi`.
105
146
 
106
147
  ## License
107
148
 
package/dist/index.cjs CHANGED
@@ -1149,7 +1149,8 @@ var indent = (text, level) => {
1149
1149
  `).map((line) => line.trim() === "" ? "" : `${prefix}${line}`).join(`
1150
1150
  `);
1151
1151
  };
1152
- var basicAuthLines = (basicAuth) => basicAuth ? ["basicauth {", ` ${basicAuth.user} ${basicAuth.hash}`, "}"] : [];
1152
+ var encodeHash = (hash) => hash.startsWith("$") ? Buffer.from(hash).toString("base64") : hash;
1153
+ var basicAuthLines = (basicAuth) => basicAuth ? ["basic_auth {", ` ${basicAuth.user} ${encodeHash(basicAuth.hash)}`, "}"] : [];
1153
1154
  var generateHandleBlock = (handle, level, basicAuth) => {
1154
1155
  const hasPath = handle.path != null;
1155
1156
  const header = hasPath ? `handle ${handle.path}* {` : "handle {";
package/dist/index.js CHANGED
@@ -1053,7 +1053,8 @@ var indent = (text, level) => {
1053
1053
  `).map((line) => line.trim() === "" ? "" : `${prefix}${line}`).join(`
1054
1054
  `);
1055
1055
  };
1056
- var basicAuthLines = (basicAuth) => basicAuth ? ["basicauth {", ` ${basicAuth.user} ${basicAuth.hash}`, "}"] : [];
1056
+ var encodeHash = (hash) => hash.startsWith("$") ? Buffer.from(hash).toString("base64") : hash;
1057
+ var basicAuthLines = (basicAuth) => basicAuth ? ["basic_auth {", ` ${basicAuth.user} ${encodeHash(basicAuth.hash)}`, "}"] : [];
1057
1058
  var generateHandleBlock = (handle, level, basicAuth) => {
1058
1059
  const hasPath = handle.path != null;
1059
1060
  const header = hasPath ? `handle ${handle.path}* {` : "handle {";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/infrastructure",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {