@crossdelta/platform-sdk 0.21.27 → 0.21.28
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,26 @@ 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.28] - 2026-08-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **`pf dev` creates streams without the Pulumi peers installed.** `ensureDevStreams` imported
|
|
12
|
+
`collectStreamDefinitions` from the `@crossdelta/infrastructure` barrel, which requires
|
|
13
|
+
`@pulumi/pulumi`, `@pulumi/kubernetes` and `@pulumi/digitalocean`. A globally installed CLI
|
|
14
|
+
carries none of them, so stream creation failed with
|
|
15
|
+
`Cannot find package '@pulumi/kubernetes'` and fell back to "streams will be created when
|
|
16
|
+
services connect".
|
|
17
|
+
|
|
18
|
+
That fallback is what made the failure hard to place: `ensureStream` only adds missing subjects
|
|
19
|
+
and never rewrites wrong ones, so streams kept whatever shape they already had. The subject
|
|
20
|
+
correction shipped in 0.21.26 therefore never reached any machine whose streams predated it —
|
|
21
|
+
contract events were published to a subject no stream bound, and vanished without an error, a
|
|
22
|
+
redelivery or a dead letter. Now imports `@crossdelta/infrastructure/streams`, which pulls
|
|
23
|
+
nothing but `@crossdelta/cloudevents`.
|
|
24
|
+
|
|
25
|
+
Existing local streams still need to be recreated once, since nothing corrects a bound subject in
|
|
26
|
+
place.
|
|
27
|
+
|
|
8
28
|
## [0.21.26] - 2026-07-07
|
|
9
29
|
|
|
10
30
|
### 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
|
|
|
26
|
-
<img src="https://img.shields.io/badge/License-MIT-
|
|
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.
|
|
11
|
+
"@crossdelta/infrastructure": "^0.15.0",
|
|
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.28",
|
|
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.15.0",
|
|
148
148
|
"@nestjs/schematics": "^11.0.5",
|
|
149
149
|
"turbo": "^2.0.0"
|
|
150
150
|
},
|