@crossdelta/platform-sdk 0.19.0 → 0.19.3
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 +27 -5
- package/bin/chunk-634PL24Z.mjs +20 -0
- package/bin/cli.mjs +604 -0
- package/bin/config-CKQHYOF4.mjs +2 -0
- package/bin/docs/generators/code-style.md +79 -0
- package/bin/docs/generators/natural-language.md +117 -0
- package/bin/docs/generators/service.md +129 -60
- package/bin/templates/hono-microservice/Dockerfile.hbs +3 -1
- package/bin/templates/hono-microservice/src/config/env.ts.hbs +3 -0
- package/bin/templates/nest-microservice/Dockerfile.hbs +6 -2
- package/bin/templates/nest-microservice/src/config/env.ts.hbs +17 -0
- package/bin/templates/nest-microservice/src/main.ts.hbs +2 -1
- package/bin/templates/workspace/.github/actions/prepare-build-context/action.yml +58 -6
- package/bin/templates/workspace/.github/workflows/build-and-deploy.yml.hbs +25 -3
- package/bin/templates/workspace/.github/workflows/publish-packages.yml +6 -8
- package/bin/templates/workspace/biome.json.hbs +4 -1
- package/bin/templates/workspace/infra/package.json.hbs +2 -2
- package/bin/templates/workspace/package.json.hbs +1 -0
- package/bin/templates/workspace/packages/contracts/README.md.hbs +5 -5
- package/bin/templates/workspace/packages/contracts/package.json.hbs +15 -6
- package/bin/templates/workspace/packages/contracts/src/index.ts +1 -1
- package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +6 -1
- package/bin/templates/workspace/turbo.json +8 -11
- package/bin/templates/workspace/turbo.json.hbs +6 -5
- package/dist/facade.d.mts +840 -0
- package/dist/facade.d.ts +840 -0
- package/dist/facade.js +2294 -0
- package/dist/facade.js.map +1 -0
- package/dist/facade.mjs +2221 -0
- package/dist/facade.mjs.map +1 -0
- package/dist/plugin-types-DQOv97Zh.d.mts +180 -0
- package/dist/plugin-types-DQOv97Zh.d.ts +180 -0
- package/dist/plugin-types.d.mts +1 -0
- package/dist/plugin-types.d.ts +1 -0
- package/dist/plugin-types.js +19 -0
- package/dist/plugin-types.js.map +1 -0
- package/dist/plugin-types.mjs +1 -0
- package/dist/plugin-types.mjs.map +1 -0
- package/dist/plugin.d.mts +31 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +105 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.mjs +75 -0
- package/dist/plugin.mjs.map +1 -0
- package/package.json +118 -99
- package/bin/cli.js +0 -540
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{A as g,B as h,C as i,D as j,E as k,F as l,G as m,H as n,u as a,v as b,w as c,x as d,y as e,z as f}from"./chunk-634PL24Z.mjs";export{n as discoverAvailableServices,b as findWorkspaceRoot,f as getContractsConfig,m as getGeneratorConfig,h as getPackageJsonField,e as getPfConfig,k as getPkgJson,g as getRootPackageScope,c as getWorkspacePackageJson,d as getWorkspacePathsConfig,a as isInWorkspace,l as pkgJson,j as readPackageConfig,i as updatePackageJsonField};
|
|
@@ -94,3 +94,82 @@ z.string().datetime()
|
|
|
94
94
|
// ❌ Deprecated
|
|
95
95
|
z.string().email('Invalid')
|
|
96
96
|
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Type-Safe External Package Integration
|
|
101
|
+
|
|
102
|
+
**NEVER use `any` types** - always create explicit type definitions for packages without TypeScript support.
|
|
103
|
+
|
|
104
|
+
### Guidelines
|
|
105
|
+
|
|
106
|
+
- ✅ Create type definitions based on API documentation
|
|
107
|
+
- ✅ Prefer `type` over `interface` for simple object shapes
|
|
108
|
+
- ✅ Use `const` with direct initialization
|
|
109
|
+
- ✅ Use type assertions (`as Type`) only after explicit types
|
|
110
|
+
- ❌ Never use `any`
|
|
111
|
+
- ❌ Never use `let` + null + conditional initialization
|
|
112
|
+
|
|
113
|
+
### Example (Pusher Beams)
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// ❌ WRONG - Using any
|
|
117
|
+
let client: any = new PushNotifications({ ... })
|
|
118
|
+
|
|
119
|
+
// ❌ WRONG - let + null + conditional
|
|
120
|
+
let client: BeamsClient | null = null
|
|
121
|
+
if (!client) {
|
|
122
|
+
client = new PushNotifications({ ... }) as BeamsClient
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ✅ CORRECT - Explicit types + const + direct initialization
|
|
126
|
+
type BeamsClient = {
|
|
127
|
+
publishToUsers: (userIds: string[], request: BeamsPublishRequest) => Promise<PublishResponse>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type BeamsPublishRequest = {
|
|
131
|
+
web?: {
|
|
132
|
+
notification?: {
|
|
133
|
+
title: string
|
|
134
|
+
body: string
|
|
135
|
+
deep_link?: string
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type PublishResponse = {
|
|
141
|
+
publishId: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const getBeamsClient = (): BeamsClient => {
|
|
145
|
+
if (!env.PUSHER_INSTANCE_ID || !env.PUSHER_SECRET_KEY) {
|
|
146
|
+
throw new Error('Missing Pusher Beams credentials')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return new PushNotifications({
|
|
150
|
+
instanceId: env.PUSHER_INSTANCE_ID,
|
|
151
|
+
secretKey: env.PUSHER_SECRET_KEY,
|
|
152
|
+
}) as BeamsClient
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Why `type` over `interface`?
|
|
157
|
+
|
|
158
|
+
- More flexible (unions, intersections, mapped types)
|
|
159
|
+
- More concise for simple object shapes
|
|
160
|
+
- Better for functional programming patterns
|
|
161
|
+
- Standard in modern TypeScript codebases
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// ✅ CORRECT - type
|
|
165
|
+
type User = {
|
|
166
|
+
id: string
|
|
167
|
+
name: string
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ❌ AVOID - interface (only use for extensible APIs)
|
|
171
|
+
interface User {
|
|
172
|
+
id: string
|
|
173
|
+
name: string
|
|
174
|
+
}
|
|
175
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Natural Language Service Generation
|
|
2
|
+
|
|
3
|
+
Generate services from natural language prompts using the capabilities system.
|
|
4
|
+
|
|
5
|
+
## Quick Examples
|
|
6
|
+
|
|
7
|
+
### 1. Freeform German
|
|
8
|
+
```
|
|
9
|
+
Erstelle notification-service, hört auf order.created, sendet Push via Pusher Beams an interest order-updates.
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### 2. Semi-Structured
|
|
13
|
+
```
|
|
14
|
+
Service: notification-service | When: order.created | Do: push via pusher beams | Target: interest order-updates
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 3. HTTP Integration
|
|
18
|
+
```
|
|
19
|
+
Erstelle shipping-service, hört auf order.paid, ruft HTTP POST /shipments bei https://api.example.com auf.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## How It Works
|
|
23
|
+
|
|
24
|
+
The capabilities pipeline parses your prompt:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
NL Prompt
|
|
28
|
+
│
|
|
29
|
+
▼ parseServicePrompt()
|
|
30
|
+
ServiceSpec { serviceName, triggers, actions }
|
|
31
|
+
│
|
|
32
|
+
▼ lowerSpecToCapabilities()
|
|
33
|
+
CapabilityInvocation[]
|
|
34
|
+
│
|
|
35
|
+
▼ registry.planAll()
|
|
36
|
+
GenerationPlan (deterministic paths from policy)
|
|
37
|
+
│
|
|
38
|
+
▼ generateFileContents()
|
|
39
|
+
Generated Files
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Key:** AI extracts intent, but paths and structure come from policy—not AI.
|
|
43
|
+
|
|
44
|
+
## Optional Defaults via package.json
|
|
45
|
+
|
|
46
|
+
Configure workspace defaults in `package.json`:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"pf": {
|
|
51
|
+
"capabilities": {
|
|
52
|
+
"notifier": {
|
|
53
|
+
"defaults": {
|
|
54
|
+
"push": "pusher-beams",
|
|
55
|
+
"email": "resend"
|
|
56
|
+
},
|
|
57
|
+
"providers": {
|
|
58
|
+
"push": {
|
|
59
|
+
"custom-provider": {
|
|
60
|
+
"dependencies": { "custom-sdk": "^1.0.0" },
|
|
61
|
+
"envVars": [
|
|
62
|
+
{ "key": "CUSTOM_API_KEY", "description": "API key", "required": true }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Important:** Defaults are only applied when the prompt doesn't specify a provider. Explicit mentions in the prompt always win.
|
|
74
|
+
|
|
75
|
+
## Supported Capabilities
|
|
76
|
+
|
|
77
|
+
| Capability | Trigger/Action | Example |
|
|
78
|
+
|------------|----------------|---------|
|
|
79
|
+
| `event-consumer` | Trigger | "listens to order.created" |
|
|
80
|
+
| `event-publisher` | Action | "emits order.shipped event" |
|
|
81
|
+
| `notifier` | Action | "sends push via pusher beams" |
|
|
82
|
+
| `http-endpoint` | Trigger | "POST /webhooks" |
|
|
83
|
+
|
|
84
|
+
## Built-in Providers
|
|
85
|
+
|
|
86
|
+
### Push Notifications
|
|
87
|
+
- `pusher-beams` - Pusher Beams
|
|
88
|
+
- `firebase` - Firebase Cloud Messaging
|
|
89
|
+
- `onesignal` - OneSignal
|
|
90
|
+
- `expo` - Expo Push Notifications
|
|
91
|
+
|
|
92
|
+
### Email
|
|
93
|
+
- `resend` - Resend
|
|
94
|
+
- `sendgrid` - SendGrid
|
|
95
|
+
- `ses` - AWS SES
|
|
96
|
+
- `postmark` - Postmark
|
|
97
|
+
|
|
98
|
+
### Slack
|
|
99
|
+
- `slack-webhook` - Slack Webhook
|
|
100
|
+
- `slack-api` - Slack Web API
|
|
101
|
+
|
|
102
|
+
### SMS
|
|
103
|
+
- `twilio` - Twilio
|
|
104
|
+
- `vonage` - Vonage
|
|
105
|
+
|
|
106
|
+
## Provider Detection
|
|
107
|
+
|
|
108
|
+
The parser detects providers from your prompt:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// These all detect "pusher-beams":
|
|
112
|
+
"send push via Pusher Beams"
|
|
113
|
+
"push notification using pusher-beams"
|
|
114
|
+
"notify with PusherBeams"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
If no provider is detected and no default is configured, elicitation prompts for selection.
|
|
@@ -7,6 +7,45 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## 🚨 pf MCP Flow (VERBINDLICH)
|
|
11
|
+
|
|
12
|
+
### Architektur-Entscheidung
|
|
13
|
+
|
|
14
|
+
Der `pf` MCP-Server ist ein **Planner**, nicht ein Code-Generator. Die AI (Copilot) generiert den Code.
|
|
15
|
+
|
|
16
|
+
### Korrekter Flow
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
1. AI sammelt Kontext → mcp_pf_scan_workspace()
|
|
20
|
+
2. AI ruft Generate auf → mcp_pf_generate_service(dryRun: true, prompt: "...")
|
|
21
|
+
3. pf liefert Plan → { files: [...], postCommands: ["pf cloudevents add ..."], ... }
|
|
22
|
+
4. 🆕 MCP verifiziert NPM → Automatisch via npm registry API
|
|
23
|
+
5. 🆕 Falls Korrekturen → In diagnostics: "📦 Corrected pkg: ^1.3.3 → ^1.2.7"
|
|
24
|
+
6. AI führt postCommands aus → `pf cloudevents add order.created --service services/my-service`
|
|
25
|
+
7. AI generiert Code → NUR für adapters/use-cases, NICHT für handlers!
|
|
26
|
+
8. User OK Gate → User bestätigt Plan mit korrekten Versionen
|
|
27
|
+
9. AI ruft Apply auf → mcp_pf_apply_changes(changes: [...])
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### ✅ DO (korrekt)
|
|
31
|
+
|
|
32
|
+
- `mcp_pf_generate_service(dryRun: true)` → Plan holen
|
|
33
|
+
- **`postCommands` ausführen** → `pf cloudevents add` erstellt Handler automatisch!
|
|
34
|
+
- AI generiert nur adapters/use-cases basierend auf `files[].intent`
|
|
35
|
+
- `mcp_pf_apply_changes()` nur nach User-OK
|
|
36
|
+
- `expectedPlanHash` für Drift-Detection nutzen
|
|
37
|
+
|
|
38
|
+
### ❌ DON'T (verboten)
|
|
39
|
+
|
|
40
|
+
- **KEINE Handler selbst schreiben!** → `pf cloudevents add` benutzen
|
|
41
|
+
- Kein `dryRun: false` ohne vorherige Preview
|
|
42
|
+
- Kein Code in Capabilities einbauen (nur `intent` + `inputs`)
|
|
43
|
+
- Kein "builtin provider catalogue" mit Templates
|
|
44
|
+
- Keine direkten File-Writes während Generate
|
|
45
|
+
- Kein Package.json direkt editieren wenn `deps:add` Effect existiert
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
10
49
|
## 🚨 CRITICAL: Port Configuration
|
|
11
50
|
|
|
12
51
|
Services MUST read their port from environment variables using this pattern:
|
|
@@ -106,10 +145,10 @@ pf new hono-micro services/order-processing -y
|
|
|
106
145
|
```
|
|
107
146
|
|
|
108
147
|
```post-commands
|
|
109
|
-
pf
|
|
148
|
+
pf cloudevents add order.created --service services/order-processing
|
|
110
149
|
```
|
|
111
150
|
|
|
112
|
-
**What `pf
|
|
151
|
+
**What `pf cloudevents add` does:**
|
|
113
152
|
- Creates `packages/contracts/src/events/orders/created.mock.json` (test mock)
|
|
114
153
|
- Adds export to `packages/contracts/src/index.ts`
|
|
115
154
|
- Skips contract creation if already exists (AI's schema is preserved!)
|
|
@@ -119,10 +158,10 @@ pf event add order.created --service services/order-processing
|
|
|
119
158
|
**Step 1: Create contract with CLI**
|
|
120
159
|
```bash
|
|
121
160
|
# Create contract with schema
|
|
122
|
-
pf
|
|
161
|
+
pf cloudevents add order.created --fields "orderId:string,total:number,customerId:string"
|
|
123
162
|
|
|
124
163
|
# Or with JSON schema
|
|
125
|
-
pf
|
|
164
|
+
pf cloudevents add order.created --schema '{"orderId":"string","total":"number"}'
|
|
126
165
|
```
|
|
127
166
|
|
|
128
167
|
**Step 2: Scaffold service**
|
|
@@ -132,7 +171,7 @@ pf new hono-micro services/order-processing
|
|
|
132
171
|
|
|
133
172
|
**Step 3: Register event and create handler**
|
|
134
173
|
```bash
|
|
135
|
-
pf
|
|
174
|
+
pf cloudevents add order.created --service services/order-processing
|
|
136
175
|
# Creates: src/events/order-created.handler.ts (singular!)
|
|
137
176
|
```
|
|
138
177
|
|
|
@@ -172,6 +211,57 @@ pf dev # Starts NATS + creates ephemeral streams from contracts + all services
|
|
|
172
211
|
|
|
173
212
|
**Stream Creation:** `pf dev` scans `packages/contracts/src/index.ts` for contracts with `channel.stream` metadata and creates ephemeral streams automatically. No manual stream creation needed!
|
|
174
213
|
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 🚨 CRITICAL: Package Version Verification
|
|
217
|
+
|
|
218
|
+
**Package versions are automatically verified and corrected by the MCP server.**
|
|
219
|
+
|
|
220
|
+
### The Problem
|
|
221
|
+
|
|
222
|
+
AI training data is outdated → may suggest versions that don't exist:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
❌ "@pusher/push-notifications-server": "^1.3.3" # Doesn't exist!
|
|
226
|
+
✅ "@pusher/push-notifications-server": "^1.2.7" # Actual latest
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### The Solution
|
|
230
|
+
|
|
231
|
+
The pf MCP server automatically:
|
|
232
|
+
1. Detects package.json in generated files
|
|
233
|
+
2. Fetches latest versions from npm registry
|
|
234
|
+
3. Corrects mismatched versions
|
|
235
|
+
4. Reports corrections in diagnostics
|
|
236
|
+
|
|
237
|
+
**Example Output:**
|
|
238
|
+
```
|
|
239
|
+
📦 Corrected @pusher/push-notifications-server: ^1.3.3 → ^1.2.7
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Implementation
|
|
243
|
+
|
|
244
|
+
Located in `packages/pf-mcp/src/tools/services-generate/npm-verifier.ts`:
|
|
245
|
+
|
|
246
|
+
- **Pure functions**: parsePackageJson, extractNpmPackages
|
|
247
|
+
- **IO adapter**: fetchNpmVersion (npm registry API)
|
|
248
|
+
- **Higher-order**: verifyAndCorrectVersions
|
|
249
|
+
|
|
250
|
+
**Integration point**: `mode-nl.ts` after scaffold generation, before returning plan
|
|
251
|
+
|
|
252
|
+
### What Gets Verified
|
|
253
|
+
|
|
254
|
+
- ✅ All external npm packages
|
|
255
|
+
- ❌ Skips `workspace:*` packages (internal monorepo)
|
|
256
|
+
- ❌ Skips `@crossdelta/*` packages (platform packages)
|
|
257
|
+
- ❌ Skips `@orderboss/*` packages (workspace packages)
|
|
258
|
+
|
|
259
|
+
### No AI Action Required
|
|
260
|
+
|
|
261
|
+
This is handled entirely by the MCP server - AI does not need to verify packages manually.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
175
265
|
### 4️⃣ **Deploy to Production**
|
|
176
266
|
|
|
177
267
|
```bash
|
|
@@ -249,15 +339,19 @@ The exact command depends on the framework - see the framework-specific docs:
|
|
|
249
339
|
**For Event Consumer services, include a `post-commands` block with ALL events:**
|
|
250
340
|
|
|
251
341
|
```post-commands
|
|
252
|
-
pf
|
|
253
|
-
pf
|
|
342
|
+
pf cloudevents add order.created --service services/my-service --fields "id:string"
|
|
343
|
+
pf cloudevents add customer.updated --service services/my-service --fields "id:string"
|
|
254
344
|
```
|
|
255
345
|
|
|
256
|
-
|
|
257
|
-
|
|
346
|
+
**⚠️ CRITICAL: Always include `--fields` to skip interactive prompts!**
|
|
347
|
+
|
|
348
|
+
**What `pf cloudevents add` creates:**
|
|
349
|
+
- `packages/contracts/src/events/<domain>/<event>.ts` - Contract with schema
|
|
350
|
+
- `packages/contracts/src/events/<domain>/<event>.mock.json` - Test mock data
|
|
351
|
+
- `services/<service>/src/events/<event>.handler.ts` - Event handler
|
|
258
352
|
- Adds export to `packages/contracts/src/index.ts`
|
|
259
353
|
|
|
260
|
-
**⚠️ IMPORTANT:** `pf
|
|
354
|
+
**⚠️ IMPORTANT:** `pf cloudevents add` skips contract creation if it already exists - so YOUR contract with the correct schema is preserved!
|
|
261
355
|
|
|
262
356
|
---
|
|
263
357
|
|
|
@@ -285,49 +379,33 @@ Export named 'OrderCreatedContract' not found in module 'packages/contracts/src/
|
|
|
285
379
|
|
|
286
380
|
1. **`packages/contracts/src/events/<domain>/<event>.ts`** - Contract with correct schema fields in domain-grouped structure (e.g., `events/orders/created.ts`)
|
|
287
381
|
2. **`packages/contracts/src/index.ts`** - ⚠️ **ADD EXPORT** for the contract (CRITICAL!)
|
|
288
|
-
3. **`src/
|
|
289
|
-
4. **`src/use-cases/*.
|
|
290
|
-
5. **`src/
|
|
382
|
+
3. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
|
|
383
|
+
4. **`src/use-cases/*.test.ts`** - Tests for use-cases
|
|
384
|
+
5. **`src/adapters/*.adapter.ts`** - External service integrations (if needed)
|
|
291
385
|
6. **`README.md`** - Documentation
|
|
292
386
|
|
|
293
|
-
**⚠️ DO NOT generate
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
**⚠️ MUST be in `src/events/*.handler.ts` - NEVER in `handlers/` subdirectory**
|
|
298
|
-
|
|
299
|
-
```
|
|
300
|
-
✅ CORRECT:
|
|
301
|
-
services/my-service/src/events/
|
|
302
|
-
├── order-created.handler.ts
|
|
303
|
-
└── customer-updated.handler.ts
|
|
304
|
-
|
|
305
|
-
❌ WRONG:
|
|
306
|
-
services/my-service/src/events/handlers/ # NEVER create!
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
### Handler Export Pattern
|
|
387
|
+
**⚠️ DO NOT generate these files** - They are created by CLI commands:
|
|
388
|
+
- `src/index.ts` or `src/main.ts` - Created by `pf new`
|
|
389
|
+
- `src/events/*.handler.ts` - Created by `pf cloudevents add`
|
|
310
390
|
|
|
311
|
-
|
|
312
|
-
// ✅ CORRECT: Default export
|
|
313
|
-
export default handleEvent(OrdersCreatedContract, async (data) => { ... })
|
|
391
|
+
### Handler Creation (CRITICAL!)
|
|
314
392
|
|
|
315
|
-
|
|
316
|
-
export const OrdersCreatedHandler = handleEvent(...)
|
|
317
|
-
```
|
|
393
|
+
**Handlers are created by `pf cloudevents add`, NOT by AI!**
|
|
318
394
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
console.log(`[order.created] Processing orderId=${data.orderId}`) // ✅ console.log
|
|
326
|
-
await processOrder(data)
|
|
327
|
-
})
|
|
395
|
+
```bash
|
|
396
|
+
# This command creates the handler automatically:
|
|
397
|
+
pf cloudevents add order.created --service services/my-service --fields "id:string"
|
|
398
|
+
# → Creates: src/events/order-created.handler.ts
|
|
399
|
+
# → Creates: packages/contracts/src/events/orders/created.mock.json
|
|
400
|
+
# → Updates: packages/contracts/src/index.ts (adds export)
|
|
328
401
|
```
|
|
329
402
|
|
|
330
|
-
**
|
|
403
|
+
**Why `pf cloudevents add` instead of AI generation?**
|
|
404
|
+
- ✅ Consistent handler structure
|
|
405
|
+
- ✅ Correct imports from contracts
|
|
406
|
+
- ✅ Proper mock file generation
|
|
407
|
+
- ✅ Contract export management
|
|
408
|
+
- ✅ No hallucinated code
|
|
331
409
|
|
|
332
410
|
---
|
|
333
411
|
|
|
@@ -362,18 +440,6 @@ export const OrdersCreatedContract = createContract({
|
|
|
362
440
|
export * from './events/orders/created'
|
|
363
441
|
```
|
|
364
442
|
|
|
365
|
-
#### `src/events/order-created.handler.ts`
|
|
366
|
-
```ts
|
|
367
|
-
import { handleEvent } from '@crossdelta/cloudevents'
|
|
368
|
-
import { OrdersCreatedContract, type OrderCreatedData } from '{workspaceScope}/contracts'
|
|
369
|
-
import { processOrder } from '../use-cases/process-order.use-case'
|
|
370
|
-
|
|
371
|
-
export default handleEvent(OrdersCreatedContract, async (data: OrderCreatedData) => {
|
|
372
|
-
console.log('📦 Processing order:', data.orderId)
|
|
373
|
-
await processOrder(data)
|
|
374
|
-
})
|
|
375
|
-
```
|
|
376
|
-
|
|
377
443
|
#### `src/use-cases/process-order.use-case.ts`
|
|
378
444
|
```ts
|
|
379
445
|
import type { OrderCreatedData } from '{workspaceScope}/contracts'
|
|
@@ -384,7 +450,7 @@ export const processOrder = async (data: OrderCreatedData): Promise<void> => {
|
|
|
384
450
|
```
|
|
385
451
|
|
|
386
452
|
```post-commands
|
|
387
|
-
pf
|
|
453
|
+
pf cloudevents add order.created --service services/my-service --fields "id:string"
|
|
388
454
|
```
|
|
389
455
|
|
|
390
456
|
```dependencies
|
|
@@ -581,7 +647,7 @@ consumeJetStreams({
|
|
|
581
647
|
| Block | Purpose |
|
|
582
648
|
|-------|---------|
|
|
583
649
|
| `commands` | Scaffolds service (REQUIRED FIRST) |
|
|
584
|
-
| `post-commands` | Runs after files created (e.g., `pf
|
|
650
|
+
| `post-commands` | Runs after files created (e.g., `pf cloudevents add`) |
|
|
585
651
|
| `dependencies` | Extra npm packages (NOT `@crossdelta/*`) |
|
|
586
652
|
|
|
587
653
|
**When adding dependencies:**
|
|
@@ -591,6 +657,7 @@ consumeJetStreams({
|
|
|
591
657
|
- ✅ Prefer packages with TypeScript definitions (`@types/*` or built-in)
|
|
592
658
|
- ✅ Check GitHub for recent activity and TypeScript support
|
|
593
659
|
- ⚠️ If using an unfamiliar package, mention: "Check official docs for latest API"
|
|
660
|
+
- ⚠️ **For packages without TS support, see [code-style.md](code-style.md#type-safe-external-package-integration)**
|
|
594
661
|
|
|
595
662
|
---
|
|
596
663
|
|
|
@@ -637,6 +704,7 @@ packages/contracts/src/events/
|
|
|
637
704
|
- ❌ Insert semicolons
|
|
638
705
|
- ❌ Create contracts without adding exports to `packages/contracts/src/index.ts`
|
|
639
706
|
- ❌ Create `use-cases/` folder in NestJS (use Services instead)
|
|
707
|
+
- ❌ **Use `any` types** - see [code-style.md](code-style.md#type-safe-external-package-integration)
|
|
640
708
|
|
|
641
709
|
**DO:**
|
|
642
710
|
- ✅ Contracts in `packages/contracts/src/events/`
|
|
@@ -649,3 +717,4 @@ packages/contracts/src/events/
|
|
|
649
717
|
- ✅ Use current, non-deprecated APIs - check package documentation
|
|
650
718
|
- ✅ Prefer TypeScript-first packages with good type definitions
|
|
651
719
|
- ✅ Check npm for latest package versions and breaking changes
|
|
720
|
+
- ✅ **Follow [code-style.md](code-style.md) for type-safe patterns**
|
|
@@ -5,9 +5,11 @@ FROM oven/bun:${BUN_VERSION}-alpine AS production
|
|
|
5
5
|
WORKDIR /app
|
|
6
6
|
|
|
7
7
|
COPY bunfig.toml package.json ./
|
|
8
|
+
COPY packages ./packages
|
|
8
9
|
COPY src ./src
|
|
9
10
|
|
|
10
|
-
RUN --mount=type=
|
|
11
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
12
|
+
--mount=type=secret,id=NPM_TOKEN \
|
|
11
13
|
export NPM_TOKEN="$(cat /run/secrets/NPM_TOKEN)" && \
|
|
12
14
|
bun install --production --omit=optional
|
|
13
15
|
|
|
@@ -6,9 +6,11 @@ FROM oven/bun:${BUN_VERSION}-alpine AS builder
|
|
|
6
6
|
WORKDIR /app
|
|
7
7
|
|
|
8
8
|
COPY package.json tsconfig*.json nest-cli.json ./
|
|
9
|
+
COPY packages ./packages
|
|
9
10
|
COPY src ./src
|
|
10
11
|
|
|
11
|
-
RUN --mount=type=
|
|
12
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
13
|
+
--mount=type=secret,id=NPM_TOKEN \
|
|
12
14
|
export NPM_TOKEN="$(cat /run/secrets/NPM_TOKEN)" && \
|
|
13
15
|
bun install
|
|
14
16
|
|
|
@@ -19,8 +21,10 @@ FROM oven/bun:${BUN_VERSION}-alpine AS deps
|
|
|
19
21
|
WORKDIR /app
|
|
20
22
|
|
|
21
23
|
COPY package.json ./
|
|
24
|
+
COPY packages ./packages
|
|
22
25
|
|
|
23
|
-
RUN --mount=type=
|
|
26
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
27
|
+
--mount=type=secret,id=NPM_TOKEN \
|
|
24
28
|
export NPM_TOKEN="$(cat /run/secrets/NPM_TOKEN)" && \
|
|
25
29
|
bun install --production --omit=optional
|
|
26
30
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const envSchema = z.object({
|
|
4
|
+
{{envKey}}_PORT: z.string().optional(),
|
|
5
|
+
{{#each envVars}}
|
|
6
|
+
{{key}}: z.string().min(1, '{{key}} is required'),
|
|
7
|
+
{{/each}}
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const result = envSchema.safeParse(process.env)
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
console.error('❌ Environment validation failed:')
|
|
13
|
+
console.error(result.error.format())
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const env = result.data
|
|
@@ -29,7 +29,7 @@ runs:
|
|
|
29
29
|
# Copy bun.lock from json folder
|
|
30
30
|
cp "out/${{ inputs.scope-short-name }}/json/bun.lock" "$CONTEXT_DIR/"
|
|
31
31
|
|
|
32
|
-
# Flatten: move service/app files to root of
|
|
32
|
+
# Flatten: move service/app files to root of context
|
|
33
33
|
if [ -d "$CONTEXT_DIR/$SCOPE_DIR" ]; then
|
|
34
34
|
# Copy all files and directories from the scope dir to context root
|
|
35
35
|
cp -r "$CONTEXT_DIR/$SCOPE_DIR"/. "$CONTEXT_DIR/"
|
|
@@ -38,12 +38,64 @@ runs:
|
|
|
38
38
|
rm -rf "$CONTEXT_DIR/$SCOPE_DIR"
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
|
-
# Remove
|
|
42
|
-
rm -rf "$CONTEXT_DIR/
|
|
41
|
+
# Remove apps and services dirs (flattened above)
|
|
42
|
+
rm -rf "$CONTEXT_DIR/apps" "$CONTEXT_DIR/services"
|
|
43
43
|
|
|
44
|
-
# Clean up root package.json workspaces
|
|
45
44
|
cd "$CONTEXT_DIR"
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
# Replace workspace:* with npm versions for published packages
|
|
47
|
+
# so bun install fetches pre-built packages from registry (with dist/)
|
|
48
|
+
# Private packages stay as workspace deps (Bun resolves their .ts source)
|
|
49
|
+
if [ -d "packages" ]; then
|
|
50
|
+
for pkg_dir in packages/*/; do
|
|
51
|
+
[ -f "$pkg_dir/package.json" ] || continue
|
|
52
|
+
|
|
53
|
+
pkg_name=$(jq -r '.name' "$pkg_dir/package.json")
|
|
54
|
+
is_private=$(jq -r '.private // false' "$pkg_dir/package.json")
|
|
55
|
+
pkg_version=$(jq -r '.version // empty' "$pkg_dir/package.json")
|
|
56
|
+
|
|
57
|
+
if [ "$is_private" = "true" ] || [ -z "$pkg_version" ]; then
|
|
58
|
+
echo " keeping workspace dep: $pkg_name (private)"
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo " replacing workspace:* → ^$pkg_version for $pkg_name"
|
|
63
|
+
|
|
64
|
+
# Replace in dependencies and devDependencies
|
|
65
|
+
for field in dependencies devDependencies; do
|
|
66
|
+
if jq -e ".${field}[\"${pkg_name}\"]" package.json > /dev/null 2>&1; then
|
|
67
|
+
jq ".${field}[\"${pkg_name}\"] = \"^${pkg_version}\"" package.json > package.json.tmp
|
|
68
|
+
mv package.json.tmp package.json
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
|
|
72
|
+
# Also update private packages that depend on this published package
|
|
73
|
+
for other_pkg in packages/*/; do
|
|
74
|
+
[ -f "$other_pkg/package.json" ] || continue
|
|
75
|
+
for field in dependencies devDependencies; do
|
|
76
|
+
if jq -e ".${field}[\"${pkg_name}\"]" "$other_pkg/package.json" > /dev/null 2>&1; then
|
|
77
|
+
jq ".${field}[\"${pkg_name}\"] = \"^${pkg_version}\"" "$other_pkg/package.json" > "$other_pkg/package.json.tmp"
|
|
78
|
+
mv "$other_pkg/package.json.tmp" "$other_pkg/package.json"
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
# Remove published package dir (will be installed from npm)
|
|
84
|
+
rm -rf "$pkg_dir"
|
|
85
|
+
done
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Set workspace config for remaining private packages, or remove it
|
|
89
|
+
if [ -d "packages" ] && [ -n "$(ls -A packages/ 2>/dev/null)" ]; then
|
|
90
|
+
jq '.workspaces = ["packages/*"]' package.json > package.json.tmp
|
|
91
|
+
mv package.json.tmp package.json
|
|
92
|
+
else
|
|
93
|
+
rm -rf packages
|
|
94
|
+
jq 'del(.workspaces)' package.json > package.json.tmp
|
|
95
|
+
mv package.json.tmp package.json
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Ensure packages/ dir exists (Dockerfiles COPY it even when empty)
|
|
99
|
+
mkdir -p packages
|
|
48
100
|
|
|
49
101
|
echo "context-dir=$CONTEXT_DIR" >> "$GITHUB_OUTPUT"
|