@crossdelta/platform-sdk 0.21.27 → 0.21.29

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/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ All notable changes to `@crossdelta/platform-sdk` will be documented in this fil
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.21.29] - 2026-08-23
9
+
10
+ ### Changed
11
+ - **Workspace templates pin `@crossdelta/infrastructure@^0.15.2`.** That release makes the generated
12
+ Caddyfile declare port 80 explicitly; without it, a scaffolded workspace deploying behind a
13
+ PROXY-protocol load balancer answers every plain-HTTP request with `400 Bad Request`. Template
14
+ content only, no CLI behaviour change.
15
+
16
+ ## [0.21.28] - 2026-08-09
17
+
18
+ ### Fixed
19
+ - **`pf dev` creates streams without the Pulumi peers installed.** `ensureDevStreams` imported
20
+ `collectStreamDefinitions` from the `@crossdelta/infrastructure` barrel, which requires
21
+ `@pulumi/pulumi`, `@pulumi/kubernetes` and `@pulumi/digitalocean`. A globally installed CLI
22
+ carries none of them, so stream creation failed with
23
+ `Cannot find package '@pulumi/kubernetes'` and fell back to "streams will be created when
24
+ services connect".
25
+
26
+ That fallback is what made the failure hard to place: `ensureStream` only adds missing subjects
27
+ and never rewrites wrong ones, so streams kept whatever shape they already had. The subject
28
+ correction shipped in 0.21.26 therefore never reached any machine whose streams predated it —
29
+ contract events were published to a subject no stream bound, and vanished without an error, a
30
+ redelivery or a dead letter. Now imports `@crossdelta/infrastructure/streams`, which pulls
31
+ nothing but `@crossdelta/cloudevents`.
32
+
33
+ Existing local streams still need to be recreated once, since nothing corrects a bound subject in
34
+ place.
35
+
8
36
  ## [0.21.26] - 2026-07-07
9
37
 
10
38
  ### Fixed
package/README.md CHANGED
@@ -23,7 +23,7 @@
23
23
   
24
24
  <img src="https://img.shields.io/badge/Bun-000000?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
25
25
  &nbsp;
26
- <img src="https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge" alt="MIT License" />
26
+ <img src="https://img.shields.io/badge/License-MIT-be1b4b?style=for-the-badge" alt="MIT License" />
27
27
  </p>
28
28
 
29
29
  <p align="center">
package/bin/cli.mjs CHANGED
@@ -520,7 +520,7 @@ export default handleEvent(${n}, async (data: ${o}) => {
520
520
  // TODO: Inject and call service
521
521
  })
522
522
  `},td=(e,t,r)=>!t.hasEvents||t.events.length===0?[]:he(r,"stub").delegateFromHandler?t.events.map(o=>{let s=Be(o),i=he(r,s);return{kind:"file:write",path:Vr(e,i.filePath),content:Qm(o,t.workspaceScope)}}):[],rd=(e,t,r)=>!t.hasEvents||t.events.length===0?[]:t.events.map(n=>{let{kebab:o}=Jr(n),s=zr(r,o);return{kind:"file:write",path:Vr(e,s.filePath),content:ed(n,t.workspaceScope,r)}}),nd=e=>e==="hono"?"hono":e==="nest"?"nestjs":"hono",Gs=(e,t,r,n)=>{let o=nd(n.framework),s=Ym(e,t),i=td(e,r,o),a=rd(e,r,o),c=Zm(r.serviceName,r.port,n),l=Xm(r.envVarName,r.port);return[...s,...i,...a,c,l]};import{existsSync as od,readdirSync as sd,readFileSync as id}from"fs";import{join as Bs}from"path";var Us=4001,ad=e=>{let t=e.match(/ports\(\)\.(?:http|https|grpc|primary)\((\d+)\)/);if(t)return Number.parseInt(t[1],10);let r=e.match(/containerPort:\s*(\d+)/);return r?Number.parseInt(r[1],10):null},zs=e=>{try{let t=Bs(e,"infra","services");return od(t)?sd(t).filter(n=>n.endsWith(".ts")).reduce((n,o)=>{let s=id(Bs(t,o),"utf-8"),i=ad(s);return i!==null&&n.add(i),n},new Set):new Set}catch(t){return console.warn("Failed to scan ports:",t),new Set}},Kr=(e,t)=>e.has(t)?Kr(e,t+1):t;var ld=e=>{if(!e||e.trim().length===0)throw new Error("Service name cannot be empty");if(!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(e))throw new Error(`Service name '${e}' must be kebab-case (lowercase, alphanumeric, hyphens only)`)},pd=(e,t,r)=>{if(t){if(t.startsWith("/"))return t;if(t.startsWith("."))return It(r,t);if(t.includes("/"))return It(r,t)}let n=E(r);return It(r,n.services,e)},md=e=>[...new Set(e.map(t=>cd(t).streamName))],dd=(e,t,r,n)=>{let s=x(t).packageName.split("/")[0],i=e.events??[],a=i.length>0||e.hasEvents||!1,c=i.length>0?md(i):e.streams??[];return{serviceName:e.name,packageName:`${s}/${e.name}`,servicePath:r.replace(`${t}/`,""),port:n,envVarName:B(e.name),hasEvents:a,streams:c,events:i,workspaceScope:s,envVars:e.envVars}},gd=(e,t)=>e.files.filter(r=>!r.skip?.(t)).map(r=>({path:r.path,content:r.content(t)})),ud=(e,t,r)=>[...e.map(o=>({type:"file",path:It(r,o.path),description:`Service file: ${o.path}`})),{type:"config",path:`infra/services/${t}.ts`,description:"Infrastructure configuration"},{type:"env",path:".env.local",description:"Environment variables"}],fd=e=>[{command:`cd ${e} && bun dev`,description:"Start this service"},{command:"pf dev",description:"Start all services"}],Vs=e=>{ld(e.name);let t=e.workspaceRoot??g(),r=pd(e.name,e.path,t),n=e.port??Kr(zs(t),Us),o=dd(e,t,r,n),s=Ws(e.type),i=gd(s,o),a=Gs(r,i,o,s),c=ud(i,e.name,r),l=fd(r);return Hr("service.generate",`Generated ${e.name} service (port ${n})`,{artifacts:c,changes:a,next:l,data:{serviceName:e.name,servicePath:r,port:n,vars:o}})},Js=e=>{let t=Vs(e);if(!t.ok||!t.data)throw new Error(t.summary);return{serviceName:t.data.serviceName,servicePath:t.data.servicePath,port:t.data.port,effects:t.changes,vars:t.data.vars}};f();f();f();import A from"chalk";import{execa as yd}from"execa";import{createJiti as vd}from"jiti";var qr=async e=>{let{stdout:t}=await yd("sh",["-c",e]);return t.trim()},Cd="pf-local-nats",kd="2.10-alpine",xd=4222,wd=8222,Pd=e=>({clientPort:e.clientPort??xd,monitoringPort:e.monitoringPort??wd,containerName:e.containerName??Cd,version:e.version??kd}),Sd=e=>["run","--rm","--detach",`--name=${e.containerName}`,`-p=${e.clientPort}:4222`,`-p=${e.monitoringPort}:8222`,`nats:${e.version}`,"--jetstream","--store_dir=/data","--http_port=8222"],bd=e=>({containerName:e.containerName,clientUrl:`nats://localhost:${e.clientPort}`,monitoringUrl:`http://localhost:${e.monitoringPort}`}),Ed=async()=>{try{return await qr("docker info"),!0}catch{return!1}},Td=async e=>{try{return(await qr(`docker ps -q -f name=${e}`)).length>0}catch{return!1}};var $d=async e=>{let t=Sd(e);try{return await qr(`docker ${t.join(" ")}`),{success:!0,value:bd(e)}}catch{return{success:!1}}},Ad=e=>{console.log(A.green("\u2713 NATS started")),console.log(A.dim(` Client: ${e.clientUrl}`)),console.log(A.dim(` Monitoring: ${e.monitoringUrl}`)),console.log(A.dim(" Streams: ephemeral, auto-created by services")),console.log("")},Ks=async(e={})=>{let t=Pd(e);if(!await Ed())return console.log(A.yellow("\u26A0\uFE0F Docker not available - skipping NATS (services may fail)")),!1;if(await Td(t.containerName))return console.log(A.dim("\u2713 NATS already running")),!0;console.log(A.cyan("\u{1F680} Starting development infrastructure...")),console.log(A.dim(" NATS with JetStream (ephemeral)"));let r=await $d(t);return r.success?(Ad(r.value),!0):(console.error(A.red("\u2717 Failed to start NATS")),!1)},Id=e=>e.map(({stream:t,subjects:r})=>({stream:t,subjects:r,config:{storage:"memory",retention:"limits",maxAge:36e5}})),Rd=e=>{if(!(e instanceof Error))return!0;let t=e.message.toLowerCase();return!t.includes("insufficient storage")&&!t.includes("connection refused")},Nd=e=>{if(!(e instanceof Error))return"Unknown error";let t=e.message.toLowerCase();return t.includes("insufficient storage")?["NATS has insufficient storage to create streams."," Your disk is likely full. Free space and restart:",""," docker system prune -af"," pf dev"].join(`
523
- `):t.includes("connection refused")?"Cannot connect to NATS \u2014 is the container running?":e.message},Yr=async e=>{try{let{ensureJetStreams:t}=await import("@crossdelta/cloudevents"),{indexPath:r}=x(e),o=await vd(e,{moduleCache:!1}).import(r,{default:!0}),{collectStreamDefinitions:s}=await import("@crossdelta/infrastructure"),i=s(o);if(i.length===0){console.log(A.dim(" No streams found in contracts"));return}let a=Id(i);await t({streams:a});let c=i.map(l=>l.stream);console.log(A.dim(` Created ${i.length} ephemeral stream(s): ${c.join(", ")}`))}catch(t){if(!Rd(t))throw console.error(A.red(`
523
+ `):t.includes("connection refused")?"Cannot connect to NATS \u2014 is the container running?":e.message},Yr=async e=>{try{let{ensureJetStreams:t}=await import("@crossdelta/cloudevents"),{indexPath:r}=x(e),o=await vd(e,{moduleCache:!1}).import(r,{default:!0}),{collectStreamDefinitions:s}=await import("@crossdelta/infrastructure/streams"),i=s(o);if(i.length===0){console.log(A.dim(" No streams found in contracts"));return}let a=Id(i);await t({streams:a});let c=i.map(l=>l.stream);console.log(A.dim(` Created ${i.length} ephemeral stream(s): ${c.join(", ")}`))}catch(t){if(!Rd(t))throw console.error(A.red(`
524
524
  \u2717 ${Nd(t)}
525
525
  `)),t;console.log(A.yellow(` \u26A0\uFE0F Stream creation failed: ${t instanceof Error?t.message:"Unknown error"}`)),console.log(A.dim(" Streams will be created when services connect"))}};var qs=`// env: string literals \u2192 written to .env.local by generate-env (use for values meaningful locally)
526
526
  // containerEnv: \u2192 only applied in the K8s container, never in .env.local
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@crossdelta/cloudevents": "^0.8.4",
11
- "@crossdelta/infrastructure": "^0.13.0",
11
+ "@crossdelta/infrastructure": "^0.15.2",
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.8.4",
23
- "@crossdelta/infrastructure": "^0.13.0",
23
+ "@crossdelta/infrastructure": "^0.15.2",
24
24
  "{{scope}}/types": "workspace:*",
25
25
  "zod": "^4.0.0"
26
26
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossdelta/platform-sdk",
3
- "version": "0.21.27",
3
+ "version": "0.21.29",
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.13.0",
147
+ "@crossdelta/infrastructure": "^0.15.2",
148
148
  "@nestjs/schematics": "^11.0.5",
149
149
  "turbo": "^2.0.0"
150
150
  },