@crossdelta/platform-sdk 0.21.6 → 0.21.8

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
@@ -9,13 +9,10 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <code>pf</code> scaffolds real-world platforms: backend services, event-driven messaging, infrastructure all wired together and kept in sync. <strong>AI-assisted.</strong>
12
+ <code>pf</code> scaffolds real-world platforms: backend services, event-driven messaging, infrastructure. All wired together and kept in sync. <strong>AI-assisted.</strong>
13
13
  </p>
14
14
 
15
- <p align="center">
16
- <sub>Frontend SDK coming soon — contract-based client models.</sub>
17
15
 
18
- </p>
19
16
 
20
17
  <br />
21
18
 
@@ -29,55 +26,15 @@
29
26
  <img src="https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge" alt="MIT License" />
30
27
  </p>
31
28
 
32
- <p align="center">
33
- <img src="https://img.shields.io/badge/Hono-E36002?style=flat-square&logo=hono&logoColor=white" alt="Hono" />
34
- <img src="https://img.shields.io/badge/NestJS-E0234E?style=flat-square&logo=nestjs&logoColor=white" alt="NestJS" />
35
- <img src="https://img.shields.io/badge/NATS-27AAE1?style=flat-square&logo=natsdotio&logoColor=white" alt="NATS" />
36
- <img src="https://img.shields.io/badge/Pulumi-8A3391?style=flat-square&logo=pulumi&logoColor=white" alt="Pulumi" />
37
- <img src="https://img.shields.io/badge/Turborepo-EF4444?style=flat-square&logo=turborepo&logoColor=white" alt="Turborepo" />
38
- <img src="https://img.shields.io/badge/DigitalOcean-0080FF?style=flat-square&logo=digitalocean&logoColor=white" alt="DigitalOcean" />
39
- </p>
40
-
41
- <br />
42
-
43
29
  <p align="center">
44
30
  <a href="#-quick-start">Quick Start</a> •
45
31
  <a href="#-5-minute-tutorial">Tutorial</a> •
46
32
  <a href="#-commands">Commands</a> •
47
- <a href="#-ai-assisted-generation">AI Generation</a> •
48
33
  <a href="#-deployment">Deployment</a>
49
34
  </p>
50
35
 
51
36
  ---
52
37
 
53
- <br />
54
-
55
- ## 👋 Who is this for?
56
-
57
- <table>
58
- <tr>
59
- <td width="60">🏗️</td>
60
- <td><strong>Teams building full-stack platforms</strong> — services, events, apps that actually work together</td>
61
- </tr>
62
- <tr>
63
- <td>📡</td>
64
- <td><strong>Event-driven architectures</strong> — or teams ready to start using events</td>
65
- </tr>
66
- <tr>
67
- <td>😤</td>
68
- <td><strong>Engineers tired of drift</strong> — ports, env vars, and infra configs that never match</td>
69
- </tr>
70
- <tr>
71
- <td>🚀</td>
72
- <td><strong>Startups & platform teams</strong> — who want consistency without building everything from scratch</td>
73
- </tr>
74
- </table>
75
-
76
- > **You can start without understanding all the pieces.**
77
- > The depth reveals itself as you grow.
78
-
79
- ---
80
-
81
38
  ## 🚀 Quick Start
82
39
 
83
40
  ```bash
@@ -137,7 +94,7 @@ pf cloudevents add order.created --service services/notifications
137
94
  # 3. Restart to pick up new services
138
95
  pf dev
139
96
 
140
- # 4. Publish a test event watch notifications react
97
+ # 4. Publish a test event, watch notifications react
141
98
  pf cloudevents publish order.created
142
99
  ```
143
100
 
@@ -159,34 +116,7 @@ pf cloudevents publish order.created
159
116
 
160
117
  ---
161
118
 
162
- ## 💡 What problem does pf solve?
163
-
164
- <table>
165
- <tr>
166
- <th width="300">❌ The Problem</th>
167
- <th width="300">✅ With pf</th>
168
- </tr>
169
- <tr>
170
- <td>Application code lives here, infra config lives somewhere else</td>
171
- <td>Both generated from the same source of truth</td>
172
- </tr>
173
- <tr>
174
- <td>Ports, env vars, and event wiring drift over time</td>
175
- <td>Change a service → infra stays in sync</td>
176
- </tr>
177
- <tr>
178
- <td>New devs spend days setting up local environments</td>
179
- <td>Run <code>pf dev</code> → everything starts together</td>
180
- </tr>
181
- <tr>
182
- <td>Manual event subscriptions and handler registration</td>
183
- <td>Add an event → consumers are wired automatically</td>
184
- </tr>
185
- </table>
186
-
187
- ---
188
-
189
- ## 📋 Commands
119
+ ## Commands
190
120
 
191
121
  | Command | What it does |
192
122
  |:--------|:-------------|
@@ -203,48 +133,7 @@ pf cloudevents publish order.created
203
133
  | `pf lint` | ✨ Lint and format with Biome |
204
134
  | `pf audit` | 🔒 Run security audit on dependencies |
205
135
 
206
- > `pf` proxies all commands to Turborepo (works from any subdirectory).
207
-
208
- #### Custom Command Descriptions
209
-
210
- Add descriptions to your workspace commands for better autocompletion:
211
-
212
- ```json
213
- {
214
- "pf": {
215
- "commands": {
216
- "deploy": {
217
- "command": "cd infra && pulumi up",
218
- "description": "Deploy infrastructure to production"
219
- }
220
- }
221
- }
222
- }
223
- ```
224
-
225
- Then `pf <TAB>` shows: `deploy -- Deploy infrastructure to production`
226
-
227
- #### Plugins
228
-
229
- Extend `pf` with additional commands via plugins. Plugins are loaded from workspace `package.json`:
230
-
231
- ```json
232
- {
233
- "pf": {
234
- "plugins": ["@crossdelta/cloudevents"]
235
- }
236
- }
237
- ```
238
-
239
- **Built-in plugins:**
240
- - `@crossdelta/cloudevents` - Event contracts, handlers, and NATS publishing (auto-enabled in new workspaces)
241
-
242
- **Plugin commands are namespaced:**
243
- ```bash
244
- pf cloudevents add order.created --service services/orders
245
- pf cloudevents list
246
- pf cloudevents publish order.created
247
- ```
136
+ > `pf` proxies all commands to Turborepo (works from any subdirectory). Extend via [custom commands and plugins](https://www.npmjs.com/package/@crossdelta/platform-sdk#configuration).
248
137
 
249
138
  ---
250
139
 
@@ -259,7 +148,7 @@ pf new hono-micro services/notifications --ai \
259
148
  -d "Send emails when orders are created"
260
149
  ```
261
150
 
262
- The AI generates **production-ready code** that follows your platform's conventions:
151
+ The AI generates **convention-following code** tailored to your platform:
263
152
 
264
153
  <table>
265
154
  <tr>
@@ -280,30 +169,34 @@ The AI generates **production-ready code** that follows your platform's conventi
280
169
  </tr>
281
170
  <tr>
282
171
  <td>✅</td>
283
- <td><strong>Type-safe</strong> no <code>any</code>, proper imports, strict TypeScript</td>
172
+ <td><strong>Type-safe:</strong> no <code>any</code>, proper imports, strict TypeScript</td>
284
173
  </tr>
285
174
  <tr>
286
175
  <td>✅</td>
287
- <td><strong>Lint-compliant</strong> passes Biome formatting out of the box</td>
176
+ <td><strong>Lint-compliant:</strong> passes Biome formatting out of the box</td>
288
177
  </tr>
289
178
  </table>
290
179
 
291
180
  **What makes it different:**
292
181
  - Uses your **existing contracts** when wiring events between services
293
182
  - Follows **framework-specific patterns** (Hono use-cases vs. NestJS Services)
294
- - Generates **only the code you need** no bloat, no unused abstractions
295
- - **Hot-reload friendly** `pf dev` automatically restarts when AI finishes
183
+ - Generates **only the code you need**, no bloat, no unused abstractions
184
+ - **Hot-reload friendly:** `pf dev` automatically restarts when AI finishes
296
185
 
297
- Works with OpenAI (GPT-4o) and Anthropic (Claude 3.5 Sonnet).
186
+ Works with OpenAI and Anthropic.
298
187
 
299
188
  ---
300
189
 
301
190
  ## 🚢 Deployment
302
191
 
303
192
  ```bash
304
- pf pulumi up --stack dev
193
+ pf pulumi up # deploys the current Pulumi stack
305
194
  ```
306
195
 
196
+ Each workspace ships with one Pulumi stack and pre-configured GitHub Actions. Add more stacks (e.g. `stage`, `production`) as your platform grows. `pf` and `generate-env` work with any stack name.
197
+
198
+ `pf dev` generates `.env.local` from the current stack's Pulumi config (secrets, API keys) and discovered service ports.
199
+
307
200
  **Pre-configured GitHub Actions included:**
308
201
 
309
202
  | Trigger | Action |
@@ -321,13 +214,12 @@ pf pulumi up --stack dev
321
214
  |:-------|:------------|
322
215
  | `PULUMI_ACCESS_TOKEN` | [Pulumi Cloud token](https://app.pulumi.com/account/tokens) |
323
216
  | `DIGITALOCEAN_TOKEN` | [DO API token](https://cloud.digitalocean.com/account/api/tokens) |
324
- | `GHCR_TOKEN` | GitHub Container Registry PAT |
325
217
 
326
218
  </details>
327
219
 
328
220
  ---
329
221
 
330
- ## 📋 Requirements
222
+ ## ⚙️ Requirements
331
223
 
332
224
  | Requirement | Notes |
333
225
  |:------------|:------|
@@ -495,43 +387,12 @@ Every workspace includes pre-configured NATS with JetStream for development:
495
387
  - Streams materialized from contracts with retention policies
496
388
  - Persistent storage (file-based) with explicit limits
497
389
 
498
- > **Services never create streams** in dev: `pf dev` auto-creates from contracts, in prod: Pulumi materializes.
499
-
500
- See `infra/dev/README.md` for Dev-Infra vs Platform-Infra separation.
390
+ > **Services never create streams.** In dev, `pf dev` auto-creates from contracts. In prod, Pulumi materializes.
501
391
 
502
392
  </details>
503
393
 
504
394
  ---
505
395
 
506
- ## 🧭 Philosophy
507
-
508
- <table>
509
- <tr>
510
- <td width="50%">
511
-
512
- ### ✅ What pf is
513
-
514
- - Opinionated full-stack platform toolkit
515
- - Unified dev workflow — one command to run everything
516
- - Infrastructure that stays in sync with your code
517
-
518
- </td>
519
- <td width="50%">
520
-
521
- ### ❌ What pf is not
522
-
523
- - Generic scaffolder — it makes choices for you
524
- - Multi-cloud out of the box — DigitalOcean first, extensible later
525
- - Magic — no hidden daemons, you control when things run
526
-
527
- </td>
528
- </tr>
529
- </table>
530
-
531
- > **`pf` makes architectural decisions so your team doesn't have to — without hiding how things work.**
532
-
533
- ---
534
-
535
396
  ## License
536
397
 
537
398
  MIT © [crossdelta](https://crossdelta.de)
package/bin/cli.mjs CHANGED
@@ -17,7 +17,7 @@ To create a new workspace, run:
17
17
 
18
18
  \x1B[36mpf new workspace my-platform\x1B[0m
19
19
  `);return e},F=e=>{let t=e??g();return ot(I(t,"package.json"))},E=e=>{let t=F(e);if(!t?.pf?.paths)return $e;let{paths:r}=t.pf;return{services:nt(r.services,$e.services),apps:nt(r.apps,$e.apps),packages:nt(r.packages,$e.packages),contracts:nt(r.contracts,$e.contracts)}},Ie=e=>{let t=F(e);return{plugins:t?.pf?.plugins??Ra,dev:{filter:t?.pf?.dev?.filter??[]}}},x=e=>{let t=e??g(),r=E(t),n=I(t,r.contracts),s=ot(I(n,"package.json"))?.name??`${st()}/contracts`;return{packagePath:n,eventsPath:I(n,"src","events"),indexPath:I(n,"src","index.ts"),relativePath:r.contracts,packageName:s}},st=()=>{let e=g(),t=ot(I(e,"package.json")),r=`@${Fa(e)}`;return t?.name?t.name.startsWith("@")?t.name.split("/")[0]:`@${t.name}`:r},ja=(e,t)=>{let r=Un(t),n=e;for(let o of r){if(n===null||typeof n!="object"||!(o in n))return;n=n[o]}return n},_a=(e,t,r)=>{let n=Un(t),o=n[n.length-1],s=e;for(let i of n.slice(0,-1))(!(i in s)||typeof s[i]!="object"||s[i]===null)&&(s[i]={}),s=s[i];s[o]=r},La=(e,t=process.cwd())=>{let r=cr(I(t,"package.json"));return ja(r,e)},it=(e,t,r=process.cwd())=>{let n=I(r,"package.json"),o=cr(n);_a(o,e,t),Ia(n,o,{spaces:2,EOL:`
20
- `,encoding:"utf-8"})},Re=e=>{let t=jn();return Aa($a(t,e))},ar=null,lr=()=>(ar||(ar=Re("package.json")),ar),R=new Proxy({},{get:(e,t)=>lr()[t]}),ce=()=>lr().generatorConfig??Na,at=e=>{let t=e??g(),r=E(t),n=I(t,r.services);if(!Ae(n))return[];try{return Ea(n,{withFileTypes:!0}).filter(s=>s.isDirectory()&&!s.name.startsWith(".")).map(s=>`${r.services}/${s.name}`).sort()}catch{return[]}}});import{argv as Pu}from"process";import Je from"chalk";import{Command as Su}from"commander";import bu from"terminal-link";V();f();import rc from"chalk";import{Command as nc}from"commander";v();import{existsSync as Ya}from"fs";import{join as Za}from"path";import{cwd as Zn}from"process";import{execaSync as Xa}from"execa";v();import{spawn as Kn}from"child_process";import{resolve as Ga}from"path";import{execa as Ua}from"execa";import{existsSync as Wa}from"fs";import{resolve as Ha}from"path";import{config as Ba}from"dotenv";var pr=(e,t)=>{let r=Ha(t,e);return Wa(r)?Ba({path:r,processEnv:{}}).parsed||{}:{}},Jn=(e,t)=>{let r=pr(e,t),n=[];for(let[o,s]of Object.entries(r))if(o.endsWith("_PORT")&&s){let i=Number.parseInt(s,10);!Number.isNaN(i)&&i>0&&n.push(i)}return n};var mr={...process.env,NODE_NO_WARNINGS:"1"},za=(e,t,r)=>e?"pipe":t||r?["ignore","pipe","pipe"]:"inherit",Va=(e,t,r)=>e?{...mr,...e}:t||r?{...mr,CI:"true"}:{...mr},Ja=e=>e.includes("EEXIST")||e.includes("failed to link package");async function K(e,t,{cwd:r=process.cwd(),task:n,shell:o,context:s=e,quiet:i=!1,nonInteractive:a=!1,env:c}={}){try{t.length===0&&(t=e.split(" ").slice(1));let l=Ua(e,t,{cwd:r,stdio:za(i,n,a),all:n||a?!0:void 0,shell:o??!0,env:Va(c,n,a)});l.all&&l.all.on("data",p=>{let m=p.toString().trim();n?(n.output=m,m.length&&d.storeLog(m,s)):a&&m.length&&console.log(m)}),await l}catch(l){let p=l instanceof Error?l.message:String(l);if(Ja(p))return;throw new Error(Ka(p))}}var Ka=e=>{let t=e.match(/error: (.+)/);return t?t[1].trim():e};function qn(e,t,r={}){let{cwd:n=process.cwd(),envFile:o,detached:s=!0,pipeStdout:i=!1,onStdout:a,onStderr:c,onExit:l}=r,p=o?pr(o,n):{},m=Ga(n,"node_modules",".bin"),y=process.env.PATH||"",_=`${m}:${y}`,be={...process.env,...p,PATH:_,FORCE_COLOR:"1"},N=Kn(e,t,{cwd:n,env:be,stdio:["inherit",i?"pipe":"inherit","pipe"],detached:s});return a&&i&&N.stdout?.on("data",a),c&&N.stderr?.on("data",c),l&&N.on("exit",l),N}function ct(e,t,r={}){let{cwd:n=process.cwd()}=r;Kn(e,t,{cwd:n,stdio:"inherit",shell:!0}).on("exit",s=>process.exit(s||0))}async function Yn(e){let{execSync:t}=await import("child_process");for(let r of e)try{let o=t(`lsof -ti:${r}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
20
+ `,encoding:"utf-8"})},Re=e=>{let t=jn();return Aa($a(t,e))},ar=null,lr=()=>(ar||(ar=Re("package.json")),ar),R=new Proxy({},{get:(e,t)=>lr()[t]}),ce=()=>lr().generatorConfig??Na,at=e=>{let t=e??g(),r=E(t),n=I(t,r.services);if(!Ae(n))return[];try{return Ea(n,{withFileTypes:!0}).filter(s=>s.isDirectory()&&!s.name.startsWith(".")).map(s=>`${r.services}/${s.name}`).sort()}catch{return[]}}});import{argv as Pu}from"process";import Je from"chalk";import{Command as Su}from"commander";import bu from"terminal-link";V();f();import rc from"chalk";import{Command as nc}from"commander";v();import{existsSync as Ya}from"fs";import{join as Za}from"path";import{cwd as Zn}from"process";import{execaSync as Xa}from"execa";v();import{spawn as Kn}from"child_process";import{resolve as Ga}from"path";import{execa as Ua}from"execa";import{existsSync as Wa}from"fs";import{resolve as Ha}from"path";import{config as Ba}from"dotenv";var pr=(e,t)=>{let r=Ha(t,e);return Wa(r)?Ba({path:r,processEnv:{}}).parsed||{}:{}},Jn=(e,t)=>{let r=pr(e,t),n=[];for(let[o,s]of Object.entries(r))if(o.endsWith("_PORT")&&s){let i=Number.parseInt(s,10);!Number.isNaN(i)&&i>0&&n.push(i)}return n};var mr={...process.env,NODE_NO_WARNINGS:"1"},za=(e,t,r)=>e?"pipe":t||r?["ignore","pipe","pipe"]:"inherit",Va=(e,t,r)=>e?{...mr,...e}:t||r?{...mr,CI:"true"}:{...mr},Ja=e=>e.includes("EEXIST")||e.includes("failed to link package");async function K(e,t,{cwd:r=process.cwd(),task:n,shell:o,context:s=e,quiet:i=!1,nonInteractive:a=!1,env:c}={}){try{t.length===0&&(t=e.split(" ").slice(1));let l=Ua(e,t,{cwd:r,stdio:za(i,n,a),all:n||a?!0:void 0,shell:o??!0,env:Va(c,n,a)});l.all&&l.all.on("data",p=>{let m=p.toString().trim();n?(n.output=m,m.length&&d.storeLog(m,s)):a&&m.length&&console.log(m)}),await l}catch(l){let p=l instanceof Error?l.message:String(l);if(Ja(p))return;throw new Error(Ka(p))}}var Ka=e=>{let t=e.match(/error: (.+)/);return t?t[1].trim():e};function qn(e,t,r={}){let{cwd:n=process.cwd(),envFile:o,detached:s=!0,pipeStdout:i=!1,onStdout:a,onStderr:c,onExit:l}=r,p=o?pr(o,n):{},m=Ga(n,"node_modules",".bin"),y=process.env.PATH||"",_=`${m}:${y}`,be={...process.env,...p,PATH:_,FORCE_COLOR:"1"},N=Kn(e,t,{cwd:n,env:be,stdio:["inherit",i?"pipe":"inherit","pipe"],detached:s});return a&&i&&N.stdout?.on("data",a),c&&N.stderr?.on("data",c),l&&N.on("exit",l),N}function ct(e,t,r={}){let{cwd:n=process.cwd()}=r;Kn(e,t,{cwd:n,stdio:"inherit"}).on("exit",s=>process.exit(s||0))}async function Yn(e){let{execSync:t}=await import("child_process");for(let r of e)try{let o=t(`lsof -ti:${r}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
21
21
  `).filter(Boolean);for(let s of o)try{process.kill(Number.parseInt(s,10),"SIGKILL")}catch{}}catch{}}f();var lt=e=>Ee(e);function dr(e=process.cwd()){let t=r=>Ya(Za(e,r));if(t("bun.lock")||t("bun.lockb"))return"bun";if(t("pnpm-lock.yaml"))return"pnpm";if(t("yarn.lock"))return"yarn";if(t("package-lock.json"))return"npm"}function b(e){return dr(e)??Qa()}function Qa(){return lt("bun")?"bun":lt("pnpm")?"pnpm":lt("yarn")?"yarn":"npm"}function Xn(){return["bun","pnpm","yarn","npm"].filter(lt)}async function L(e,t){let r=ec(e,t),{mergedOptions:n,packages:o}=r,{cwd:s=process.cwd(),flags:i=[],task:a,quiet:c}=n,l=n.packageManager??b(),p=[],m=Array.isArray(o)?o:[o];l==="bun"&&(m=m.map(_=>_.replace("git+ssh://",""))),l==="pnpm"&&(p.push("--config.engine-strict=false"),p.push("--no-frozen-lockfile")),["yarn","npm"].includes(l)&&p.push("--ignore-engines");let y=m.length?l==="npm"?["install",...i,...p,...m]:["add",...i,...p,...m]:["install",...i,...p];await K(l,y,{cwd:s,task:a,quiet:c})}async function Qn(e){let t=["-g"],r=[...e.flags??[],...t];await L({...e,flags:r,packageManager:e.packageManager??b()})}function eo(e,t,r){let{args:n,mergedOptions:o}=ro(t??[],r),s=o.manager??b(),{command:i,args:a}=to(s);console.log(`
22
22
  Running command: ${i} ${[...a,e,...n].join(" ")}
23
23
  `),Xa(i,[...a,e,...n],{cwd:o.cwd??Zn(),stdio:"inherit",preferLocal:!0})}async function Ne(e,t,r){let{args:n,mergedOptions:o}=ro(t??[],r),s=o.manager??b(),{command:i,args:a}=to(s);await K(i,[...a,e,...n],o)}async function Fe(e,t){let{script:r,mergedOptions:n}=tc(e,t),o=n.packageManager??b();await K(o,["run",r,...n.args??[]],{cwd:n.cwd??Zn(),task:n.task})}function to(e){let r={bun:"bunx",pnpm:"npx",yarn:"npx",npm:"npx"}[e];if(!r)throw new Error(`No executor found for the detected package manager: ${e}`);let[n,...o]=r.split(" ");return{command:n,args:o}}function ec(e,t){return Array.isArray(e)?{packages:e,mergedOptions:{...t,packages:e}}:{packages:e.packages??[],mergedOptions:e}}function ro(e,t){return Array.isArray(e)?{args:e,mergedOptions:t??{}}:{args:[],mergedOptions:e}}function tc(e,t){return typeof e=="string"?{script:e,mergedOptions:t??{}}:{script:e.script,mergedOptions:e}}var oc=e=>e==="yarn"?["npm","audit"]:["audit"],no=new nc("audit").description("Run security audit on dependencies").allowUnknownOption(!0).action((e,t)=>{try{let r=g(),n=b(),o=oc(n),s=t.args||[];console.log(rc.dim(`Running ${n} ${[...o,...s].join(" ")}`)),ct(n,[...o,...s],{cwd:r})}catch(r){$(r)}});import q from"chalk";import{existsSync as vc,readFileSync as kc,writeFileSync as Cc}from"fs";import{homedir as xc}from"os";import{join as wc}from"path";import{createCipheriv as sc,createDecipheriv as ic,createHash as ac,randomBytes as cc}from"crypto";import{existsSync as lc,readFileSync as pc,writeFileSync as mc}from"fs";import{homedir as oo,hostname as dc,userInfo as gc}from"os";import{join as uc}from"path";var gr=uc(oo(),".platform-sdk-keys"),so="aes-256-gcm",io=()=>{let e=`${dc()}:${gc().username}:${oo()}`;return ac("sha256").update(e).digest()},ao=()=>{if(!lc(gr))return{};try{return JSON.parse(pc(gr,"utf-8"))}catch{return{}}},fc=e=>{mc(gr,JSON.stringify(e,null,2),{mode:384})},hc=e=>{let t=io(),r=cc(16),n=sc(so,t,r),o=n.update(e,"utf8","hex");return o+=n.final("hex"),{iv:r.toString("hex"),tag:n.getAuthTag().toString("hex"),data:o}},yc=e=>{let t=io(),r=Buffer.from(e.iv,"hex"),n=Buffer.from(e.tag,"hex"),o=ic(so,t,r);o.setAuthTag(n);let s=o.update(e.data,"hex","utf8");return s+=o.final("utf8"),s};async function co(e,t){let r=ao();r[e]=hc(t),fc(r)}async function pt(e){let r=ao()[e];if(!r)return null;try{return yc(r)}catch{return null}}var Oe={openai:{id:"openai",name:"OpenAI",defaultApiKeyEnvVar:"OPENAI_API_KEY",defaultModel:"gpt-4o-mini",modelsApiUrl:"https://api.openai.com/v1/models",apiKeyUrl:"https://platform.openai.com/api-keys",buildHeaders:e=>({Authorization:`Bearer ${e}`}),parseModelsResponse:e=>e.data??[],modelFilters:[/^gpt-4/,/^gpt-3\.5/,/^o1/,/^o3/]},anthropic:{id:"anthropic",name:"Anthropic (Claude)",defaultApiKeyEnvVar:"ANTHROPIC_API_KEY",defaultModel:"claude-sonnet-4-20250514",modelsApiUrl:"https://api.anthropic.com/v1/models",apiKeyUrl:"https://console.anthropic.com/settings/keys",buildHeaders:e=>({"x-api-key":e,"anthropic-version":"2023-06-01"}),parseModelsResponse:e=>(e.data??[]).map(r=>({id:r.id,created:new Date(r.created_at).getTime()})),modelFilters:[/^claude-/]}};function lo(){return Object.entries(Oe).map(([e,t])=>({name:t.name,value:e}))}function Me(e){let t=Oe[e];if(!t)throw new Error(`Unknown AI provider: ${e}`);return t}var Q=wc(xc(),".platform-sdk-ai.json"),Pc=()=>{let e="anthropic",t=Oe[e];return{provider:e,model:t.defaultModel}},ur=Pc();function De(){return vc(Q)}function mt(){if(!De())throw new Error("AI configuration not found. Please run 'pf ai:setup' to configure your AI provider.");try{let e=kc(Q,"utf-8"),t=JSON.parse(e);return{...ur,...t}}catch{throw new Error(`Failed to load AI configuration from ${Q}. Please run 'pf setup --ai' again.`)}}function po(e){let t=JSON.stringify(e,null,2);Cc(Q,t,"utf-8")}async function mo(e,t){await co(e,t)}async function go(e){let t=await pt(e.provider);if(t)return t;let r=Oe[e.provider],n=process.env[r.defaultApiKeyEnvVar];if(n)return n;throw new Error("API key not found. Please run 'pf setup --ai' to configure your API key.")}var Sc=async(e,t,r,n)=>{let{createOpenAI:o}=await import("@ai-sdk/openai"),s=o({apiKey:r});if(n.onToken){let{streamText:c}=await import("ai"),l=c({model:s(t),prompt:e,system:n.system,maxOutputTokens:n.maxTokens,temperature:n.temperature}),p="";for await(let m of(await l).textStream)p+=m,n.onToken(m);return p}let{generateText:i}=await import("ai"),{text:a}=await i({model:s(t),prompt:e,system:n.system,maxOutputTokens:n.maxTokens,temperature:n.temperature});return a},bc=async(e,t,r,n)=>{let{createAnthropic:o}=await import("@ai-sdk/anthropic"),s=o({apiKey:r});if(n.onToken){let{streamText:c}=await import("ai"),l=c({model:s(t),prompt:e,system:n.system,maxOutputTokens:n.maxTokens,temperature:n.temperature}),p="";for await(let m of(await l).textStream)p+=m,n.onToken(m);return p}let{generateText:i}=await import("ai"),{text:a}=await i({model:s(t),prompt:e,system:n.system,maxOutputTokens:n.maxTokens,temperature:n.temperature});return a};async function uo(e,t,r={}){let{system:n,maxTokens:o=4096,temperature:s=.7,onToken:i}=r,a=await go(e);switch(e.provider){case"openai":return Sc(t,e.model,a,{system:n,maxTokens:o,temperature:s,onToken:i});case"anthropic":return bc(t,e.model,a,{system:n,maxTokens:o,temperature:s,onToken:i});default:throw new Error(`Unsupported AI provider: ${e.provider}`)}}V();import{existsSync as Yo,readFileSync as Ql,unlinkSync as ep,writeFileSync as tp}from"fs";import{join as Zo,resolve as Ko}from"path";import{input as rp}from"@inquirer/prompts";import h from"chalk";import np from"ora";import{existsSync as fr,readdirSync as $c}from"fs";import{dirname as Ac,join as fo}from"path";import{dirname as Ec}from"path";import{fileURLToPath as Tc}from"url";var W=e=>Ec(Tc(e));nr();var Ic=()=>typeof import.meta?.url=="string"?W(import.meta.url):typeof __dirname=="string"?__dirname:process.cwd(),ee=e=>{let t=e??Ic(),r=o=>fr(fo(o,"package.json")),n=Ac(t);if(r(t))return t;if(t==="/")throw new Error("Could not find package.json (package root)");return ee(n)},ho=(e,t)=>{let r=e.find(fr);if(!r)throw new Error(t??`Directory not found. Searched in: ${e.join(", ")}`);return r},yo=async e=>{let{getWorkspacePathsConfig:t}=await Promise.resolve().then(()=>(f(),Vn)),r=t(e);return[r.services,r.apps].flatMap(o=>{let s=fo(e,o);if(!fr(s))return[];try{return $c(s,{withFileTypes:!0}).filter(i=>i.isDirectory()&&i.name!==".gitkeep").map(i=>`${o}/${i.name}`)}catch{return[]}})};import{existsSync as tl,readFileSync as rl}from"fs";import{join as nl}from"path";import{Project as ol}from"ts-morph";import{deriveEventNames as Ny,getContractPaths as Fy,getStreamName as Oy}from"@crossdelta/cloudevents";import{existsSync as dt,readFileSync as vo,writeFileSync as Rc}from"fs";import{join as hr}from"path";import{getStreamName as Nc}from"@crossdelta/cloudevents";var ko=(e,t)=>{let r=hr(e,"src","index.ts");if(!dt(r))return!1;let n=vo(r,"utf-8"),o=t.toLowerCase();return new RegExp(`streams:\\s*\\[[^\\]]*['"]${t}['"]`).test(n)||n.includes(`stream: '${t}'`)?!0:new RegExp(`subjects:\\s*\\[.*['"]${o}\\.[*>]`).test(n)},Fc=e=>{if(e.includes("@crossdelta/cloudevents"))return e;let t=`import { consumeJetStreams } from '@crossdelta/cloudevents'
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@crossdelta/cloudevents": "^0.7.20",
11
- "@crossdelta/infrastructure": "^0.8.6",
11
+ "@crossdelta/infrastructure": "^0.10.1",
12
12
  "{{scope}}/contracts": "workspace:*",
13
13
  "@pulumi/digitalocean": "^4.55.0",
14
14
  "@pulumi/kubernetes": "^4.21.0",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@crossdelta/cloudevents": "^0.7.20",
23
- "@crossdelta/infrastructure": "^0.8.6",
23
+ "@crossdelta/infrastructure": "^0.10.1",
24
24
  "zod": "^4.0.0"
25
25
  },
26
26
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.21.6",
3
+ "version": "0.21.8",
4
4
  "description": "Platform toolkit for event-driven microservices — keeping code and infrastructure in lockstep.",
5
5
  "keywords": [
6
6
  "cli",
@@ -144,7 +144,7 @@
144
144
  "zod": "^4.0.0"
145
145
  },
146
146
  "peerDependencies": {
147
- "@crossdelta/infrastructure": "^0.8.6",
147
+ "@crossdelta/infrastructure": "^0.10.1",
148
148
  "@nestjs/schematics": "^11.0.5",
149
149
  "turbo": "^2.0.0"
150
150
  },