@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
|
|
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
|
|
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
|
-
##
|
|
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 **
|
|
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
|
|
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
|
|
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
|
|
295
|
-
- **Hot-reload friendly
|
|
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
|
|
186
|
+
Works with OpenAI and Anthropic.
|
|
298
187
|
|
|
299
188
|
---
|
|
300
189
|
|
|
301
190
|
## 🚢 Deployment
|
|
302
191
|
|
|
303
192
|
```bash
|
|
304
|
-
pf pulumi up
|
|
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
|
-
##
|
|
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
|
|
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"
|
|
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.
|
|
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",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crossdelta/platform-sdk",
|
|
3
|
-
"version": "0.21.
|
|
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.
|
|
147
|
+
"@crossdelta/infrastructure": "^0.10.1",
|
|
148
148
|
"@nestjs/schematics": "^11.0.5",
|
|
149
149
|
"turbo": "^2.0.0"
|
|
150
150
|
},
|