@crossdelta/platform-sdk 0.7.10 β†’ 0.7.12

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
@@ -33,17 +33,42 @@
33
33
  <br />
34
34
 
35
35
  ---
36
- ## πŸš€ Quick Start
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ # Pick your package manager
41
+ bun add -g @crossdelta/platform-sdk
42
+ npm install -g @crossdelta/platform-sdk
43
+ pnpm add -g @crossdelta/platform-sdk
44
+ ```
45
+
46
+ <details>
47
+ <summary>Alternative: Auto-installer or use without installing</summary>
48
+
49
+ ```bash
50
+ # Auto-installer (detects package manager, offers to install Bun if needed)
51
+ curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
52
+
53
+ # Or run directly without global install
54
+ bunx @crossdelta/platform-sdk new workspace my-platform
55
+ npx @crossdelta/platform-sdk new workspace my-platform
56
+ ```
57
+
58
+ </details>
59
+
60
+ ---
61
+
62
+ ## Quick Start
37
63
 
38
64
  ```bash
39
- # Create workspace (works with bunx/npx/pnpm dlx/yarn dlx)
40
- bunx @crossdelta/platform-sdk new workspace my-platform -y
65
+ # Create workspace
66
+ pf new workspace my-platform -y
41
67
  cd my-platform
42
68
 
43
69
  # Generate microservice with AI
44
- # (first time: run `pf setup --ai` to configure provider)
45
- bunx @crossdelta/platform-sdk new hono-micro services/orders --ai \
46
- -d "Handle order creation and payment"
70
+ pf setup --ai # Configure AI provider (first time only)
71
+ pf new hono-micro services/orders --ai -d "Handle order creation and payment"
47
72
 
48
73
  # Start development
49
74
  pf dev
@@ -71,43 +96,8 @@ pf pulumi up # β†’ runs in infra/ directory
71
96
 
72
97
  **Why:** Keeps commands consistent across the team and works from any subdirectory.
73
98
 
74
- **How it works:** `pf` walks up to find your workspace root, then proxies to your scripts. Configured commands via `pf.commands` can override the working directory or command. Registered `pf` commands (like `pf new`) take precedence.
75
-
76
99
  > **πŸ“– Note:** You can be productive with `pf` in minutes using the commands above. The sections below are reference documentationβ€”explore them when you need specific details.
77
100
 
78
- ### No runtime installed?
79
-
80
- If you don't have a JavaScript runtime, use our installer that sets everything up.
81
- **The installer is optional** β€” you can always use `bunx`/`npx` or a global install.
82
-
83
- ```bash
84
- # Auto-installer (detects bun/pnpm/yarn/npm; if none found it can install Bun)
85
- curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
86
-
87
- # Then use short commands
88
- pf new workspace my-platform
89
- pf new hono-micro services/orders --ai -d "..."
90
- ```
91
-
92
- > **Security:** The installer asks permission before installing Bun (if needed) and does not run any workspace commands automatically.
93
-
94
- <details>
95
- <summary><strong>πŸ” What does the installer do?</strong></summary>
96
-
97
- The script ([view source](https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh)):
98
- 1. Checks for bun/pnpm/yarn/npm
99
- 2. If none found, **asks permission** to install Bun
100
- 3. Installs `pf` CLI globally
101
-
102
- **Manual installation:**
103
- ```bash
104
- # With Bun/npm/yarn/pnpm
105
- bun add -g @crossdelta/platform-sdk
106
- # or: npm install -g @crossdelta/platform-sdk
107
- ```
108
-
109
- </details>
110
-
111
101
  <br />
112
102
 
113
103
  ---
@@ -161,30 +151,23 @@ When you create a workspace with `pf`, you get a **Turborepo monorepo** with thi
161
151
 
162
152
  ```
163
153
  my-platform/
164
- β”œβ”€β”€ services/ # Microservices (Hono, NestJS)
165
- β”‚ β”œβ”€β”€ orders/ # Example: Order processing service (Event Publisher)
166
- β”‚ β”œβ”€β”€ notifications/# Example: Notification service (Event Consumer)
154
+ β”œβ”€β”€ services/ # Microservices (generate with pf new hono-micro)
167
155
  β”‚ └── nats/ # NATS message broker (auto-scaffolded)
168
- β”œβ”€β”€ apps/ # Frontend apps (optional: Qwik, Next.js, etc.)
156
+ β”œβ”€β”€ apps/ # Frontend apps (optional)
169
157
  β”œβ”€β”€ packages/ # Shared libraries
170
- β”‚ β”œβ”€β”€ contracts/ # Event contracts (Schema Registry - created when generating Event Consumers)
171
- β”‚ β”‚ └── src/
172
- β”‚ β”‚ └── index.ts # Export all contracts
173
- β”‚ β”œβ”€β”€ cloudevents/ # Event publishing/consuming toolkit
174
- β”‚ β”œβ”€β”€ telemetry/ # OpenTelemetry setup
175
- β”‚ └── infrastructure/ # Pulumi utilities and K8s builders
158
+ β”‚ └── contracts/ # Event contracts (Schema Registry)
159
+ β”‚ └── src/
160
+ β”‚ └── index.ts
176
161
  β”œβ”€β”€ infra/ # Pulumi Infrastructure-as-Code
177
162
  β”‚ β”œβ”€β”€ index.ts # Main Pulumi program
178
- β”‚ └── services/ # Per-service K8s configs
179
- β”‚ β”œβ”€β”€ orders.ts
180
- β”‚ └── notifications.ts
163
+ β”‚ └── services/ # Per-service K8s configs (auto-generated)
181
164
  β”œβ”€β”€ .github/
182
- β”‚ └── workflows/ # CI/CD pipelines (auto-generated)
165
+ β”‚ └── workflows/ # CI/CD pipelines
183
166
  β”œβ”€β”€ turbo.json # Turborepo task orchestration
184
167
  └── .env.local # Auto-generated from infra configs
185
168
  ```
186
169
 
187
- > **Note:** The `packages/contracts` package starts with just an `index.ts`. Event contract files (e.g., `events/order-created.ts`) are automatically generated when you create **Event Consumer** services with `pf new hono-micro --ai`.
170
+ > **Note:** Services use external packages (`@crossdelta/cloudevents`, `@crossdelta/telemetry`, `@crossdelta/infrastructure`) installed via npm. Only `packages/contracts` is local to your workspace.
188
171
 
189
172
  ### Key Architectural Decisions
190
173
 
@@ -201,11 +184,11 @@ my-platform/
201
184
  Services communicate via **CloudEvents** over **NATS JetStream** using the **Schema Registry** as single source of truth:
202
185
 
203
186
  ```typescript
204
- // packages/contracts/src/events/order-created.ts (Schema Registry)
187
+ // packages/contracts/src/events/orders-created.ts (Schema Registry)
205
188
  import { createContract } from '@crossdelta/cloudevents'
206
189
  import { z } from 'zod'
207
190
 
208
- export const OrderCreatedContract = createContract({
191
+ export const OrdersCreatedContract = createContract({
209
192
  type: 'orders.created',
210
193
  schema: z.object({
211
194
  orderId: z.string(),
@@ -214,24 +197,24 @@ export const OrderCreatedContract = createContract({
214
197
  }),
215
198
  })
216
199
 
217
- export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
200
+ export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
218
201
 
219
202
  // Service A publishes an event (Event Publisher)
220
203
  import { publish } from '@crossdelta/cloudevents'
221
- import { OrderCreatedContract } from '@my-platform/contracts'
204
+ import { OrdersCreatedContract } from '@my-platform/contracts'
222
205
 
223
- await publish(OrderCreatedContract, {
206
+ await publish(OrdersCreatedContract, {
224
207
  orderId: '123',
225
208
  customerId: 'cust-456',
226
209
  total: 99.99
227
210
  })
228
211
 
229
212
  // Service B auto-discovers and handles it (Event Consumer)
230
- // File: services/notifications/src/events/order-created.event.ts
213
+ // File: services/notifications/src/events/orders-created.event.ts
231
214
  import { handleEvent } from '@crossdelta/cloudevents'
232
- import { OrderCreatedContract, type OrderCreatedData } from '@my-platform/contracts'
215
+ import { OrdersCreatedContract, type OrdersCreatedData } from '@my-platform/contracts'
233
216
 
234
- export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData) => {
217
+ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
235
218
  await sendNotification(data)
236
219
  })
237
220
  ```
@@ -467,21 +450,21 @@ await publish('orders.created', { orderId: 'ord_123', total: 99.99 })
467
450
  ### Consume Events (Auto-Discovered)
468
451
 
469
452
  ```typescript
470
- // services/notifications/src/events/order-created.event.ts
453
+ // services/notifications/src/events/orders-created.event.ts
471
454
  import { handleEvent } from '@crossdelta/cloudevents'
472
455
  import { z } from 'zod'
473
456
 
474
- const OrderCreatedSchema = z.object({
457
+ const OrdersCreatedSchema = z.object({
475
458
  orderId: z.string(),
476
459
  total: z.number(),
477
460
  })
478
461
 
479
462
  // Export type for use in use-cases
480
- export type OrderCreatedEvent = z.infer<typeof OrderCreatedSchema>
463
+ export type OrdersCreatedEvent = z.infer<typeof OrdersCreatedSchema>
481
464
 
482
465
  export default handleEvent(
483
466
  {
484
- schema: OrderCreatedSchema,
467
+ schema: OrdersCreatedSchema,
485
468
  type: 'orders.created',
486
469
  },
487
470
  async (data) => {
package/bin/cli.js CHANGED
@@ -142,7 +142,7 @@ export type ${y} = z.infer<typeof ${g}.schema>
142
142
  `;(0,H.writeFileSync)(x,w,"utf-8");let v=(0,H.readFileSync)(l,"utf-8"),k=`export * from './events/${m}'`;if(!v.includes(k)){let M=v.split(`
143
143
  `),K=M.findLastIndex(Be=>Be.startsWith("export"));K>=0?M.splice(K+1,0,k):M.push("",k),v=M.join(`
144
144
  `),(0,H.writeFileSync)(l,v,"utf-8")}let O=(0,H.readFileSync)(r,"utf-8").replace(/import\s+\{[^}]*\}\s+from\s+['"]\.\.\/types\/[^'"]+['"]/,`import { ${g}, type ${y} } from '${p}/contracts'`).replace(/handleEvent\s*\(\s*\{[\s\S]*?type:\s*['"]([^'"]+)['"][\s\S]*?schema:\s*\w+Schema[\s\S]*?\}\s*,/,`handleEvent(${g},`).replace(new RegExp(`\\b${i.replace("Schema","Event")}\\b`,"g"),y);(0,H.writeFileSync)(r,O,"utf-8");let _=`${m}.mock.json`,C=(0,Se.join)(c,_),T={eventName:t,description:`Mock data for ${t} event`,data:n};Object.keys(o).length>0&&(T.faker=o),(0,H.writeFileSync)(C,JSON.stringify(T,null,2),"utf-8")},o5=async(r,e={})=>{let{outputDir:t,overwrite:s=!1}=e;if(!(0,H.existsSync)(r))return S.error(`Event handler not found: ${r}`),{mockPath:null};let i=(0,H.readFileSync)(r,"utf-8");if(i.includes("Contract")&&(i.includes("contracts'")||i.includes('contracts"')))return await i5(r,i,e);let n=Z4(i);if(!n)return S.error(`Could not parse event handler: ${r}`),{mockPath:null};let{eventType:o,schemaCode:a,schemaName:c}=n,l=a;if(!a){let y=(0,Se.dirname)(r),w=(0,Se.dirname)(y),v=(0,Se.join)(w,"types","events.ts");if((0,H.existsSync)(v)){let k=(0,H.readFileSync)(v,"utf-8"),A=k.indexOf(`export const ${c}`);if(A>=0){let O=k.indexOf("z.object(",A),_=k.indexOf("{",O),C=1,T=_+1;for(;T<k.length&&C>0;)k[T]==="{"&&C++,k[T]==="}"&&C--,T++;let M=k.indexOf(")",T);M>=0&&(l=k.substring(A,M+1).replace(/^export\s+/,""))}}if(!l)return S.error(`Could not load schema ${c} from types file`),{mockPath:null}}let{data:u,faker:h}=await Q4(l,c,r),f=ae();if((i.includes("from '../types/")||i.includes('from "../types/'))&&!t)return await n5(r,f,o,l,c,u,h),{mockPath:null,skipped:!0,reason:"Migrated to Advanced Mode (contracts)"};let p=t??lt().eventsPath;if(!(0,H.existsSync)(p))return S.error(`Contracts events directory not found: ${p}`),{mockPath:null};let b=`${Wg(o)}.mock.json`,x=(0,Se.join)(p,b);if((0,H.existsSync)(x)&&!s)return S.warn(`Mock file already exists: ${x}`),{mockPath:null};let g={eventName:o,description:`Mock data for ${o} event`,data:u};return Object.keys(h).length>0&&(g.faker=h),(0,H.writeFileSync)(x,JSON.stringify(g,null,2),"utf-8"),{mockPath:x}},Jo=async(r,e={})=>{let t=(0,Se.join)(r,"src","events");if(!(0,H.existsSync)(t))return{mockPaths:[],skippedHandlers:[]};let s=(0,H.readdirSync)(t).filter(a=>a.endsWith(".event.ts")).map(a=>(0,Se.join)(t,a)),i=await Promise.all(s.map(a=>o5(a,e))),n=i.filter(a=>a.mockPath!==null).map(a=>a.mockPath),o=i.filter(a=>a.skipped).map((a,c)=>`${(0,Se.basename)(s[c])} (${a.reason})`);return{mockPaths:n,skippedHandlers:o}};var Zr=require("node:fs"),s2=require("node:path"),O0=require("node:process"),i2=require("execa");var T0=require("node:child_process"),Qg=require("node:path"),e2=require("execa");var Yg=require("node:fs"),Jg=require("node:path"),Zg=E(Kg()),F0=(r,e)=>{let t=(0,Jg.resolve)(e,r);return(0,Yg.existsSync)(t)?(0,Zg.config)({path:t,processEnv:{}}).parsed||{}:{}},Xg=(r,e)=>{let t=F0(r,e),s=[];for(let[i,n]of Object.entries(t))if(i.endsWith("_PORT")&&n){let o=Number.parseInt(n,10);!Number.isNaN(o)&&o>0&&s.push(o)}return s};var E5={...process.env,NODE_NO_WARNINGS:"1"};async function vr(r,e,{cwd:t=process.cwd(),task:s,shell:i,context:n=r,quiet:o=!1,nonInteractive:a=!1,env:c}={}){try{e.length===0&&(e=r.split(" ").slice(1));let l=o?"pipe":s||a?["ignore","pipe","pipe"]:"inherit",u={...E5};c?u={...u,...c}:(s||a)&&(u={...u,CI:"true"});let h=(0,e2.execa)(r,e,{cwd:t,stdio:l,all:s||a?!0:void 0,shell:i??!0,env:u});h.all&&h.all.on("data",f=>{let d=f.toString().trim();s?(s.output=d,d.length&&S.storeLog(d,n)):a&&d.length&&console.log(d)}),await h}catch(l){let u=l instanceof Error?l.message:String(l),h=S5(u);throw new Error(h)}}var S5=r=>{let e=r.match(/error: (.+)/);return e?e[1].trim():r};function t2(r,e,t={}){let{cwd:s=process.cwd(),envFile:i,detached:n=!0,pipeStdout:o=!1,onStdout:a,onStderr:c,onExit:l}=t,u=i?F0(i,s):{},h=(0,Qg.resolve)(s,"node_modules",".bin"),f=process.env.PATH||"",d=`${h}:${f}`,p={...process.env,...u,PATH:d,FORCE_COLOR:"1"},m=(0,T0.spawn)(r,e,{cwd:s,env:p,stdio:["inherit",o?"pipe":"inherit","pipe"],detached:n});return a&&o&&m.stdout?.on("data",a),c&&m.stderr?.on("data",c),l&&m.on("exit",l),m}function P0(r,e,t={}){let{cwd:s=process.cwd()}=t;(0,T0.spawn)(r,e,{cwd:s,stdio:"inherit",shell:!0}).on("exit",n=>process.exit(n||0))}async function r2(r){let{execSync:e}=await import("node:child_process");for(let t of r)try{let i=e(`lsof -ti:${t}`,{encoding:"utf8",stdio:"pipe"}).trim().split(`
145
- `).filter(Boolean);for(let n of i)try{process.kill(Number.parseInt(n,10),"SIGKILL")}catch{}}catch{}}var Xo=r=>Mi(r);function n2(r){let e=(0,s2.join)(nn(),"node_modules",r);return(0,Zr.existsSync)(e)}var o2=r=>Object.keys(Dr.scripts??{}).includes(r);function I0(){if((0,Zr.existsSync)("bun.lock")||(0,Zr.existsSync)("bun.lockb"))return"bun";if((0,Zr.existsSync)("pnpm-lock.yaml"))return"pnpm";if((0,Zr.existsSync)("yarn.lock"))return"yarn";if((0,Zr.existsSync)("package-lock.json"))return"npm"}function Le(){return I0()??B0()}function B0(){return Xo("bun")?"bun":Xo("pnpm")?"pnpm":Xo("yarn")?"yarn":"npm"}function a2(){return["bun","pnpm","yarn","npm"].filter(Xo)}async function Cr(r,e){let t=k5(r,e),{mergedOptions:s,packages:i}=t,{cwd:n=process.cwd(),flags:o=[],task:a}=s,c=s.packageManager??Le(),l=[],u=Array.isArray(i)?i:[i];c==="bun"&&(u=u.map(f=>f.replace("git+ssh://",""))),c==="pnpm"&&(l.push("--config.engine-strict=false"),l.push("--no-frozen-lockfile")),["yarn","npm"].includes(c)&&l.push("--ignore-engines");let h=u.length?c==="npm"?["install",...o,...l,...u]:["add",...o,...l,...u]:["install",...l];await vr(c,h,{cwd:n,task:a})}async function c2(r){let e=["-g"],t=[...r.flags??[],...e];await Cr({...r,flags:t,packageManager:r.packageManager??Le()})}function l2(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);(0,i2.execaSync)(o,[...a,r,...s],{cwd:i.cwd??(0,O0.cwd)(),stdio:"inherit",preferLocal:!0})}async function Mt(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);await vr(o,[...a,r,...s],i)}async function Qo(r,e){let{script:t,mergedOptions:s}=_5(r,e),i=s.packageManager??Le();await vr(i,["run",t,...s.args??[]],{cwd:s.cwd??(0,O0.cwd)(),task:s.task})}function u2(r){let t={bun:"bunx",pnpm:"npx",yarn:"npx",npm:"npx"}[r];if(!t)throw new Error(`No executor found for the detected package manager: ${r}`);let[s,...i]=t.split(" ");return{command:s,args:i}}function k5(r,e){return Array.isArray(r)?{packages:r,mergedOptions:{...e,packages:r}}:{packages:r.packages??[],mergedOptions:r}}function h2(r,e){return Array.isArray(r)?{args:r,mergedOptions:e??{}}:{args:[],mergedOptions:r}}function _5(r,e){return typeof r=="string"?{script:r,mergedOptions:e??{}}:{script:r.script,mergedOptions:r}}function A5(r){let e=r.split(/\s+/),t=e[0],s=e.slice(1);if(t==="pf"){t=process.argv[1];let i=s.filter(o=>o.startsWith("-"));s=[...s.filter(o=>!o.startsWith("-")),...i]}return{executable:t,args:s}}async function F5(r,e){try{let{executable:t,args:s}=A5(r.command);return await vr(t,s,{cwd:e,shell:!1}),{command:r.command,success:!0}}catch(t){return{command:r.command,success:!1,error:t instanceof Error?t.message:String(t)}}}async function f2(r,e){let t=[];for(let s of r){let i=await F5(s,e);if(t.push(i),!i.success)break}return t}function d2(r){let e=[/^pf\s+new\s+/,/^pf\s+add\s+/,/^bun\s+pf\s+new\s+/,/^bun\s+pf\s+add\s+/];return r.filter(t=>e.some(s=>s.test(t.command)))}function p2(r){for(let e of r){let t=e.command.match(/(?:bun\s+)?pf\s+new\s+\S+\s+(\S+)/);if(t)return t[1]}return null}var qs=require("node:fs"),m2=require("node:path");var x2=r=>{let{eventsPath:e}=lt(r);if(!(0,qs.existsSync)(e))return[];let t=[];try{let s=(0,qs.readdirSync)(e).filter(i=>i.endsWith(".ts")&&!i.endsWith(".mock.json")&&i!=="index.ts");for(let i of s){let n=(0,m2.join)(e,i),o=(0,qs.readFileSync)(n,"utf-8"),a=T5(o,n);a&&t.push(a)}}catch(s){return console.warn("Warning: Could not scan contracts package:",s),[]}return t},T5=(r,e)=>{try{let t=r.match(/export const (\w+Contract) = createContract/);if(!t)return null;let s=t[1],i=r.match(/export type (\w+(?:Data|Event)) = z\.infer/);if(!i)return null;let n=i[1],o=r.match(/type:\s*['"]([^'"]+)['"]/);if(!o)return null;let a=o[1],c=P5(r);return{name:s,typeName:n,eventType:a,fields:c,filePath:e}}catch{return null}},P5=r=>{let e=[],t=r.match(/z\.object\({([^}]+)\}/);if(!t)return e;let s=t[1],i=/(\w+):\s*z\.(\w+)\([^)]*\)/g,n;for(;(n=i.exec(s))!==null;){let[,o,a]=n,l={string:"string",number:"number",boolean:"boolean",array:"array",object:"object",date:"date"}[a]||a;e.push(`${o}: ${l}`)}if(r.includes("z.array(")){let o=/(\w+):\s*z\.array\(/g;for(;(n=o.exec(r))!==null;){let a=n[1];e.some(c=>c.startsWith(a))||e.push(`${a}: array`)}}return e},g2=(r,e)=>{if(r.length===0)return`
145
+ `).filter(Boolean);for(let n of i)try{process.kill(Number.parseInt(n,10),"SIGKILL")}catch{}}catch{}}var Xo=r=>Mi(r);function n2(r){let e=(0,s2.join)(nn(),"node_modules",r);return(0,Zr.existsSync)(e)}var o2=r=>Object.keys(Dr.scripts??{}).includes(r);function I0(){if((0,Zr.existsSync)("bun.lock")||(0,Zr.existsSync)("bun.lockb"))return"bun";if((0,Zr.existsSync)("pnpm-lock.yaml"))return"pnpm";if((0,Zr.existsSync)("yarn.lock"))return"yarn";if((0,Zr.existsSync)("package-lock.json"))return"npm"}function Le(){return I0()??B0()}function B0(){return Xo("bun")?"bun":Xo("pnpm")?"pnpm":Xo("yarn")?"yarn":"npm"}function a2(){return["bun","pnpm","yarn","npm"].filter(Xo)}async function Cr(r,e){let t=k5(r,e),{mergedOptions:s,packages:i}=t,{cwd:n=process.cwd(),flags:o=[],task:a}=s,c=s.packageManager??Le(),l=[],u=Array.isArray(i)?i:[i];c==="bun"&&(u=u.map(f=>f.replace("git+ssh://",""))),c==="pnpm"&&l.push("--config.engine-strict=false"),["yarn","npm"].includes(c)&&l.push("--ignore-engines");let h=u.length?c==="npm"?["install",...o,...l,...u]:["add",...o,...l,...u]:["install",...l];await vr(c,h,{cwd:n,task:a})}async function c2(r){let e=["-g"],t=[...r.flags??[],...e];await Cr({...r,flags:t,packageManager:r.packageManager??Le()})}function l2(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);(0,i2.execaSync)(o,[...a,r,...s],{cwd:i.cwd??(0,O0.cwd)(),stdio:"inherit",preferLocal:!0})}async function Mt(r,e,t){let{args:s,mergedOptions:i}=h2(e??[],t),n=i.manager??Le(),{command:o,args:a}=u2(n);await vr(o,[...a,r,...s],i)}async function Qo(r,e){let{script:t,mergedOptions:s}=_5(r,e),i=s.packageManager??Le();await vr(i,["run",t,...s.args??[]],{cwd:s.cwd??(0,O0.cwd)(),task:s.task})}function u2(r){let t={bun:"bunx",pnpm:"npx",yarn:"npx",npm:"npx"}[r];if(!t)throw new Error(`No executor found for the detected package manager: ${r}`);let[s,...i]=t.split(" ");return{command:s,args:i}}function k5(r,e){return Array.isArray(r)?{packages:r,mergedOptions:{...e,packages:r}}:{packages:r.packages??[],mergedOptions:r}}function h2(r,e){return Array.isArray(r)?{args:r,mergedOptions:e??{}}:{args:[],mergedOptions:r}}function _5(r,e){return typeof r=="string"?{script:r,mergedOptions:e??{}}:{script:r.script,mergedOptions:r}}function A5(r){let e=r.split(/\s+/),t=e[0],s=e.slice(1);if(t==="pf"){t=process.argv[1];let i=s.filter(o=>o.startsWith("-"));s=[...s.filter(o=>!o.startsWith("-")),...i]}return{executable:t,args:s}}async function F5(r,e){try{let{executable:t,args:s}=A5(r.command);return await vr(t,s,{cwd:e,shell:!1}),{command:r.command,success:!0}}catch(t){return{command:r.command,success:!1,error:t instanceof Error?t.message:String(t)}}}async function f2(r,e){let t=[];for(let s of r){let i=await F5(s,e);if(t.push(i),!i.success)break}return t}function d2(r){let e=[/^pf\s+new\s+/,/^pf\s+add\s+/,/^bun\s+pf\s+new\s+/,/^bun\s+pf\s+add\s+/];return r.filter(t=>e.some(s=>s.test(t.command)))}function p2(r){for(let e of r){let t=e.command.match(/(?:bun\s+)?pf\s+new\s+\S+\s+(\S+)/);if(t)return t[1]}return null}var qs=require("node:fs"),m2=require("node:path");var x2=r=>{let{eventsPath:e}=lt(r);if(!(0,qs.existsSync)(e))return[];let t=[];try{let s=(0,qs.readdirSync)(e).filter(i=>i.endsWith(".ts")&&!i.endsWith(".mock.json")&&i!=="index.ts");for(let i of s){let n=(0,m2.join)(e,i),o=(0,qs.readFileSync)(n,"utf-8"),a=T5(o,n);a&&t.push(a)}}catch(s){return console.warn("Warning: Could not scan contracts package:",s),[]}return t},T5=(r,e)=>{try{let t=r.match(/export const (\w+Contract) = createContract/);if(!t)return null;let s=t[1],i=r.match(/export type (\w+(?:Data|Event)) = z\.infer/);if(!i)return null;let n=i[1],o=r.match(/type:\s*['"]([^'"]+)['"]/);if(!o)return null;let a=o[1],c=P5(r);return{name:s,typeName:n,eventType:a,fields:c,filePath:e}}catch{return null}},P5=r=>{let e=[],t=r.match(/z\.object\({([^}]+)\}/);if(!t)return e;let s=t[1],i=/(\w+):\s*z\.(\w+)\([^)]*\)/g,n;for(;(n=i.exec(s))!==null;){let[,o,a]=n,l={string:"string",number:"number",boolean:"boolean",array:"array",object:"object",date:"date"}[a]||a;e.push(`${o}: ${l}`)}if(r.includes("z.array(")){let o=/(\w+):\s*z\.array\(/g;for(;(n=o.exec(r))!==null;){let a=n[1];e.some(c=>c.startsWith(a))||e.push(`${a}: array`)}}return e},g2=(r,e)=>{if(r.length===0)return`
146
146
  **Available Contracts:**
147
147
 
148
148
  No contracts are currently defined. Use Basic Mode with inline schemas for all events.
@@ -11,9 +11,9 @@ This package contains **shared event definitions** (contracts) for events that a
11
11
  ```
12
12
  src/
13
13
  β”œβ”€β”€ events/ # Event contracts and mock data
14
- β”‚ β”œβ”€β”€ order-created.ts # Contract definition
15
- β”‚ β”œβ”€β”€ order-created.mock.json # Mock data for testing
16
- β”‚ └── index.ts # Re-exports
14
+ β”‚ β”œβ”€β”€ orders-created.ts # Contract definition
15
+ β”‚ β”œβ”€β”€ orders-created.mock.json # Mock data for testing
16
+ β”‚ └── index.ts # Re-exports
17
17
  └── index.ts # Main exports
18
18
  ```
19
19
 
@@ -29,9 +29,9 @@ src/
29
29
 
30
30
  ```ts
31
31
  import { handleEvent } from '@crossdelta/cloudevents'
32
- import { OrderCreatedContract, type OrderCreatedData } from '{{scope}}/contracts'
32
+ import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
33
33
 
34
- export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData) => {
34
+ export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
35
35
  // data is fully typed from contract
36
36
  console.log(data.orderId, data.customerId)
37
37
  })
@@ -41,10 +41,10 @@ export default handleEvent(OrderCreatedContract, async (data: OrderCreatedData)
41
41
 
42
42
  ```ts
43
43
  import { publish } from '@crossdelta/cloudevents'
44
- import { OrderCreatedContract } from '{{scope}}/contracts'
44
+ import { OrdersCreatedContract } from '{{scope}}/contracts'
45
45
 
46
46
  // Type-safe publishing with contract
47
- await publish(OrderCreatedContract, {
47
+ await publish(OrdersCreatedContract, {
48
48
  orderId: 'order-123',
49
49
  customerId: 'cust-456',
50
50
  total: 99.99,
@@ -55,9 +55,9 @@ await publish(OrderCreatedContract, {
55
55
  ### In Use-Cases
56
56
 
57
57
  ```ts
58
- import type { OrderCreatedData } from '{{scope}}/contracts'
58
+ import type { OrdersCreatedData } from '{{scope}}/contracts'
59
59
 
60
- export const processOrder = async (data: OrderCreatedData) => {
60
+ export const processOrder = async (data: OrdersCreatedData) => {
61
61
  // Use typed event data
62
62
  }
63
63
  ```
@@ -68,25 +68,25 @@ Contracts are **auto-generated** when you create event handlers:
68
68
 
69
69
  ```bash
70
70
  # 1. Create service with event handler
71
- pf new hono-micro notifications --ai -d "Sends emails on order.created events"
71
+ pf new hono-micro notifications --ai -d "Sends emails on orders.created events"
72
72
 
73
73
  # 2. Generate contract (if missing) and mock data
74
74
  pf event:generate services/notifications
75
75
  ```
76
76
 
77
77
  This creates:
78
- - `packages/contracts/src/events/order-created.ts` (contract)
79
- - `packages/contracts/src/events/order-created.mock.json` (mock)
78
+ - `packages/contracts/src/events/orders-created.ts` (contract)
79
+ - `packages/contracts/src/events/orders-created.mock.json` (mock)
80
80
  - Updates `packages/contracts/src/index.ts` (exports)
81
81
 
82
82
  ### Manual Contract Creation
83
83
 
84
84
  ```ts
85
- // packages/contracts/src/events/order-created.ts
85
+ // packages/contracts/src/events/orders-created.ts
86
86
  import { createContract } from '@crossdelta/cloudevents'
87
87
  import { z } from 'zod'
88
88
 
89
- export const OrderCreatedSchema = z.object({
89
+ export const OrdersCreatedSchema = z.object({
90
90
  orderId: z.string(),
91
91
  customerId: z.string(),
92
92
  total: z.number(),
@@ -97,12 +97,12 @@ export const OrderCreatedSchema = z.object({
97
97
  })),
98
98
  })
99
99
 
100
- export const OrderCreatedContract = createContract({
101
- type: 'order.created',
102
- schema: OrderCreatedSchema,
100
+ export const OrdersCreatedContract = createContract({
101
+ type: 'orders.created',
102
+ schema: OrdersCreatedSchema,
103
103
  })
104
104
 
105
- export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
105
+ export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
106
106
  ```
107
107
 
108
108
  ## Testing with Mocks
@@ -112,10 +112,10 @@ export type OrderCreatedData = z.infer<typeof OrderCreatedContract.schema>
112
112
  pf event:list
113
113
 
114
114
  # Publish mock event
115
- pf event:publish order.created
115
+ pf event:publish orders.created
116
116
 
117
117
  # Publish with custom data
118
- pf event:publish order.created --data '{"orderId":"test-123"}'
118
+ pf event:publish orders.created --data '{"orderId":"test-123"}'
119
119
  ```
120
120
 
121
121
  ## Guidelines
@@ -128,7 +128,7 @@ pf event:publish order.created --data '{"orderId":"test-123"}'
128
128
  - ❌ Don't include event handlers in this package
129
129
 
130
130
  **Naming conventions:**
131
- - Contracts: `OrderCreatedContract`
132
- - Types: `OrderCreatedData`
133
- - Files: `order-created.ts`
134
- - Event types: `order.created` (dot notation)
131
+ - Contracts: `OrdersCreatedContract` (plural namespace)
132
+ - Types: `OrdersCreatedData`
133
+ - Files: `orders-created.ts`
134
+ - Event types: `orders.created` (plural namespace, dot notation)
@@ -9,7 +9,7 @@
9
9
  }
10
10
  },
11
11
  "dependencies": {
12
- "@crossdelta/cloudevents": "^0.4.2",
12
+ "@crossdelta/cloudevents": "^0.4.21",
13
13
  "zod": "^4.0.0"
14
14
  },
15
15
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "Your AI-powered platform engineer. Scaffold complete Turborepo workspaces, generate microservice boilerplate with natural language, and deploy to the cloud β€” all from one CLI",
5
5
  "keywords": [
6
6
  "cli",