@crossdelta/platform-sdk 0.3.42 → 0.4.1
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 +344 -42
- package/bin/cli.js +122 -114
- package/bin/services/ai/instructions/ai-instructions.md +234 -0
- package/bin/templates/workspace/.github/copilot-instructions.md.hbs +155 -5
- package/bin/templates/workspace/.github/workflows/publish-packages.yml +5 -14
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -30,36 +30,116 @@
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## 🎯 Why This Exists
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
- You prefer **monorepos + Infrastructure-as-Code** as your architectural baseline
|
|
37
|
-
- You're okay with **DigitalOcean as the initial cloud provider** (AWS/GCP coming soon)
|
|
35
|
+
Building microservice platforms is hard. You start with great intentions, but complexity creeps in fast:
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
- **Scattered repositories** with inconsistent conventions across services
|
|
38
|
+
- **No unified DX** — every team member invents their own scripts
|
|
39
|
+
- **Infrastructure drift** — Terraform/Pulumi configs live in separate repos
|
|
40
|
+
- **Environment hell** — `.env` files manually maintained, ports collide, services don't talk
|
|
41
|
+
- **Slow onboarding** — new developers spend days setting up the platform
|
|
42
|
+
|
|
43
|
+
**`pf` acts like an opinionated platform engineer on your team.** It's a single CLI that:
|
|
44
|
+
|
|
45
|
+
- Scaffolds production-ready monorepos with best practices baked in
|
|
46
|
+
- Generates microservices (manually or with AI) with consistent structure
|
|
47
|
+
- Auto-wires infrastructure, environment variables, and port assignments
|
|
48
|
+
- Unifies your dev workflow: one command to run everything locally
|
|
49
|
+
|
|
50
|
+
Built for teams using **Turborepo + Pulumi + NATS + Bun** who want to move fast without sacrificing quality.
|
|
44
51
|
|
|
45
52
|
<br />
|
|
46
53
|
|
|
47
54
|
---
|
|
48
55
|
|
|
49
|
-
##
|
|
56
|
+
## 🧩 What This SDK Is / Is Not
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
### ✅ `pf` is:
|
|
59
|
+
|
|
60
|
+
- **Opinionated CLI** for event-driven microservice platforms
|
|
61
|
+
- **Turborepo scaffolder** with Pulumi IaC and NATS messaging built-in
|
|
62
|
+
- **AI-powered code generator** that creates complete services from natural language
|
|
63
|
+
- **Unified dev workflow** — one command to rule them all (`bun dev`)
|
|
64
|
+
- **DigitalOcean-first** deployment target (App Platform + DOKS)
|
|
65
|
+
|
|
66
|
+
### ❌ `pf` is not:
|
|
67
|
+
|
|
68
|
+
- **Not a generic microservice generator** — we make architectural choices for you
|
|
69
|
+
- **Not cloud-agnostic** (yet) — DigitalOcean first, AWS/GCP coming later
|
|
70
|
+
- **Not a runtime manager** — we scaffold code, you deploy with Pulumi
|
|
71
|
+
- **Not a replacement for Kubernetes** — we generate K8s configs via Pulumi
|
|
72
|
+
|
|
73
|
+
<br />
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🏗️ Architecture at 10,000 ft
|
|
78
|
+
|
|
79
|
+
When you create a workspace with `pf`, you get a **Turborepo monorepo** with this structure:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
my-platform/
|
|
83
|
+
├── services/ # Microservices (Hono, NestJS)
|
|
84
|
+
│ ├── orders/ # Example: Order processing service
|
|
85
|
+
│ ├── notifications/# Example: Notification service
|
|
86
|
+
│ └── nats/ # NATS message broker (auto-scaffolded)
|
|
87
|
+
├── apps/ # Frontend apps (optional: Qwik, Next.js, etc.)
|
|
88
|
+
├── packages/ # Shared libraries
|
|
89
|
+
│ ├── cloudevents/ # Event publishing/consuming toolkit
|
|
90
|
+
│ └── telemetry/ # OpenTelemetry setup
|
|
91
|
+
├── infra/ # Pulumi Infrastructure-as-Code
|
|
92
|
+
│ ├── index.ts # Main Pulumi program
|
|
93
|
+
│ └── services/ # Per-service K8s configs
|
|
94
|
+
│ ├── orders.ts
|
|
95
|
+
│ └── notifications.ts
|
|
96
|
+
├── .github/
|
|
97
|
+
│ └── workflows/ # CI/CD pipelines (auto-generated)
|
|
98
|
+
├── turbo.json # Turborepo task orchestration
|
|
99
|
+
└── .env.local # Auto-generated from infra configs
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Key Architectural Decisions
|
|
103
|
+
|
|
104
|
+
1. **NATS + JetStream baseline** — Event-driven communication is built-in, not bolted on
|
|
105
|
+
2. **Infrastructure-as-Code by default** — Every service has a matching `infra/services/<name>.ts` config
|
|
106
|
+
3. **Auto-wiring everywhere** — Ports, env vars, and NATS subjects are derived automatically
|
|
107
|
+
4. **Opinionated conventions** — Event handlers live in `src/handlers/*.event.ts`, business logic in `src/use-cases/*.use-case.ts`
|
|
108
|
+
5. **Bun-first DX** — Ultra-fast installs, tests, and dev server with fallback to npm/yarn
|
|
109
|
+
|
|
110
|
+
### Event-Driven Mental Model
|
|
111
|
+
|
|
112
|
+
Services communicate via **CloudEvents** over **NATS JetStream**:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Service A publishes an event
|
|
116
|
+
await publish('orders.created', { orderId: '123', total: 99.99 })
|
|
117
|
+
|
|
118
|
+
// Service B auto-discovers and handles it
|
|
119
|
+
// File: services/notifications/src/handlers/order-created.event.ts
|
|
120
|
+
export default handleEvent(
|
|
121
|
+
{ schema: OrderCreatedSchema, type: 'orders.created' },
|
|
122
|
+
async (data) => { await sendNotification(data) }
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
No manual NATS subscriptions. No boilerplate. Just **convention over configuration**.
|
|
127
|
+
|
|
128
|
+
<br />
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 🧭 Design Principles
|
|
133
|
+
|
|
134
|
+
These principles guide every decision in `pf`:
|
|
135
|
+
|
|
136
|
+
- **🥟 Bun-first DX** — Leverage Bun's speed for installs, tests, and dev mode (npm/yarn fallback supported)
|
|
137
|
+
- **📦 Monorepo-centric** — One repo to rule them all. Turborepo for caching and parallel builds
|
|
138
|
+
- **🏗️ Infrastructure-as-Code by default** — No ClickOps. Every service has Pulumi config
|
|
139
|
+
- **🔁 Event-driven communication baked in** — NATS + JetStream are first-class citizens, not afterthoughts
|
|
140
|
+
- **🌊 DigitalOcean-first, cloud-agnostic later** — Start simple with DO, expand to AWS/GCP when needed
|
|
141
|
+
- **🤖 AI-augmented development** — Use AI to generate complete services, not just snippets
|
|
142
|
+
- **📏 Convention over configuration** — Strong opinions enable automation
|
|
63
143
|
|
|
64
144
|
<br />
|
|
65
145
|
|
|
@@ -126,30 +206,230 @@ This will:
|
|
|
126
206
|
- 🔌 Auto-assign unique ports per service
|
|
127
207
|
- 📦 Monitor for file changes and hot-reload
|
|
128
208
|
|
|
209
|
+
### 4. Essential workspace commands
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Run all tests across the monorepo
|
|
213
|
+
bun test
|
|
214
|
+
|
|
215
|
+
# Lint and format all code with Biome
|
|
216
|
+
bun lint
|
|
217
|
+
|
|
218
|
+
# Build all packages and services
|
|
219
|
+
bun run build
|
|
220
|
+
```
|
|
221
|
+
|
|
129
222
|
<br />
|
|
130
223
|
|
|
131
224
|
---
|
|
132
225
|
|
|
133
|
-
##
|
|
226
|
+
## 📘 Typical Workflows
|
|
227
|
+
|
|
228
|
+
Here's how developers actually use `pf` in real-world scenarios:
|
|
134
229
|
|
|
230
|
+
### Workflow 1: Start a New Platform
|
|
231
|
+
|
|
232
|
+
You're building a new product from scratch and want to establish a solid foundation.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Step 1: Scaffold the workspace
|
|
236
|
+
bunx @crossdelta/platform-sdk new workspace orderboss-platform \
|
|
237
|
+
--github-owner orderboss \
|
|
238
|
+
--pulumi-stack dev \
|
|
239
|
+
-y
|
|
240
|
+
|
|
241
|
+
cd orderboss-platform
|
|
242
|
+
|
|
243
|
+
# Step 2: Configure infrastructure (optional)
|
|
244
|
+
# Edit infra/config.ts to customize:
|
|
245
|
+
# - DigitalOcean region (defaults to nyc3)
|
|
246
|
+
# - Kubernetes cluster size
|
|
247
|
+
# - Database instance sizes
|
|
248
|
+
|
|
249
|
+
# Step 3: Start local development
|
|
250
|
+
bun dev
|
|
135
251
|
```
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
252
|
+
|
|
253
|
+
**What you get:**
|
|
254
|
+
- ✅ Turborepo monorepo with Biome linting/formatting
|
|
255
|
+
- ✅ NATS service running in Docker
|
|
256
|
+
- ✅ Pulumi infrastructure setup for DigitalOcean
|
|
257
|
+
- ✅ GitHub Actions workflows for CI/CD
|
|
258
|
+
- ✅ `.env.local` auto-generated with all service ports
|
|
259
|
+
|
|
260
|
+
**Time to first service running:** ~60 seconds
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Workflow 2: Add a New Microservice
|
|
265
|
+
|
|
266
|
+
Your platform is growing and you need to add a new service for handling payments.
|
|
267
|
+
|
|
268
|
+
**Option A: Manual scaffolding (fast, predictable)**
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Generate a lightweight Hono microservice
|
|
272
|
+
pf new hono-micro services/payments -y
|
|
273
|
+
|
|
274
|
+
# What gets auto-generated:
|
|
275
|
+
# ✅ services/payments/src/index.ts (Hono server)
|
|
276
|
+
# ✅ services/payments/src/handlers/*.event.ts (example event handler)
|
|
277
|
+
# ✅ infra/services/payments.ts (Pulumi K8s config)
|
|
278
|
+
# ✅ services/payments/README.md (service documentation)
|
|
279
|
+
# ✅ Dockerfile + package.json with scripts
|
|
280
|
+
# ✅ Auto-assigned port (e.g., 4003)
|
|
281
|
+
|
|
282
|
+
# Start the service
|
|
283
|
+
bun dev
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Option B: AI-powered generation (intelligent, complete)**
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# First time: configure AI provider
|
|
290
|
+
pf setup --ai
|
|
291
|
+
|
|
292
|
+
# Generate a complete service with AI
|
|
293
|
+
pf new hono-micro services/payments --ai \
|
|
294
|
+
-d "Stripe payment processing: handle checkout.session.completed webhooks, \
|
|
295
|
+
publish payment.succeeded events, update order status in database"
|
|
296
|
+
|
|
297
|
+
# AI generates:
|
|
298
|
+
# ✅ Complete service implementation with Stripe SDK
|
|
299
|
+
# ✅ Event handlers for incoming orders
|
|
300
|
+
# ✅ Event publishers for payment lifecycle
|
|
301
|
+
# ✅ Use cases with validation logic
|
|
302
|
+
# ✅ Test files for all use cases
|
|
303
|
+
# ✅ Environment variable documentation
|
|
304
|
+
# ✅ Complete README with API docs
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**When to use which:**
|
|
308
|
+
- **Manual scaffolding:** You know exactly what you're building, want full control
|
|
309
|
+
- **AI generation:** Bootstrapping new domains, exploring integrations, speeding up prototyping
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Workflow 3: Build an Event-Driven Feature
|
|
314
|
+
|
|
315
|
+
You want to send notifications whenever a new order is created.
|
|
316
|
+
|
|
317
|
+
**Step 1: Create the notification service**
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
pf new hono-micro services/notifications -y
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Step 2: Implement the event handler**
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// services/notifications/src/handlers/order-created.event.ts
|
|
327
|
+
import { handleEvent } from '@crossdelta/cloudevents'
|
|
328
|
+
import { z } from 'zod'
|
|
329
|
+
import { sendNotification } from '../use-cases/send-notification.use-case'
|
|
330
|
+
|
|
331
|
+
const OrderCreatedSchema = z.object({
|
|
332
|
+
orderId: z.string(),
|
|
333
|
+
customerId: z.string(),
|
|
334
|
+
total: z.number(),
|
|
335
|
+
items: z.array(
|
|
336
|
+
z.object({
|
|
337
|
+
productId: z.string(),
|
|
338
|
+
quantity: z.number(),
|
|
339
|
+
price: z.number(),
|
|
340
|
+
}),
|
|
341
|
+
).optional(),
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// Export type for use in use-cases
|
|
345
|
+
export type OrderCreatedEvent = z.infer<typeof OrderCreatedSchema>
|
|
346
|
+
|
|
347
|
+
export default handleEvent(
|
|
348
|
+
{
|
|
349
|
+
schema: OrderCreatedSchema,
|
|
350
|
+
type: 'orders.created', // Event type to subscribe to
|
|
351
|
+
},
|
|
352
|
+
async (data) => {
|
|
353
|
+
await sendNotification(data)
|
|
354
|
+
},
|
|
355
|
+
)
|
|
151
356
|
```
|
|
152
357
|
|
|
358
|
+
**Step 3: Implement the use case**
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// services/notifications/src/use-cases/send-notification.use-case.ts
|
|
362
|
+
import type { OrderCreatedEvent } from '../handlers/order-created.event'
|
|
363
|
+
|
|
364
|
+
export async function sendNotification(data: OrderCreatedEvent): Promise<void> {
|
|
365
|
+
// Full type inference from the event handler schema
|
|
366
|
+
console.log(`Sending notification for order: ${data.orderId}`)
|
|
367
|
+
|
|
368
|
+
// Your notification logic here:
|
|
369
|
+
// - Send email via SendGrid
|
|
370
|
+
// - Push notification via Firebase
|
|
371
|
+
// - SMS via Twilio
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Step 4: Start consuming events**
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// services/notifications/src/index.ts
|
|
379
|
+
import '@crossdelta/telemetry' // Must be first import
|
|
380
|
+
|
|
381
|
+
import { consumeJetStreamEvents } from '@crossdelta/cloudevents/transports/nats'
|
|
382
|
+
import { Hono } from 'hono'
|
|
383
|
+
|
|
384
|
+
const port = Number(process.env.PORT || process.env.NOTIFICATIONS_PORT) || 4002
|
|
385
|
+
const app = new Hono()
|
|
386
|
+
|
|
387
|
+
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
388
|
+
|
|
389
|
+
// Auto-discover and register all event handlers
|
|
390
|
+
consumeJetStreamEvents({
|
|
391
|
+
stream: 'ORDERS',
|
|
392
|
+
subjects: ['orders.>'], // Subscribe to all orders.* events
|
|
393
|
+
consumer: 'notifications',
|
|
394
|
+
discover: './src/handlers/**/*.event.ts',
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
Bun.serve({ port, fetch: app.fetch })
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Step 5: Publish events from the orders service**
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// services/orders/src/routes/create-order.ts
|
|
404
|
+
import { publish } from '@crossdelta/cloudevents'
|
|
405
|
+
|
|
406
|
+
export async function createOrder(orderData) {
|
|
407
|
+
const order = await db.orders.create(orderData)
|
|
408
|
+
|
|
409
|
+
// Publish event to NATS
|
|
410
|
+
await publish('orders.created', {
|
|
411
|
+
orderId: order.id,
|
|
412
|
+
customerId: order.customerId,
|
|
413
|
+
total: order.total,
|
|
414
|
+
items: order.items,
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
return order
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**How it works:**
|
|
422
|
+
1. **Publishing:** `publish()` sends a CloudEvent to NATS JetStream
|
|
423
|
+
2. **Auto-discovery:** `consumeJetStreamEvents()` scans `src/handlers/*.event.ts` and registers handlers
|
|
424
|
+
3. **Type safety:** Zod schema validates incoming events, provides TypeScript types
|
|
425
|
+
4. **Decoupling:** Services don't know about each other, only about events
|
|
426
|
+
|
|
427
|
+
**Benefits:**
|
|
428
|
+
- ✅ Zero boilerplate for NATS subscriptions
|
|
429
|
+
- ✅ Type-safe event handling with Zod
|
|
430
|
+
- ✅ Automatic retries and error handling (JetStream guarantees)
|
|
431
|
+
- ✅ Easy to add new consumers without touching existing services
|
|
432
|
+
|
|
153
433
|
<br />
|
|
154
434
|
|
|
155
435
|
---
|
|
@@ -291,16 +571,38 @@ export const config: K8sServiceConfig = {
|
|
|
291
571
|
|
|
292
572
|
## 🚢 Deployment
|
|
293
573
|
|
|
574
|
+
### Local Deployment
|
|
575
|
+
|
|
294
576
|
```bash
|
|
295
577
|
pulumi login
|
|
296
578
|
pulumi up --stack dev
|
|
297
579
|
```
|
|
298
580
|
|
|
299
|
-
GitHub
|
|
581
|
+
### CI/CD with GitHub Actions
|
|
582
|
+
|
|
583
|
+
Every workspace includes pre-configured GitHub Actions workflows in `.github/workflows/`:
|
|
584
|
+
|
|
585
|
+
| Workflow | Trigger | Purpose |
|
|
586
|
+
|----------|---------|---------|
|
|
587
|
+
| **`lint-and-tests.yml`** | Pull Requests to `main` | Runs `bun lint` and `bun test` on PRs |
|
|
588
|
+
| **`build-and-deploy.yml`** | Push to `main` | Builds Docker images, pushes to GHCR, deploys via Pulumi |
|
|
589
|
+
| **`publish-packages.yml`** | Changes in `packages/` | Auto-publishes packages to npm with versioning |
|
|
590
|
+
|
|
591
|
+
### Required GitHub Secrets
|
|
592
|
+
|
|
593
|
+
Configure these secrets in your repository settings (`Settings` → `Secrets and variables` → `Actions`):
|
|
594
|
+
|
|
595
|
+
| Secret | Description | Required For |
|
|
596
|
+
|--------|-------------|--------------|
|
|
597
|
+
| `PULUMI_ACCESS_TOKEN` | Pulumi Cloud access token | All deployments |
|
|
598
|
+
| `DIGITALOCEAN_TOKEN` | DigitalOcean API token | Infrastructure provisioning |
|
|
599
|
+
| `NPM_TOKEN` | npm registry token | Package publishing (optional) |
|
|
600
|
+
| `GHCR_TOKEN` | GitHub Container Registry PAT | Docker image publishing |
|
|
300
601
|
|
|
301
|
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
602
|
+
**Get tokens:**
|
|
603
|
+
- **Pulumi:** https://app.pulumi.com/account/tokens
|
|
604
|
+
- **DigitalOcean:** https://cloud.digitalocean.com/account/api/tokens
|
|
605
|
+
- **npm:** https://www.npmjs.com/settings/~/tokens
|
|
304
606
|
|
|
305
607
|
<br />
|
|
306
608
|
|