palbase 0.1.0 → 0.2.0

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.
Files changed (45) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +3 -3
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/{QXUMLO7O.js → 5W6XJM46.js} +2 -2
  8. package/dist/chunks/{JAWWIVZH.js → 6OPBXB7Z.js} +2 -2
  9. package/dist/chunks/{3OGEBGYZ.js → 7LOMYBLN.js} +2 -2
  10. package/dist/chunks/AN5LCF5K.js +55 -0
  11. package/dist/chunks/ECUYQ73P.js +2 -0
  12. package/dist/chunks/{DAOLIUNZ.js → HDX6YUAB.js} +1 -1
  13. package/dist/chunks/HVC2C35F.js +2 -0
  14. package/dist/chunks/{LYEFPLJZ.js → I3JNX6MZ.js} +1 -1
  15. package/dist/chunks/JEJ5CFTK.js +2 -0
  16. package/dist/chunks/{E5Q4VTW4.js → K3C2IEDS.js} +2 -2
  17. package/dist/chunks/{6FWCNTAM.js → KRBVYYME.js} +1 -1
  18. package/dist/chunks/{3VASGTIN.js → NIJHTHNF.js} +2 -2
  19. package/dist/chunks/{45J7YXRB.js → TL4H6V3P.js} +1 -1
  20. package/dist/chunks/{UAY56IQR.js → UTRQWCMD.js} +2 -2
  21. package/dist/chunks/ZFPDYRRY.js +2 -0
  22. package/dist/cli.js +72 -72
  23. package/dist/refuse/ai.js +1 -1
  24. package/dist/refuse/backend.js +1 -1
  25. package/dist/refuse/server.js +1 -1
  26. package/dist/server/index.js +2 -2
  27. package/dist/server/testing.js +2 -2
  28. package/dist/web/index.js +2 -2
  29. package/dist/web/palbe.js +2 -2
  30. package/dist/web/paywall.js +2 -0
  31. package/dist/web/vite.js +2 -2
  32. package/package.json +6 -2
  33. package/types/internal/src/modules/events.d.ts +2 -2
  34. package/types/internal/src/modules/purchases.d.ts +1213 -44
  35. package/types/web/src/index.d.ts +2 -0
  36. package/types/web/src/paywall/icons.d.ts +5 -0
  37. package/types/web/src/paywall/index.d.ts +5 -0
  38. package/types/web/src/paywall/placement.d.ts +85 -0
  39. package/types/web/src/paywall/prices.d.ts +40 -0
  40. package/types/web/src/paywall/render.d.ts +54 -0
  41. package/types/web/src/paywall/resolve.d.ts +149 -0
  42. package/types/web/src/purchases.d.ts +13 -0
  43. package/dist/chunks/3XT4L2XV.js +0 -2
  44. package/dist/chunks/HKJU2FKE.js +0 -2
  45. package/dist/chunks/IKVOXHEE.js +0 -2
package/dist/cli.js CHANGED
@@ -1,37 +1,37 @@
1
1
  #!/usr/bin/env node
2
- /*! palbase 0.1.0 — Palbase SDK License (see LICENSE) */
3
- import{parseArgs as du}from"node:util";var Fp=Symbol.for("palbase.data"),Vp=globalThis,Fr=Vp[Fp]??=new WeakSet;function Hp(e){return e instanceof Date||ArrayBuffer.isView(e)||e instanceof ArrayBuffer}function Rn(e){let t=[e];for(;t.length>0;){let n=t.pop();if(!(typeof n!="object"||n===null||Fr.has(n)||Hp(n))){Fr.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var Ew=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));var Y=class extends Error{status;code;requestId;retryAfter;body;op;constructor(t){super(t.message,t.cause===void 0?void 0:{cause:t.cause}),this.name="PalbaseError",this.op=t.op,this.status=t.status,this.code=t.code,this.requestId=t.requestId,this.retryAfter=t.retryAfter,this.body=t.body}};var Lw=64*1024;function Es(e){let t;try{t=new URL(e)}catch{throw new TypeError(`baseUrl ${JSON.stringify(e)} is not an absolute URL`)}let n=t.hostname==="localhost"||t.hostname.endsWith(".localhost")||t.hostname==="127.0.0.1"||t.hostname==="[::1]";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw new TypeError(`baseUrl must be https (http only for a loopback address): ${t.origin}`);if(t.username||t.password||t.search||t.hash)throw new TypeError("baseUrl carries no user, query or fragment");if(t.pathname!=="/"&&t.pathname!=="")throw new TypeError("baseUrl is the project origin alone, with no path");return t}var Tn={project:{type:"string",short:"P",value:"project",description:"the project (name or id; org/project when the name is in two organizations)"},env:{type:"string",short:"E",value:"env",description:"the project's environment (slug or ref)"},json:{type:"boolean",description:"print the answer as JSON"},"non-interactive":{type:"boolean",description:"never ask: refuse what would need a question"},yes:{type:"boolean",short:"y",description:"confirm a change to a production environment without asking"},debug:{type:"boolean",description:"print each request the command sends"},help:{type:"boolean",short:"h",description:"show help"}};var m=class extends Error{exitCode;hint;code;constructor(t,n,s={}){super(n),this.name="CliError",this.code=t,this.hint=s.hint,this.exitCode=s.exitCode??1}};function g(e,t){return new m("usage",e,{hint:t,exitCode:2})}var En=class{io;json;constructor(t,n){this.io=t,this.json=n}result(t,n){this.json?this.io.stdout(`${JSON.stringify(t,null,2)}
2
+ /*! palbase 0.2.0 — Palbase SDK License (see LICENSE) */
3
+ import{parseArgs as pu}from"node:util";var Fd=Symbol.for("palbase.data"),Vd=globalThis,Fr=Vd[Fd]??=new WeakSet;function Hd(e){return e instanceof Date||ArrayBuffer.isView(e)||e instanceof ArrayBuffer}function Rn(e){let t=[e];for(;t.length>0;){let n=t.pop();if(!(typeof n!="object"||n===null||Fr.has(n)||Hd(n))){Fr.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var Ew=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));var Y=class extends Error{status;code;requestId;retryAfter;body;op;constructor(t){super(t.message,t.cause===void 0?void 0:{cause:t.cause}),this.name="PalbaseError",this.op=t.op,this.status=t.status,this.code=t.code,this.requestId=t.requestId,this.retryAfter=t.retryAfter,this.body=t.body}};var Lw=64*1024;function Es(e){let t;try{t=new URL(e)}catch{throw new TypeError(`baseUrl ${JSON.stringify(e)} is not an absolute URL`)}let n=t.hostname==="localhost"||t.hostname.endsWith(".localhost")||t.hostname==="127.0.0.1"||t.hostname==="[::1]";if(t.protocol!=="https:"&&!(t.protocol==="http:"&&n))throw new TypeError(`baseUrl must be https (http only for a loopback address): ${t.origin}`);if(t.username||t.password||t.search||t.hash)throw new TypeError("baseUrl carries no user, query or fragment");if(t.pathname!=="/"&&t.pathname!=="")throw new TypeError("baseUrl is the project origin alone, with no path");return t}var Tn={project:{type:"string",short:"P",value:"project",description:"the project (name or id; org/project when the name is in two organizations)"},env:{type:"string",short:"E",value:"env",description:"the project's environment (slug or ref)"},json:{type:"boolean",description:"print the answer as JSON"},"non-interactive":{type:"boolean",description:"never ask: refuse what would need a question"},yes:{type:"boolean",short:"y",description:"confirm a change to a production environment without asking"},debug:{type:"boolean",description:"print each request the command sends"},help:{type:"boolean",short:"h",description:"show help"}};var m=class extends Error{exitCode;hint;code;constructor(t,n,s={}){super(n),this.name="CliError",this.code=t,this.hint=s.hint,this.exitCode=s.exitCode??1}};function g(e,t){return new m("usage",e,{hint:t,exitCode:2})}var En=class{io;json;constructor(t,n){this.io=t,this.json=n}result(t,n){this.json?this.io.stdout(`${JSON.stringify(t,null,2)}
4
4
  `):n()}line(t=""){this.io.stdout(`${G(t)}
5
5
  `)}note(t){this.io.stderr(`${G(t)}
6
6
  `)}warn(t){this.io.stderr(`warning: ${G(t)}
7
- `)}table(t,n){if(t.length===0)return;let s=t.map(i=>n.map(o=>j(o.value(i)))),r=n.map((i,o)=>Math.max(i.header.length,...s.map(l=>l[o].length))),a=i=>i.map((o,l)=>l===i.length-1?o:o.padEnd(r[l])).join(" ").trimEnd();this.line(a(n.map(i=>i.header.toUpperCase())));for(let i of s)this.line(a(i))}};function G(e){return e.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}function j(e){return G(e).replace(/[\n\t\u2028\u2029]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}import{chmod as Xp,mkdir as Vr,readFile as Yp,rename as Zp,rm as tn,stat as Qp,writeFile as Hr}from"node:fs/promises";import{dirname as eu,join as Qe,parse as tu,resolve as Cs}from"node:path";import{randomBytes as nu}from"node:crypto";var Cn=class{dir;constructor(t){this.dir=t}async readSession(){return this.read("session.json")}async writeSession(t){await this.write("session.json",t)}async clearSession(){await tn(Qe(this.dir,"session.json"),{force:!0})}async targetFor(t){let n=await this.read("targets.json")??{},s=Cs(t),r=tu(s).root;for(;;){let a=n[s];if(a)return{dir:s,target:a};if(s===r)return null;s=eu(s)}}async setTarget(t,n){await this.update("targets.json",s=>(s[Cs(t)]=n,!0))}async clearTarget(t){let n=!1;return await this.update("targets.json",s=>{let r=Cs(t);return n=r in s,n&&delete s[r],n}),n}async databasePasswords(t,n){return(await this.read("databases.json")??{})[t]?.[n]??{}}async keepDatabasePasswords(t,n,s){await this.update("databases.json",r=>(r[t]={...r[t]??{},[n]:{...r[t]?.[n]??{},...s}},!0))}async forgetDatabase(t,n){await this.update("databases.json",s=>s[t]?.[n]?(delete s[t][n],!0):!1)}async personalKey(t){return(await this.read("keys.json"))?.[t]??null}async keepPersonalKey(t,n){await this.update("keys.json",s=>(s[t]=n,!0))}async personalKeys(){return await this.read("keys.json")??{}}async forgetPersonalKeys(){await tn(Qe(this.dir,"keys.json"),{force:!0})}async forget(t){await tn(Qe(this.dir,t),{force:!0})}async read(t){let n;try{n=await Yp(Qe(this.dir,t),"utf8")}catch(s){if(s.code==="ENOENT")return null;throw s}return JSON.parse(n)}async update(t,n){await Vr(this.dir,{recursive:!0,mode:448});let s=Qe(this.dir,`${t}.lock`);for(let r=0;;r+=Xr)try{await Hr(s,String(process.pid),{flag:"wx",mode:384});break}catch(a){if(a.code!=="EEXIST")throw a;if(await Qp(s).then(o=>Date.now()-o.mtimeMs,()=>0)>ru)await tn(s,{force:!0});else{if(r>su)throw new Error(`${s} is held by another palbase command: try again, or remove it if none runs`);await new Promise(o=>setTimeout(o,Xr))}}try{let r=await this.read(t)??{};n(r)&&await this.write(t,r)}finally{await tn(s,{force:!0})}}async write(t,n){await Vr(this.dir,{recursive:!0,mode:448}),await Xp(this.dir,448);let s=Qe(this.dir,t),r=`${s}.${nu(6).toString("hex")}.tmp`;await Hr(r,`${JSON.stringify(n,null,2)}
8
- `,{mode:384}),await Zp(r,s)}},Xr=25,su=1e4,ru=3e4;function Yr(e,t){if(e.PALBASE_CONFIG_DIR)return e.PALBASE_CONFIG_DIR;let n=e.XDG_CONFIG_HOME&&e.XDG_CONFIG_HOME!==""?e.XDG_CONFIG_HOME:Qe(t,".config");return Qe(n,"palbase")}var Zr="v3.palbase.studio",nn="palbase-cli";function Qr(e){let t=e.PALBASE_DOMAIN&&e.PALBASE_DOMAIN!==""?e.PALBASE_DOMAIN:Zr;if(!/^[a-z0-9.-]+$/.test(t))throw new TypeError(`PALBASE_DOMAIN ${JSON.stringify(t)} is not a domain`);return{domain:t,platformUrl:`https://palbase.${t}`,studioApi:`https://palbase.app.${t}`,studioWeb:`https://studio-palbase.app.${t}`}}function ct(e,t){if(!/^[a-z0-9]{1,63}$/.test(t))throw new TypeError(`${JSON.stringify(t)} is not an environment's ref`);return`https://${t}.${e.domain}`}function ea(e,t){return t.domain===Zr?e:`${e}/domains/${t.domain}`}var au="openid profile email offline_access";async function ta(e,t,n){let s=await jn(e,t,"/oauth/device",{client_id:nn,scope:au,...n!==void 0?{max_age:String(n)}:{}});if(s.status!==200)throw await js("the sign-in could not be started",s);let r=await s.json();if(typeof r.device_code!="string"||typeof r.user_code!="string")throw new m("auth_unavailable","the sign-in could not be started: auth's answer has no code");return{device_code:r.device_code,user_code:r.user_code,expires_in:r.expires_in??900,interval:r.interval??5}}async function na(e,t,n){let s=Math.max(1,n.interval),r=e.now()+n.expires_in*1e3;for(;e.now()<r;){await e.sleep(s*1e3);let a;try{a=await jn(e,t,"/oauth/token",{grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:n.device_code,client_id:nn})}catch{continue}if(a.status>=500||a.status===429)continue;if(a.status===200)return aa(e,await a.json());let i=await a.json().catch(()=>({}));switch(i.error){case"authorization_pending":continue;case"slow_down":s+=5;continue;case"access_denied":throw new m("access_denied","the sign-in was denied in Studio");case"expired_token":throw new m("expired_code","the code expired before it was approved",{hint:"run palbase login again"});default:throw new m(i.error??`http_${a.status}`,`the sign-in failed: ${i.error_description??i.error??a.status}`)}}throw new m("expired_code","the code expired before it was approved",{hint:"run palbase login again"})}async function sa(e,t,n){if(!n.refresh_token)throw sn();let s=await jn(e,t,"/oauth/token",{grant_type:"refresh_token",refresh_token:n.refresh_token,client_id:nn});if(s.status===400||s.status===401)throw sn();if(s.status!==200)throw await js("the session could not be renewed",s);let r=aa(e,await s.json());return{...r,refresh_token:r.refresh_token??n.refresh_token}}async function ra(e,t,n){if(!n.refresh_token)return;let s=await jn(e,t,"/oauth/revoke",{token:n.refresh_token,token_type_hint:"refresh_token",client_id:nn});if(s.status!==200)throw await js("the session could not be ended at auth",s)}function sn(){return new m("signed_out","you are not signed in",{hint:"run palbase login"})}function aa(e,t){if(typeof t.access_token!="string")throw new m("auth_unavailable","auth's answer has no access token");let n=rn(t.access_token),s=typeof n.sub=="string"?n.sub:"";if(s==="")throw new m("auth_unavailable","auth's token names no user");return{access_token:t.access_token,refresh_token:t.refresh_token,expires_at:e.now()+(t.expires_in??0)*1e3,user:{id:s,...typeof n.email=="string"?{email:n.email}:{}}}}function rn(e){let t=e.split(".")[1];if(!t)return{};try{return JSON.parse(Buffer.from(t,"base64url").toString("utf8"))}catch{return{}}}async function jn(e,t,n,s){try{return await e.fetch(`${t.platformUrl}${n}`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded",accept:"application/json"},body:new URLSearchParams(s),redirect:"manual"})}catch(r){throw new m("network_error",`Palbase (${t.platformUrl}) could not be reached: ${r instanceof Error?r.message:String(r)}`)}}async function js(e,t){let n=await t.json().catch(()=>({}));return new m(n.error??`http_${t.status}`,`${e}: ${n.error_description??n.error??`auth answered ${t.status}`}`)}var iu="/v1",ou="x-studio-module-answer",ia=3e4,On=class{cloud;io;state;debug;session=null;constructor(t,n,s,r=!1){this.io=t,this.cloud=n,this.state=s,this.debug=r}async currentSession(){if(this.session&&this.io.now()<this.session.expires_at-ia)return this.session;let t=this.session??await this.state.readSession();if(!t)throw sn();return this.io.now()<t.expires_at-ia?(this.session=t,t):this.renew(t)}async get(t){return this.request("GET",t)}async post(t,n){return this.request("POST",t,n)}async request(t,n,s){let{res:r,renewable:a}=await this.exchange(t,n,s);return this.answered(t,n,r,a)}async call(t,n){let{res:s,renewable:r}=await this.exchange("POST",t,n);if(s.headers.get(ou)!=="1")throw await this.answered("POST",t,s,r),new m("studio_answer",`Studio answered ${t} ${s.status} as no module's answer`);let a=s.status===204||s.status===304?"":await s.text(),i=Number(s.headers.get("retry-after")??"");return{status:s.status,body:a===""?void 0:oa(a),etag:s.headers.get("etag")??void 0,retryAfter:Number.isFinite(i)&&i>0?i:void 0}}async exchange(t,n,s){let r=await this.token(),a=await this.send(t,n,s,r.value);if(a.status===401&&r.renewable){let i=await this.renew(await this.currentSession());a=await this.send(t,n,s,i.access_token)}return{res:a,renewable:r.renewable}}async answered(t,n,s,r){if(s.status===204)return;let a=await s.text(),i=a===""?void 0:oa(a);if(s.ok)return i;let o=i??{};throw s.status===401?r?sn():new m("token_refused","Studio refused PALBASE_TOKEN: it was revoked, it ended, or the one who made it no longer holds its role",{hint:"an admin makes a new one: palbase tokens:create <name>"}):new Y({op:`${t} ${n}`,status:s.status,code:o.error??`http_${s.status}`,message:o.error_description??`Studio answered ${s.status}`,requestId:o.request_id??s.headers.get("x-request-id")??void 0,body:i})}async token(){let t=this.io.env.PALBASE_TOKEN;return t&&t!==""?{value:t,renewable:!1}:{value:(await this.currentSession()).access_token,renewable:!0}}async renew(t){let n=await sa(this.io,this.cloud,t);return await this.state.writeSession(n),this.session=n,n}async send(t,n,s,r){let a=`${this.cloud.studioApi}${iu}${n}`;this.debug&&this.io.stderr(`> ${t} ${a}
7
+ `)}table(t,n){if(t.length===0)return;let s=t.map(i=>n.map(o=>j(o.value(i)))),r=n.map((i,o)=>Math.max(i.header.length,...s.map(l=>l[o].length))),a=i=>i.map((o,l)=>l===i.length-1?o:o.padEnd(r[l])).join(" ").trimEnd();this.line(a(n.map(i=>i.header.toUpperCase())));for(let i of s)this.line(a(i))}};function G(e){return e.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}function j(e){return G(e).replace(/[\n\t\u2028\u2029]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}import{chmod as Xd,mkdir as Vr,readFile as Yd,rename as Zd,rm as tn,stat as Qd,writeFile as Hr}from"node:fs/promises";import{dirname as eu,join as Qe,parse as tu,resolve as Cs}from"node:path";import{randomBytes as nu}from"node:crypto";var Cn=class{dir;constructor(t){this.dir=t}async readSession(){return this.read("session.json")}async writeSession(t){await this.write("session.json",t)}async clearSession(){await tn(Qe(this.dir,"session.json"),{force:!0})}async targetFor(t){let n=await this.read("targets.json")??{},s=Cs(t),r=tu(s).root;for(;;){let a=n[s];if(a)return{dir:s,target:a};if(s===r)return null;s=eu(s)}}async setTarget(t,n){await this.update("targets.json",s=>(s[Cs(t)]=n,!0))}async clearTarget(t){let n=!1;return await this.update("targets.json",s=>{let r=Cs(t);return n=r in s,n&&delete s[r],n}),n}async databasePasswords(t,n){return(await this.read("databases.json")??{})[t]?.[n]??{}}async keepDatabasePasswords(t,n,s){await this.update("databases.json",r=>(r[t]={...r[t]??{},[n]:{...r[t]?.[n]??{},...s}},!0))}async forgetDatabase(t,n){await this.update("databases.json",s=>s[t]?.[n]?(delete s[t][n],!0):!1)}async personalKey(t){return(await this.read("keys.json"))?.[t]??null}async keepPersonalKey(t,n){await this.update("keys.json",s=>(s[t]=n,!0))}async personalKeys(){return await this.read("keys.json")??{}}async forgetPersonalKeys(){await tn(Qe(this.dir,"keys.json"),{force:!0})}async forget(t){await tn(Qe(this.dir,t),{force:!0})}async read(t){let n;try{n=await Yd(Qe(this.dir,t),"utf8")}catch(s){if(s.code==="ENOENT")return null;throw s}return JSON.parse(n)}async update(t,n){await Vr(this.dir,{recursive:!0,mode:448});let s=Qe(this.dir,`${t}.lock`);for(let r=0;;r+=Xr)try{await Hr(s,String(process.pid),{flag:"wx",mode:384});break}catch(a){if(a.code!=="EEXIST")throw a;if(await Qd(s).then(o=>Date.now()-o.mtimeMs,()=>0)>ru)await tn(s,{force:!0});else{if(r>su)throw new Error(`${s} is held by another palbase command: try again, or remove it if none runs`);await new Promise(o=>setTimeout(o,Xr))}}try{let r=await this.read(t)??{};n(r)&&await this.write(t,r)}finally{await tn(s,{force:!0})}}async write(t,n){await Vr(this.dir,{recursive:!0,mode:448}),await Xd(this.dir,448);let s=Qe(this.dir,t),r=`${s}.${nu(6).toString("hex")}.tmp`;await Hr(r,`${JSON.stringify(n,null,2)}
8
+ `,{mode:384}),await Zd(r,s)}},Xr=25,su=1e4,ru=3e4;function Yr(e,t){if(e.PALBASE_CONFIG_DIR)return e.PALBASE_CONFIG_DIR;let n=e.XDG_CONFIG_HOME&&e.XDG_CONFIG_HOME!==""?e.XDG_CONFIG_HOME:Qe(t,".config");return Qe(n,"palbase")}var Zr="v3.palbase.studio",nn="palbase-cli";function Qr(e){let t=e.PALBASE_DOMAIN&&e.PALBASE_DOMAIN!==""?e.PALBASE_DOMAIN:Zr;if(!/^[a-z0-9.-]+$/.test(t))throw new TypeError(`PALBASE_DOMAIN ${JSON.stringify(t)} is not a domain`);return{domain:t,platformUrl:`https://palbase.${t}`,studioApi:`https://palbase.app.${t}`,studioWeb:`https://studio-palbase.app.${t}`}}function ct(e,t){if(!/^[a-z0-9]{1,63}$/.test(t))throw new TypeError(`${JSON.stringify(t)} is not an environment's ref`);return`https://${t}.${e.domain}`}function ea(e,t){return t.domain===Zr?e:`${e}/domains/${t.domain}`}var au="openid profile email offline_access";async function ta(e,t,n){let s=await jn(e,t,"/oauth/device",{client_id:nn,scope:au,...n!==void 0?{max_age:String(n)}:{}});if(s.status!==200)throw await js("the sign-in could not be started",s);let r=await s.json();if(typeof r.device_code!="string"||typeof r.user_code!="string")throw new m("auth_unavailable","the sign-in could not be started: auth's answer has no code");return{device_code:r.device_code,user_code:r.user_code,expires_in:r.expires_in??900,interval:r.interval??5}}async function na(e,t,n){let s=Math.max(1,n.interval),r=e.now()+n.expires_in*1e3;for(;e.now()<r;){await e.sleep(s*1e3);let a;try{a=await jn(e,t,"/oauth/token",{grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:n.device_code,client_id:nn})}catch{continue}if(a.status>=500||a.status===429)continue;if(a.status===200)return aa(e,await a.json());let i=await a.json().catch(()=>({}));switch(i.error){case"authorization_pending":continue;case"slow_down":s+=5;continue;case"access_denied":throw new m("access_denied","the sign-in was denied in Studio");case"expired_token":throw new m("expired_code","the code expired before it was approved",{hint:"run palbase login again"});default:throw new m(i.error??`http_${a.status}`,`the sign-in failed: ${i.error_description??i.error??a.status}`)}}throw new m("expired_code","the code expired before it was approved",{hint:"run palbase login again"})}async function sa(e,t,n){if(!n.refresh_token)throw sn();let s=await jn(e,t,"/oauth/token",{grant_type:"refresh_token",refresh_token:n.refresh_token,client_id:nn});if(s.status===400||s.status===401)throw sn();if(s.status!==200)throw await js("the session could not be renewed",s);let r=aa(e,await s.json());return{...r,refresh_token:r.refresh_token??n.refresh_token}}async function ra(e,t,n){if(!n.refresh_token)return;let s=await jn(e,t,"/oauth/revoke",{token:n.refresh_token,token_type_hint:"refresh_token",client_id:nn});if(s.status!==200)throw await js("the session could not be ended at auth",s)}function sn(){return new m("signed_out","you are not signed in",{hint:"run palbase login"})}function aa(e,t){if(typeof t.access_token!="string")throw new m("auth_unavailable","auth's answer has no access token");let n=rn(t.access_token),s=typeof n.sub=="string"?n.sub:"";if(s==="")throw new m("auth_unavailable","auth's token names no user");return{access_token:t.access_token,refresh_token:t.refresh_token,expires_at:e.now()+(t.expires_in??0)*1e3,user:{id:s,...typeof n.email=="string"?{email:n.email}:{}}}}function rn(e){let t=e.split(".")[1];if(!t)return{};try{return JSON.parse(Buffer.from(t,"base64url").toString("utf8"))}catch{return{}}}async function jn(e,t,n,s){try{return await e.fetch(`${t.platformUrl}${n}`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded",accept:"application/json"},body:new URLSearchParams(s),redirect:"manual"})}catch(r){throw new m("network_error",`Palbase (${t.platformUrl}) could not be reached: ${r instanceof Error?r.message:String(r)}`)}}async function js(e,t){let n=await t.json().catch(()=>({}));return new m(n.error??`http_${t.status}`,`${e}: ${n.error_description??n.error??`auth answered ${t.status}`}`)}var iu="/v1",ou="x-studio-module-answer",ia=3e4,On=class{cloud;io;state;debug;session=null;constructor(t,n,s,r=!1){this.io=t,this.cloud=n,this.state=s,this.debug=r}async currentSession(){if(this.session&&this.io.now()<this.session.expires_at-ia)return this.session;let t=this.session??await this.state.readSession();if(!t)throw sn();return this.io.now()<t.expires_at-ia?(this.session=t,t):this.renew(t)}async get(t){return this.request("GET",t)}async post(t,n){return this.request("POST",t,n)}async request(t,n,s){let{res:r,renewable:a}=await this.exchange(t,n,s);return this.answered(t,n,r,a)}async call(t,n){let{res:s,renewable:r}=await this.exchange("POST",t,n);if(s.headers.get(ou)!=="1")throw await this.answered("POST",t,s,r),new m("studio_answer",`Studio answered ${t} ${s.status} as no module's answer`);let a=s.status===204||s.status===304?"":await s.text(),i=Number(s.headers.get("retry-after")??"");return{status:s.status,body:a===""?void 0:oa(a),etag:s.headers.get("etag")??void 0,retryAfter:Number.isFinite(i)&&i>0?i:void 0}}async exchange(t,n,s){let r=await this.token(),a=await this.send(t,n,s,r.value);if(a.status===401&&r.renewable){let i=await this.renew(await this.currentSession());a=await this.send(t,n,s,i.access_token)}return{res:a,renewable:r.renewable}}async answered(t,n,s,r){if(s.status===204)return;let a=await s.text(),i=a===""?void 0:oa(a);if(s.ok)return i;let o=i??{};throw s.status===401?r?sn():new m("token_refused","Studio refused PALBASE_TOKEN: it was revoked, it ended, or the one who made it no longer holds its role",{hint:"an admin makes a new one: palbase tokens:create <name>"}):new Y({op:`${t} ${n}`,status:s.status,code:o.error??`http_${s.status}`,message:o.error_description??`Studio answered ${s.status}`,requestId:o.request_id??s.headers.get("x-request-id")??void 0,body:i})}async token(){let t=this.io.env.PALBASE_TOKEN;return t&&t!==""?{value:t,renewable:!1}:{value:(await this.currentSession()).access_token,renewable:!0}}async renew(t){let n=await sa(this.io,this.cloud,t);return await this.state.writeSession(n),this.session=n,n}async send(t,n,s,r){let a=`${this.cloud.studioApi}${iu}${n}`;this.debug&&this.io.stderr(`> ${t} ${a}
9
9
  `);try{let i=await this.io.fetch(a,{method:t,headers:{authorization:`Bearer ${r}`,accept:"application/json",...s===void 0?{}:{"content-type":"application/json"}},body:s===void 0?void 0:JSON.stringify(s),redirect:"manual"});return this.debug&&this.io.stderr(`< ${i.status} ${i.headers.get("x-request-id")??""}
10
- `),i}catch(i){throw new m("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${i instanceof Error?i.message:String(i)}`)}}};function oa(e){try{return JSON.parse(e)}catch{return e}}var la={version:"0.1.0"};var Ds=la.version;async function ma(e,t,n){let s=new Map(n.map(i=>[i.name,i])),r=e.includes("--json"),a=e.includes("--debug");try{let{name:i,at:o}=pu(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${Ds}
11
- `),0):(t.stdout(ca(n)),e.length===0||e.includes("--help")||e.includes("-h")?0:2);if(i==="help"){let P=e[o+1],S=P===void 0?void 0:s.get(P);if(P!==void 0&&!S)throw da(P,n);return t.stdout(S?ua(S):ca(n)),0}let l=s.get(i);if(!l)throw da(i,n);let d=[...e.slice(0,o),...e.slice(o+1)];if(d.includes("--help")||d.includes("-h"))return t.stdout(ua(l)),0;let{flags:u,positionals:p}=uu(l,d),f=l.args??{min:0,max:0};if(p.length<f.min)throw g(`${l.name} needs ${f.min===1?"an argument":`${f.min} arguments`}`,Os(l));if(p.length>f.max)throw g(`${l.name} takes ${f.max===0?"no argument":`at most ${f.max}`}: ${JSON.stringify(p.slice(f.max))} is left over`,Os(l));let y={project:u.project,env:u.env,json:u.json===!0,nonInteractive:u["non-interactive"]===!0,yes:u.yes===!0,debug:u.debug===!0},v=Qr(t.env),T=new Cn(ea(t.configDir,v)),A={io:t,state:T,out:new En(t,y.json),global:y,args:p,flags:u,interactive:t.interactive&&!y.nonInteractive,cloud:v,studio:new On(t,v,T,y.debug)};return await l.run(A)??0}catch(i){return cu(i,t,r,a)}}function pu(e){let t=new Set;for(let[n,s]of Object.entries(Tn))s.type==="string"&&(t.add(`--${n}`),s.short&&t.add(`-${s.short}`));for(let n=0;n<e.length;n++){let s=e[n];if(s==="--")return{at:-1};if(t.has(s)){n++;continue}if(!s.startsWith("-"))return{name:s,at:n}}return{at:-1}}function uu(e,t){let n={...Tn,...e.flags??{}},s={};for(let[r,a]of Object.entries(n))s[r]={type:a.type,...a.short?{short:a.short}:{},...a.multiple?{multiple:!0}:{}};try{let{values:r,positionals:a}=du({args:t,options:s,strict:!0,allowPositionals:!0});return{flags:r,positionals:a}}catch(r){throw g(r instanceof Error?r.message.replace(/^.*?: /,""):String(r),`palbase help ${e.name}`)}}function cu(e,t,n,s){let r="error",a,i,o=1,l;return e instanceof m?(r=e.code,a=e.message,i=e.hint,o=e.exitCode):e instanceof Y?(r=e.code,a=e.message,l=e.requestId):a=e instanceof Error?e.message:String(e),n?t.stdout(`${JSON.stringify({error:r,message:a,...i?{hint:i}:{},...l?{request_id:l}:{}},null,2)}
10
+ `),i}catch(i){throw new m("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${i instanceof Error?i.message:String(i)}`)}}};function oa(e){try{return JSON.parse(e)}catch{return e}}var la={version:"0.2.0"};var Ds=la.version;async function ma(e,t,n){let s=new Map(n.map(i=>[i.name,i])),r=e.includes("--json"),a=e.includes("--debug");try{let{name:i,at:o}=du(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${Ds}
11
+ `),0):(t.stdout(ca(n)),e.length===0||e.includes("--help")||e.includes("-h")?0:2);if(i==="help"){let P=e[o+1],S=P===void 0?void 0:s.get(P);if(P!==void 0&&!S)throw pa(P,n);return t.stdout(S?ua(S):ca(n)),0}let l=s.get(i);if(!l)throw pa(i,n);let p=[...e.slice(0,o),...e.slice(o+1)];if(p.includes("--help")||p.includes("-h"))return t.stdout(ua(l)),0;let{flags:u,positionals:d}=uu(l,p),f=l.args??{min:0,max:0};if(d.length<f.min)throw g(`${l.name} needs ${f.min===1?"an argument":`${f.min} arguments`}`,Os(l));if(d.length>f.max)throw g(`${l.name} takes ${f.max===0?"no argument":`at most ${f.max}`}: ${JSON.stringify(d.slice(f.max))} is left over`,Os(l));let y={project:u.project,env:u.env,json:u.json===!0,nonInteractive:u["non-interactive"]===!0,yes:u.yes===!0,debug:u.debug===!0},v=Qr(t.env),T=new Cn(ea(t.configDir,v)),A={io:t,state:T,out:new En(t,y.json),global:y,args:d,flags:u,interactive:t.interactive&&!y.nonInteractive,cloud:v,studio:new On(t,v,T,y.debug)};return await l.run(A)??0}catch(i){return cu(i,t,r,a)}}function du(e){let t=new Set;for(let[n,s]of Object.entries(Tn))s.type==="string"&&(t.add(`--${n}`),s.short&&t.add(`-${s.short}`));for(let n=0;n<e.length;n++){let s=e[n];if(s==="--")return{at:-1};if(t.has(s)){n++;continue}if(!s.startsWith("-"))return{name:s,at:n}}return{at:-1}}function uu(e,t){let n={...Tn,...e.flags??{}},s={};for(let[r,a]of Object.entries(n))s[r]={type:a.type,...a.short?{short:a.short}:{},...a.multiple?{multiple:!0}:{}};try{let{values:r,positionals:a}=pu({args:t,options:s,strict:!0,allowPositionals:!0});return{flags:r,positionals:a}}catch(r){throw g(r instanceof Error?r.message.replace(/^.*?: /,""):String(r),`palbase help ${e.name}`)}}function cu(e,t,n,s){let r="error",a,i,o=1,l;return e instanceof m?(r=e.code,a=e.message,i=e.hint,o=e.exitCode):e instanceof Y?(r=e.code,a=e.message,l=e.requestId):a=e instanceof Error?e.message:String(e),n?t.stdout(`${JSON.stringify({error:r,message:a,...i?{hint:i}:{},...l?{request_id:l}:{}},null,2)}
12
12
  `):(t.stderr(`error: ${G(a)}${l?` (request ${G(l)})`:""}
13
13
  `),i&&t.stderr(` ${G(i)}
14
14
  `)),s&&e instanceof Error&&e.stack&&t.stderr(`${e.stack}
15
- `),o}function da(e,t){let n=t.map(s=>s.name).filter(s=>s.startsWith(e.split(":")[0])||mu(s,e)<=2);return g(`unknown command ${JSON.stringify(e)}`,n.length>0?`did you mean: ${n.slice(0,5).join(", ")}`:"palbase help lists every command")}function mu(e,t){let n=Array.from({length:e.length+1},(s,r)=>[r,...Array(t.length).fill(0)]);for(let s=1;s<=t.length;s++)n[0][s]=s;for(let s=1;s<=e.length;s++)for(let r=1;r<=t.length;r++)n[s][r]=Math.min(n[s-1][r]+1,n[s][r-1]+1,n[s-1][r-1]+(e[s-1]===t[r-1]?0:1));return n[e.length][t.length]}function Os(e){return`usage: palbase ${e.name}${e.usage?` ${e.usage}`:""}`}function pa(e){let t=Object.entries(e).map(([s,r])=>({name:`${r.short?`-${r.short}, `:" "}--${s}${r.type==="string"?` <${r.value??"value"}>`:""}`,text:r.description})),n=Math.max(...t.map(s=>s.name.length));return t.map(s=>` ${s.name.padEnd(n)} ${s.text}`)}function ua(e){let t=[Os(e),"",e.summary];return e.details?.length&&t.push("",...e.details),e.flags&&Object.keys(e.flags).length>0&&t.push("","Flags:",...pa(e.flags)),t.push("","Global flags:",...pa(Tn)),`${t.join(`
15
+ `),o}function pa(e,t){let n=t.map(s=>s.name).filter(s=>s.startsWith(e.split(":")[0])||mu(s,e)<=2);return g(`unknown command ${JSON.stringify(e)}`,n.length>0?`did you mean: ${n.slice(0,5).join(", ")}`:"palbase help lists every command")}function mu(e,t){let n=Array.from({length:e.length+1},(s,r)=>[r,...Array(t.length).fill(0)]);for(let s=1;s<=t.length;s++)n[0][s]=s;for(let s=1;s<=e.length;s++)for(let r=1;r<=t.length;r++)n[s][r]=Math.min(n[s-1][r]+1,n[s][r-1]+1,n[s-1][r-1]+(e[s-1]===t[r-1]?0:1));return n[e.length][t.length]}function Os(e){return`usage: palbase ${e.name}${e.usage?` ${e.usage}`:""}`}function da(e){let t=Object.entries(e).map(([s,r])=>({name:`${r.short?`-${r.short}, `:" "}--${s}${r.type==="string"?` <${r.value??"value"}>`:""}`,text:r.description})),n=Math.max(...t.map(s=>s.name.length));return t.map(s=>` ${s.name.padEnd(n)} ${s.text}`)}function ua(e){let t=[Os(e),"",e.summary];return e.details?.length&&t.push("",...e.details),e.flags&&Object.keys(e.flags).length>0&&t.push("","Flags:",...da(e.flags)),t.push("","Global flags:",...da(Tn)),`${t.join(`
16
16
  `)}
17
17
  `}function ca(e){let t=new Map;for(let r of e){let a=r.name.includes(":")?r.name.slice(0,r.name.indexOf(":")):"";t.set(a,[...t.get(a)??[],r])}let n=Math.max(...e.map(r=>r.name.length)),s=["palbase \u2014 your Palbase organizations, projects and environments from the terminal","","usage: palbase <command> [flags]",""];for(let[r,a]of t){s.push(r===""?"Commands:":`${r}:`);for(let i of a)s.push(` ${i.name.padEnd(n)} ${i.summary}`);s.push("")}return s.push("palbase help <command> shows a command in full."),`${s.join(`
18
18
  `)}
19
- `}import{createHash as fa,randomBytes as ga,timingSafeEqual as gu}from"node:crypto";import{createServer as hu}from"node:http";var an="owner.json",fu="urn:palbase:params:oauth:grant-type:owner-code",ha="/palbase/owner",yu=300*1e3,bu="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function wu(e){let t=fa("sha256").update(e,"ascii").digest().subarray(0,5),n=0n;for(let r of t)n=n<<8n|BigInt(r);let s="";for(let r=7;r>=0;r--)s+=bu[Number(n>>BigInt(r*5)&31n)];return`${s.slice(0,4)}-${s.slice(4)}`}async function ya(e,t,n=()=>{}){let s=ga(32).toString("base64url"),r=fa("sha256").update(s,"ascii").digest("base64url"),a=ga(24).toString("base64url"),i,o=new Promise((A,P)=>{i={ok:A,fail:P}}),l="",d=async A=>{let P=await e.fetch(`${t.platformUrl}/oauth/token`,{method:"POST",body:new URLSearchParams({grant_type:fu,code:A,code_verifier:s,redirect_uri:l}),redirect:"manual"}),S=await P.json().catch(()=>({}));if(P.status!==200||!S.access_token)throw new m(S.error??"owner_sign_in_failed",S.error_description??`the owner's code was not redeemed (${P.status})`,{hint:"run palbase login --owner again"});let C=rn(S.access_token),$=typeof C.sub=="string"?C.sub:"",K=Number(S.expires_in);if($===""||!Number.isFinite(K)||K<=0)throw new m("owner_sign_in_failed","the token endpoint answered no token");return{access_token:S.access_token,expires_at:e.now()+Math.min(K,900)*1e3,user:{id:$,...typeof C.email=="string"?{email:C.email}:{}}}},u=!1,p=hu((A,P)=>{let S=L=>{P.writeHead(303,{location:`${t.studioWeb}/cli/owner/done?result=${L}`,"cache-control":"no-store","referrer-policy":"no-referrer"}),P.end()},C=new URL(A.url??"/","http://127.0.0.1");if(A.method!=="GET"||C.pathname!==ha)return P.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),P.end("Not here.");let $=Buffer.from(C.searchParams.get("state")??""),K=Buffer.from(a);if($.length!==K.length||!gu($,K))return S("mismatch");let X=C.searchParams.get("code")??"";if(X===""||u)return S("mismatch");d(X).then(L=>{u=!0,S("signed-in"),i.ok(L)},L=>{S("refused"),n(L)})});await new Promise((A,P)=>{p.once("error",P),p.listen(0,"127.0.0.1",()=>A())}),l=`http://127.0.0.1:${p.address().port}${ha}`;let y=setTimeout(()=>i.fail(new m("owner_sign_in_timed_out","the owner sign-in was not confirmed in Studio in time",{hint:"run palbase login --owner again"})),yu),v=()=>{clearTimeout(y),p.close()};return o.then(v,v),{url:`${t.studioWeb}/cli/owner?${new URLSearchParams({redirect_uri:l,state:a,code_challenge:r})}`,match:wu(r),token:o,close:v}}async function vu(e){let t=await ya(e.io,e.cloud,a=>e.out.warn(`a sign-in came that was not taken (${a.message}); still waiting for yours`)),n=e.flags["no-browser"]!==!0&&await e.io.openBrowser(t.url);e.out.note(n?`Opened ${t.url} \u2014 confirm with your passkey there.`:`Open ${t.url} in this computer's browser and confirm with your passkey (the page answers this computer alone).`),e.out.note(`Confirm only if the page shows ${t.match}`),e.out.note("Waiting for the confirmation\u2026");let s=await t.token;await e.state.write(an,s);let r=Math.round((s.expires_at-e.io.now())/6e4);e.out.result({owner:s.user,expires_at:new Date(s.expires_at).toISOString()},()=>e.out.line(`Signed in as Palbase's owner (${s.user.email??s.user.id}) for ${r} minutes.`))}var ba={name:"login",summary:"sign in with your Studio account",details:["Shows a one-time code and opens Studio, where you approve it while signed in there.","The CLI then holds a session of yours: Studio lists it with your others, and ending it there signs the CLI out.","On a machine with no browser (a server, an agent), open the address it prints on any device."],flags:{"no-browser":{type:"boolean",description:"print the address instead of opening a browser"},owner:{type:"boolean",description:"Palbase's own project's owner: confirm with your passkey in this computer's browser, for 15 minutes of changes to it"}},scope:"none",async run(e){if(e.io.env.PALBASE_TOKEN)throw new m("token_in_use","PALBASE_TOKEN is set: the CLI acts with that token, and a sign-in would not be used",{hint:"unset PALBASE_TOKEN to sign in as yourself"});if(e.flags.owner===!0){await vu(e);return}let t=await ta(e.io,e.cloud),n=`${e.cloud.studioWeb}/device`;e.out.note(""),e.out.note(` Enter this code in your browser: ${t.user_code}`),e.out.note("");let s=e.flags["no-browser"]!==!0&&e.interactive&&await e.io.openBrowser(n);e.out.note(s?`Opened ${n} \u2014 sign in there if asked, then enter the code.`:`Open ${n} on any device, sign in, and enter the code.`),e.out.note("Waiting for the approval\u2026");let r=await na(e.io,e.cloud,t);await e.state.writeSession(r),e.out.result({user:r.user},()=>e.out.line(`Signed in as ${r.user.email??r.user.id}.`))}},wa={name:"logout",summary:"sign out: the session is ended at Palbase and forgotten on this computer",scope:"none",async run(e){if(e.io.env.PALBASE_TOKEN)throw new m("token_in_use","PALBASE_TOKEN is set: logout ends your own sign-in, and the CLI acts with the token",{hint:"unset PALBASE_TOKEN to sign out"});let t=await e.state.readSession();if(!t){e.out.result({signed_out:!0},()=>e.out.line("You were not signed in."));return}for(let[n,s]of Object.entries(await e.state.personalKeys()))try{await e.studio.request("DELETE",`/environments/${n}/personal-key/${encodeURIComponent(s.id)}`)}catch(r){r instanceof Error&&"status"in r&&r.status===404||e.out.warn(`your key at ${n} (${s.label}) could not be revoked: ${r instanceof Error?r.message:String(r)} \u2014 revoke it in Studio, or it ends ${s.expires_at}`)}await e.state.forgetPersonalKeys(),await e.state.forget(an);try{await ra(e.io,e.cloud,t)}catch(n){e.out.warn(`${n instanceof Error?n.message:String(n)} \u2014 end the session in Studio (Account \u203A Sessions)`)}await e.state.clearSession(),e.out.result({signed_out:!0},()=>e.out.line(`Signed out ${t.user.email??t.user.id}.`))}},va={name:"whoami",summary:"the account the CLI acts as, and the environment chosen for this directory",scope:"account",async run(e){let t=e.io.env.PALBASE_TOKEN,n;if(t?.startsWith("pbo_")){let{organizations:r}=await e.studio.get("/orgs"),a=r[0];n={id:`token \u2026${t.slice(-4)}`,via:"token",...a&&{organization:{id:a.id,name:a.name,role:a.role}}}}else if(t){let r=rn(t);n={id:String(r.sub??""),...typeof r.email=="string"?{email:r.email}:{},via:"token"}}else n={...(await e.studio.currentSession()).user,via:"session"};let s=await e.state.targetFor(e.io.cwd);e.out.result({user:n,target:s?.target??null,target_dir:s?.dir??null},()=>{e.out.line(n.organization?`an access token of ${G(n.organization.name)} (${n.organization.role}), ${n.id} (PALBASE_TOKEN)`:`${n.email??n.id}${n.via==="token"?" (PALBASE_TOKEN)":""}`),s&&e.out.line(`${s.target.org_name} \u203A ${s.target.project_name} \u203A ${s.target.env_name} (${s.target.env_slug}) \u2014 chosen for ${s.dir}`)})}};import{hostname as ku}from"node:os";async function Ae(e,t,n,s,r,a){if(t===In)return Au(e,n,s,r,a);let i=await e.call(`/environments/${encodeURIComponent(t)}/call`,{method:n,path:s,...r!==void 0?{body:r}:{},...a&&Object.keys(a).length>0?{headers:a}:{}});if(i.status>=200&&i.status<300)return{status:i.status,body:Rn(i.body),etag:i.etag};let o=i.body??{};throw new Y({op:`${n} ${s}`,status:i.status,code:typeof o.error=="string"?o.error:`http_${i.status}`,message:Ln(o,i.status),requestId:typeof o.request_id=="string"?o.request_id:void 0,body:i.body})}var In="palbase",$u=10080*60*1e3;async function on(e,t,n=!1){let s=await e.state.personalKey(t);if(!n&&s&&Date.parse(s.expires_at)-e.io.now()>$u)return s;let r=await e.post(`/environments/${encodeURIComponent(t)}/personal-key`,{host:ku().slice(0,100)||"computer"});return await e.state.keepPersonalKey(t,r),r}async function Au(e,t,n,s,r){let a=e.io.env,i=async(f,y)=>{let v=`${ct(e.cloud,In)}${n}`;try{return await e.io.fetch(v,{method:t,headers:{...r,...y,apikey:f,accept:"application/json",...s!==void 0?{"content-type":"application/json"}:{}},body:s!==void 0?JSON.stringify(s):void 0,redirect:"manual"})}catch(T){throw new m("network_error",`${v} could not be reached: ${T instanceof Error?T.message:String(T)}`)}},o,l=a.PALBASE_OPERATOR_TOKEN;if(l){let f=a.PALBASE_SECRET_KEY;if(!f)throw new m("operator_without_key","PALBASE_OPERATOR_TOKEN goes with the project's secret key in PALBASE_SECRET_KEY",{hint:"set PALBASE_SECRET_KEY to the project's secret key"});o=await i(f,{"x-palbase-operator":l})}else{let f=await e.state.read(an),y=f&&f.expires_at>e.io.now()?f.access_token:void 0,v=()=>y?{"x-palbase-owner":y}:{},T=await on(e,In);o=await i(T.key,v()),o.status===401&&await Pu(o.clone())==="invalid_api_key"&&(T=await on(e,In,!0),o=await i(T.key,v()))}let d=await o.text(),u=null;if(d)try{u=JSON.parse(d)}catch{u=d}if(o.status>=200&&o.status<300)return{status:o.status,body:Rn(u),etag:o.headers.get("etag")??void 0};let p=u??{};throw o.status===401&&p.error==="insufficient_user_authentication"||o.status===403&&p.error==="owner_only"?new m(p.error,"a change to Palbase's own project is its owner's, with a passkey sign-in of the last 15 minutes",{hint:"run palbase login --owner (it opens this computer's browser; confirm with your passkey), then run this again"}):new Y({op:`${t} ${n}`,status:o.status,code:typeof p.error=="string"?p.error:`http_${o.status}`,message:Ln(p,o.status),requestId:typeof p.request_id=="string"?p.request_id:o.headers.get("x-request-id")??void 0,body:u})}function Ln(e,t){let n=e??{},s=typeof n.error_description=="string"?n.error_description:`the module answered ${t}`,r=Array.isArray(n.field_errors)?n.field_errors.flatMap(a=>{let{pointer:i,message:o}=a??{};return typeof o=="string"?[`${typeof i=="string"&&i!==""?`${i}: `:""}${o}`]:[]}):[];return r.length>0?`${s}: ${r.join("; ")}`:s}async function Pu(e){let t=await e.json().catch(()=>({}));return typeof t.error=="string"?t.error:void 0}import{relative as Ru}from"node:path";async function Bs(e,t){let n=await e.run("git",["rev-parse","--show-toplevel"],{cwd:t});if(n.code!==0)return null;let s=n.stdout.trim(),r=await e.run("git",["remote","get-url","origin"],{cwd:t}),a=await e.run("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:t}),i=a.code===0?a.stdout.trim():"";return{root:s,repository:r.code===0?Tu(r.stdout.trim()):void 0,branch:i!==""&&i!=="HEAD"?i:void 0,path:Ru(s,t).split("\\").join("/")}}function Tu(e){let t=/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/.exec(e);return t?`${t[1]}/${t[2]}`:void 0}var Pe="main";async function Me(e){let[t,n]=await Promise.all([e.get("/environments"),e.get("/orgs")]),s=new Map(n.organizations.map(r=>[r.id,r.name]));return t.environments.map(r=>({...r,org_name:s.get(r.organization_id)??r.organization_id}))}function ue(e){return{org_id:e.organization_id,org_name:e.org_name,project_id:e.project_id,project_name:e.project_name,env_ref:e.ref,env_slug:e.slug,env_name:e.name}}function M(e){return`${e.org_name} \u203A ${e.project_name} \u203A ${e.env_name}${e.env_name!==e.env_slug?` (${e.env_slug})`:""}`}async function h(e){let{project:t,env:n}=e.global;if(t!==void 0||n!==void 0){let i=await Me(e.studio),o;if(t!==void 0)o=mt(i,t);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw Js("-E names an environment of the project chosen here, and none is chosen: add -P <project>");o=i.filter(d=>d.project_id===l.target.project_id)}if(n!==void 0){let l=o.find(d=>d.slug===n||d.ref===n);if(!l)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(n)}`,{hint:`its environments: ${o.map(d=>d.slug).join(", ")}`});return ue(l)}return o.length===1?ue(o[0]):ue(await $a(e,o,"Which environment?"))}let s=await e.state.targetFor(e.io.cwd);if(s)return s.target;if(!e.interactive)throw Js("no environment is chosen for this directory");let r=await Me(e.studio);if(r.length===0)throw new m("no_environments","your account reaches no environment yet",{hint:"make one: palbase projects:create <name>"});let a=ue(await $a(e,r,"Which environment?"));return e.out.note(`Acting on ${M(a)}. palbase use remembers it for this directory.`),a}function mt(e,t){let n=e.filter(l=>l.project_id===t);if(n.length>0)return n;let s=t.indexOf("/"),[r,a]=s>=0?[t.slice(0,s),t.slice(s+1)]:[void 0,t],i=e.filter(l=>Aa(l.project_name,a)&&(r===void 0||Aa(l.org_name,r)||l.organization_id===r)),o=new Set(i.map(l=>l.project_id));if(o.size===0)throw new m("no_such_project",`no project ${JSON.stringify(t)} among yours`,{hint:"palbase projects:list shows them"});if(o.size>1){let l=[...new Set(i.map(d=>`${d.org_name}/${d.project_name}`))].join(", ");throw new m("ambiguous_project",`${JSON.stringify(t)} is a project in more than one organization: ${l}`,{hint:"name it as org/project"})}return i}async function b(e,t,n,s){e.out.note(`${M(t)}: ${n}`);let r=t.env_slug===Pe;if(!r&&s===void 0||e.global.yes)return;if(!e.interactive)throw new m("confirmation_needed",r?`${t.project_name} \u203A ${t.env_name} is the project's production environment`:s,{hint:"confirm with -y"});let a=(await e.io.prompt(G(`${r?`This is ${t.project_name}'s production environment`:s}. Go ahead? [y/N] `))).trim().toLowerCase();if(a!=="y"&&a!=="yes")throw new m("cancelled","nothing was changed")}async function $a(e,t,n){if(!e.interactive)throw Js(`${n.replace(/\?$/,"")}: ${t.map(s=>`${s.project_name}/${s.slug}`).join(", ")}`);for(t.forEach((s,r)=>e.out.note(` ${String(r+1).padStart(2)}) ${M(ue(s))}`));;){let s=(await e.io.prompt(`${n} [1-${t.length}] `)).trim(),r=Number(s);if(Number.isInteger(r)&&r>=1&&r<=t.length)return t[r-1];e.out.note(`answer with a number from 1 to ${t.length}`)}}function Js(e){return new m("no_target",e,{hint:"choose one here with palbase use, or pass -P <project> -E <env>"})}function Aa(e,t){return e.localeCompare(t,void 0,{sensitivity:"accent"})===0}import{randomUUID as Eu}from"node:crypto";import{readFile as Cu}from"node:fs/promises";import{resolve as ju}from"node:path";var Ou=6,zs=0,Pa=[];async function Du(e){zs<Ou?zs++:await new Promise(t=>Pa.push(t));try{return await e()}finally{let t=Pa.shift();t?t():zs--}}async function we(e,t,n,s,r,a){return Du(()=>Ae(e.studio,t.env_ref,n,s,r,a))}async function c(e,t,n,s,r,a){return(await we(e,t,n,s,r,a)).body}async function ce(e,t,n){let s=await we(e,t,"GET",n);return{body:s.body,etag:s.etag}}function oe(e){return e?{"if-match":e}:void 0}function R(){return{"idempotency-key":Eu()}}function _(e){return encodeURIComponent(e)}function et(e){return e.map(t=>{let n=t.indexOf("=");if(n<=0)throw g(`a setting is key=value, not ${JSON.stringify(t)}`);let s=t.slice(0,n).split(".");if(s.some(r=>r===""||r==="__proto__"||r==="constructor"||r==="prototype"))throw g(`${JSON.stringify(t.slice(0,n))} is not a key`);return{path:s,value:tt(t.slice(n+1))}})}function tt(e){try{return JSON.parse(e)}catch{return e}}function nt(e,t){let n=structuredClone(e);for(let{path:s,value:r}of t){let a=n;for(let i of s.slice(0,-1)){let o=a[i];(typeof o!="object"||o===null)&&(a[i]={}),a=a[i]}a[s[s.length-1]]=r}return n}function ze(e,t){return[...new Set([...Object.keys(e),...Object.keys(t)])].filter(n=>!Re(e[n],t[n])).sort()}function Ra(e){return JSON.stringify(e,(t,n)=>n!==null&&typeof n=="object"&&!Array.isArray(n)?Object.fromEntries(Object.entries(n).sort(([s],[r])=>s<r?-1:s>r?1:0)):n)}function Re(e,t){return Ra(e)===Ra(t)}function le(e){return e===void 0?"(not set)":typeof e=="string"&&e!==""?e:JSON.stringify(e)}function Pt(e,t){let n=Object.keys(t).sort(),s=Math.max(0,...n.map(r=>r.length));for(let r of n)e.out.line(`${r.padEnd(s)} ${le(t[r])}`)}function We(e,t,n){e.out.result({settings:n,changed:[]},()=>e.out.line(`Nothing to change: ${t} already ${t.endsWith("s")?"are":"is"} so.`))}async function ve(e,t,n){let s;if(t!==void 0&&t!=="-")try{s=await Cu(ju(e.io.cwd,t),"utf8")}catch(r){throw new m("unreadable_file",`${t} could not be read: ${r instanceof Error?r.message:String(r)}`)}else e.io.interactive&&e.out.note(`Reading ${n} from stdin; end it with Ctrl-D.`),s=await e.io.readStdin();if(s.trim()==="")throw g(`no ${n} was given`,t===void 0?"pass --file <path>, or pipe it in":void 0);return s}function he(e,t){try{return JSON.parse(e)}catch(n){throw g(`${t} is not JSON: ${n instanceof Error?n.message:String(n)}`)}}async function fe(e,t){e.io.interactive&&e.out.note(`Paste ${t}, then Ctrl-D (it is read from stdin, never from the command line):`);let n=(await e.io.readStdin()).trim();if(n==="")throw g(`no ${t} was piped in`,"pipe it in: palbase \u2026 < file");return n}function Rt(e){if(e.flags.enable===!0&&e.flags.disable===!0)throw g("--enable and --disable are one or the other");if(e.flags.enable===!0)return!0;if(e.flags.disable===!0)return!1}function Tt(e,t){if(e!==void 0){if(e==="yes"||e==="true")return!0;if(e==="no"||e==="false")return!1;throw g(`--${t} is yes or no`)}}function me(e){return e===void 0?void 0:(Array.isArray(e)?e:[e]).flatMap(n=>String(n).split(",")).map(n=>n.trim()).filter(n=>n!=="")}function Ue(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw g(`--${t} is a whole number`);return n}var ln={enable:{type:"boolean",description:"turn it on"},disable:{type:"boolean",description:"turn it off"}};async function x(e,t,n){try{await n(),e.done(t)}catch(s){if(!(s instanceof Y))throw s;e.left(`${t}: refused \u2014 ${s.message}`)}}function Q(e,t){return e.filter(n=>n.key.startsWith(t)).map(n=>({...n,name:n.key.slice(t.length)}))}function Ws(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>!(t==null||t===""||Array.isArray(t)&&t.length===0)))}import{randomBytes as Ia}from"node:crypto";import{chmod as Ku,lstat as Gu,readFile as Ea,rename as Bu,writeFile as Ca}from"node:fs/promises";var u_=1024*1024;var g_=8*1024;var __=16*1024,S_=10*6e4;var Mu=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Uu=/^(PALBASE_|NODE_|BUN_|NPM_|LD_|DYLD_|GIT_|SSH_)|^(PATH|HOME|SHELL|USER|TMPDIR|TMP|TEMP|PWD|IFS|ENV|BASH_ENV|SSL_CERT_FILE|SSL_CERT_DIR|CURL_CA_BUNDLE|REQUESTS_CA_BUNDLE|NO_PROXY|ALL_PROXY|HTTP_PROXY|HTTPS_PROXY|PG\w*)$/i;function Fs(e){return!Mu.test(e)||Uu.test(e)||/_PROXY$/i.test(e)}var xu=/^(PORT|PALBASE_(URL|PROJECT_ID|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function Vs(e){return xu.test(e)}import"node:os";import{join as Ju,resolve as zu}from"node:path";var a0=10080*60*1e3,Ta="# >>> palbase (palbase env:pull writes this block; edit outside it)",Hs="# <<< palbase";async function Wu(e,t){let n=await on(e.studio,t.env_ref),{keys:s}=await e.studio.get(`/environments/${encodeURIComponent(t.env_ref)}/keys`),r=s.find(a=>a.id===n.id);return r&&!r.revoked_at?n:on(e.studio,t.env_ref,!0)}async function Nn(e,t){let n=await e.studio.get(`/environments/${t.env_ref}`);return{url:ct(e.cloud,t.env_ref),projectId:n.platform_id,key:await Wu(e,t)}}function qn(e){let t=e.flags.secrets??"development";if(t!=="development"&&t!=="production")throw g("--secrets is development or production");return t}function Mn(e,t){return t==="production"?`the app reads ${e.project_name} \u203A ${e.env_name}'s production secrets here`:void 0}var Un={type:"string",value:"set",description:"the project's secrets the app reads at boot: development (default) or production"},ja={name:"env:pull",summary:"write what the backend needs to run here into .env.local: the environment's address and your own key \u2014 no secret's value",usage:"[--file <path>] [--secrets development|production]",details:["The app reads the project's secrets at boot (PALBASE_SECRETS); their names are listed as comments: set one in the file to override it.","The key is yours (CLI \u2014 you (this computer)), good for 30 days, made again by env:pull and run; palbase logout revokes it.","Only the block between the palbase markers is written; the rest of the file stays as it is."],flags:{file:{type:"string",value:"path",description:"the file (default .env.local)"},secrets:Un},scope:"environment",async run(e){let t=await h(e),n=qn(e);(t.env_slug===Pe||n==="production")&&await b(e,t,`write a key of ${t.env_slug===Pe?"the production environment":`${t.env_slug}`} into a file on this computer`,Mn(t,n));let s=await Nn(e,t),r=await Ae(e.studio,t.env_ref,"GET",`/secrets/${n}`).then(y=>(y.body.secrets??y.body.items??[]).map(v=>v.name).filter(v=>typeof v=="string"&&!Fs(v)).sort(),()=>[]),a=zu(e.io.cwd,e.flags.file??".env.local"),i=[Ta,`# ${Xs(M(t))} \u2014 ${new Date(e.io.now()).toISOString()}`,`# PALBASE_SECRET_KEY is your own: ${Xs(s.key.label)}, good until ${Xs(s.key.expires_at)}.`,`PALBASE_URL=${s.url}`,`PALBASE_PROJECT_ID=${s.projectId}`,`PALBASE_SECRET_KEY=${s.key.key}`,`PALBASE_SECRETS=${n}`,...r.length>0?[`# The ${n} secrets, read at boot \u2014 set one here to override it:`,...r.map(y=>`# ${y}=`)]:[],Hs].join(`
20
- `),o="";try{if((await Gu(a)).isSymbolicLink())throw new m("symlink",`${a} is a link: env:pull writes the key into a file of its own`,{hint:"remove the link, or pass --file <path>"});o=await Ea(a,"utf8")}catch(y){if(y.code!=="ENOENT")throw y}let l=o.indexOf(Ta),d=o.indexOf(Hs),u=l>=0&&d>l?`${o.slice(0,l)}${i}${o.slice(d+Hs.length)}`:`${o}${o===""||o.endsWith(`
19
+ `}import{createHash as fa,randomBytes as ga,timingSafeEqual as gu}from"node:crypto";import{createServer as hu}from"node:http";var an="owner.json",fu="urn:palbase:params:oauth:grant-type:owner-code",ha="/palbase/owner",yu=300*1e3,bu="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function wu(e){let t=fa("sha256").update(e,"ascii").digest().subarray(0,5),n=0n;for(let r of t)n=n<<8n|BigInt(r);let s="";for(let r=7;r>=0;r--)s+=bu[Number(n>>BigInt(r*5)&31n)];return`${s.slice(0,4)}-${s.slice(4)}`}async function ya(e,t,n=()=>{}){let s=ga(32).toString("base64url"),r=fa("sha256").update(s,"ascii").digest("base64url"),a=ga(24).toString("base64url"),i,o=new Promise((A,P)=>{i={ok:A,fail:P}}),l="",p=async A=>{let P=await e.fetch(`${t.platformUrl}/oauth/token`,{method:"POST",body:new URLSearchParams({grant_type:fu,code:A,code_verifier:s,redirect_uri:l}),redirect:"manual"}),S=await P.json().catch(()=>({}));if(P.status!==200||!S.access_token)throw new m(S.error??"owner_sign_in_failed",S.error_description??`the owner's code was not redeemed (${P.status})`,{hint:"run palbase login --owner again"});let C=rn(S.access_token),$=typeof C.sub=="string"?C.sub:"",K=Number(S.expires_in);if($===""||!Number.isFinite(K)||K<=0)throw new m("owner_sign_in_failed","the token endpoint answered no token");return{access_token:S.access_token,expires_at:e.now()+Math.min(K,900)*1e3,user:{id:$,...typeof C.email=="string"?{email:C.email}:{}}}},u=!1,d=hu((A,P)=>{let S=L=>{P.writeHead(303,{location:`${t.studioWeb}/cli/owner/done?result=${L}`,"cache-control":"no-store","referrer-policy":"no-referrer"}),P.end()},C=new URL(A.url??"/","http://127.0.0.1");if(A.method!=="GET"||C.pathname!==ha)return P.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),P.end("Not here.");let $=Buffer.from(C.searchParams.get("state")??""),K=Buffer.from(a);if($.length!==K.length||!gu($,K))return S("mismatch");let X=C.searchParams.get("code")??"";if(X===""||u)return S("mismatch");p(X).then(L=>{u=!0,S("signed-in"),i.ok(L)},L=>{S("refused"),n(L)})});await new Promise((A,P)=>{d.once("error",P),d.listen(0,"127.0.0.1",()=>A())}),l=`http://127.0.0.1:${d.address().port}${ha}`;let y=setTimeout(()=>i.fail(new m("owner_sign_in_timed_out","the owner sign-in was not confirmed in Studio in time",{hint:"run palbase login --owner again"})),yu),v=()=>{clearTimeout(y),d.close()};return o.then(v,v),{url:`${t.studioWeb}/cli/owner?${new URLSearchParams({redirect_uri:l,state:a,code_challenge:r})}`,match:wu(r),token:o,close:v}}async function vu(e){let t=await ya(e.io,e.cloud,a=>e.out.warn(`a sign-in came that was not taken (${a.message}); still waiting for yours`)),n=e.flags["no-browser"]!==!0&&await e.io.openBrowser(t.url);e.out.note(n?`Opened ${t.url} \u2014 confirm with your passkey there.`:`Open ${t.url} in this computer's browser and confirm with your passkey (the page answers this computer alone).`),e.out.note(`Confirm only if the page shows ${t.match}`),e.out.note("Waiting for the confirmation\u2026");let s=await t.token;await e.state.write(an,s);let r=Math.round((s.expires_at-e.io.now())/6e4);e.out.result({owner:s.user,expires_at:new Date(s.expires_at).toISOString()},()=>e.out.line(`Signed in as Palbase's owner (${s.user.email??s.user.id}) for ${r} minutes.`))}var ba={name:"login",summary:"sign in with your Studio account",details:["Shows a one-time code and opens Studio, where you approve it while signed in there.","The CLI then holds a session of yours: Studio lists it with your others, and ending it there signs the CLI out.","On a machine with no browser (a server, an agent), open the address it prints on any device."],flags:{"no-browser":{type:"boolean",description:"print the address instead of opening a browser"},owner:{type:"boolean",description:"Palbase's own project's owner: confirm with your passkey in this computer's browser, for 15 minutes of changes to it"}},scope:"none",async run(e){if(e.io.env.PALBASE_TOKEN)throw new m("token_in_use","PALBASE_TOKEN is set: the CLI acts with that token, and a sign-in would not be used",{hint:"unset PALBASE_TOKEN to sign in as yourself"});if(e.flags.owner===!0){await vu(e);return}let t=await ta(e.io,e.cloud),n=`${e.cloud.studioWeb}/device`;e.out.note(""),e.out.note(` Enter this code in your browser: ${t.user_code}`),e.out.note("");let s=e.flags["no-browser"]!==!0&&e.interactive&&await e.io.openBrowser(n);e.out.note(s?`Opened ${n} \u2014 sign in there if asked, then enter the code.`:`Open ${n} on any device, sign in, and enter the code.`),e.out.note("Waiting for the approval\u2026");let r=await na(e.io,e.cloud,t);await e.state.writeSession(r),e.out.result({user:r.user},()=>e.out.line(`Signed in as ${r.user.email??r.user.id}.`))}},wa={name:"logout",summary:"sign out: the session is ended at Palbase and forgotten on this computer",scope:"none",async run(e){if(e.io.env.PALBASE_TOKEN)throw new m("token_in_use","PALBASE_TOKEN is set: logout ends your own sign-in, and the CLI acts with the token",{hint:"unset PALBASE_TOKEN to sign out"});let t=await e.state.readSession();if(!t){e.out.result({signed_out:!0},()=>e.out.line("You were not signed in."));return}for(let[n,s]of Object.entries(await e.state.personalKeys()))try{await e.studio.request("DELETE",`/environments/${n}/personal-key/${encodeURIComponent(s.id)}`)}catch(r){r instanceof Error&&"status"in r&&r.status===404||e.out.warn(`your key at ${n} (${s.label}) could not be revoked: ${r instanceof Error?r.message:String(r)} \u2014 revoke it in Studio, or it ends ${s.expires_at}`)}await e.state.forgetPersonalKeys(),await e.state.forget(an);try{await ra(e.io,e.cloud,t)}catch(n){e.out.warn(`${n instanceof Error?n.message:String(n)} \u2014 end the session in Studio (Account \u203A Sessions)`)}await e.state.clearSession(),e.out.result({signed_out:!0},()=>e.out.line(`Signed out ${t.user.email??t.user.id}.`))}},va={name:"whoami",summary:"the account the CLI acts as, and the environment chosen for this directory",scope:"account",async run(e){let t=e.io.env.PALBASE_TOKEN,n;if(t?.startsWith("pbo_")){let{organizations:r}=await e.studio.get("/orgs"),a=r[0];n={id:`token \u2026${t.slice(-4)}`,via:"token",...a&&{organization:{id:a.id,name:a.name,role:a.role}}}}else if(t){let r=rn(t);n={id:String(r.sub??""),...typeof r.email=="string"?{email:r.email}:{},via:"token"}}else n={...(await e.studio.currentSession()).user,via:"session"};let s=await e.state.targetFor(e.io.cwd);e.out.result({user:n,target:s?.target??null,target_dir:s?.dir??null},()=>{e.out.line(n.organization?`an access token of ${G(n.organization.name)} (${n.organization.role}), ${n.id} (PALBASE_TOKEN)`:`${n.email??n.id}${n.via==="token"?" (PALBASE_TOKEN)":""}`),s&&e.out.line(`${s.target.org_name} \u203A ${s.target.project_name} \u203A ${s.target.env_name} (${s.target.env_slug}) \u2014 chosen for ${s.dir}`)})}};import{hostname as ku}from"node:os";async function Ae(e,t,n,s,r,a){if(t===In)return Au(e,n,s,r,a);let i=await e.call(`/environments/${encodeURIComponent(t)}/call`,{method:n,path:s,...r!==void 0?{body:r}:{},...a&&Object.keys(a).length>0?{headers:a}:{}});if(i.status>=200&&i.status<300)return{status:i.status,body:Rn(i.body),etag:i.etag};let o=i.body??{};throw new Y({op:`${n} ${s}`,status:i.status,code:typeof o.error=="string"?o.error:`http_${i.status}`,message:Ln(o,i.status),requestId:typeof o.request_id=="string"?o.request_id:void 0,body:i.body})}var In="palbase",$u=10080*60*1e3;async function on(e,t,n=!1){let s=await e.state.personalKey(t);if(!n&&s&&Date.parse(s.expires_at)-e.io.now()>$u)return s;let r=await e.post(`/environments/${encodeURIComponent(t)}/personal-key`,{host:ku().slice(0,100)||"computer"});return await e.state.keepPersonalKey(t,r),r}async function Au(e,t,n,s,r){let a=e.io.env,i=async(f,y)=>{let v=`${ct(e.cloud,In)}${n}`;try{return await e.io.fetch(v,{method:t,headers:{...r,...y,apikey:f,accept:"application/json",...s!==void 0?{"content-type":"application/json"}:{}},body:s!==void 0?JSON.stringify(s):void 0,redirect:"manual"})}catch(T){throw new m("network_error",`${v} could not be reached: ${T instanceof Error?T.message:String(T)}`)}},o,l=a.PALBASE_OPERATOR_TOKEN;if(l){let f=a.PALBASE_SECRET_KEY;if(!f)throw new m("operator_without_key","PALBASE_OPERATOR_TOKEN goes with the project's secret key in PALBASE_SECRET_KEY",{hint:"set PALBASE_SECRET_KEY to the project's secret key"});o=await i(f,{"x-palbase-operator":l})}else{let f=await e.state.read(an),y=f&&f.expires_at>e.io.now()?f.access_token:void 0,v=()=>y?{"x-palbase-owner":y}:{},T=await on(e,In);o=await i(T.key,v()),o.status===401&&await Pu(o.clone())==="invalid_api_key"&&(T=await on(e,In,!0),o=await i(T.key,v()))}let p=await o.text(),u=null;if(p)try{u=JSON.parse(p)}catch{u=p}if(o.status>=200&&o.status<300)return{status:o.status,body:Rn(u),etag:o.headers.get("etag")??void 0};let d=u??{};throw o.status===401&&d.error==="insufficient_user_authentication"||o.status===403&&d.error==="owner_only"?new m(d.error,"a change to Palbase's own project is its owner's, with a passkey sign-in of the last 15 minutes",{hint:"run palbase login --owner (it opens this computer's browser; confirm with your passkey), then run this again"}):new Y({op:`${t} ${n}`,status:o.status,code:typeof d.error=="string"?d.error:`http_${o.status}`,message:Ln(d,o.status),requestId:typeof d.request_id=="string"?d.request_id:o.headers.get("x-request-id")??void 0,body:u})}function Ln(e,t){let n=e??{},s=typeof n.error_description=="string"?n.error_description:`the module answered ${t}`,r=Array.isArray(n.field_errors)?n.field_errors.flatMap(a=>{let{pointer:i,message:o}=a??{};return typeof o=="string"?[`${typeof i=="string"&&i!==""?`${i}: `:""}${o}`]:[]}):[];return r.length>0?`${s}: ${r.join("; ")}`:s}async function Pu(e){let t=await e.json().catch(()=>({}));return typeof t.error=="string"?t.error:void 0}import{relative as Ru}from"node:path";async function Bs(e,t){let n=await e.run("git",["rev-parse","--show-toplevel"],{cwd:t});if(n.code!==0)return null;let s=n.stdout.trim(),r=await e.run("git",["remote","get-url","origin"],{cwd:t}),a=await e.run("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:t}),i=a.code===0?a.stdout.trim():"";return{root:s,repository:r.code===0?Tu(r.stdout.trim()):void 0,branch:i!==""&&i!=="HEAD"?i:void 0,path:Ru(s,t).split("\\").join("/")}}function Tu(e){let t=/^(?:https?:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/.exec(e);return t?`${t[1]}/${t[2]}`:void 0}var Pe="main";async function xe(e){let[t,n]=await Promise.all([e.get("/environments"),e.get("/orgs")]),s=new Map(n.organizations.map(r=>[r.id,r.name]));return t.environments.map(r=>({...r,org_name:s.get(r.organization_id)??r.organization_id}))}function ue(e){return{org_id:e.organization_id,org_name:e.org_name,project_id:e.project_id,project_name:e.project_name,env_ref:e.ref,env_slug:e.slug,env_name:e.name}}function x(e){return`${e.org_name} \u203A ${e.project_name} \u203A ${e.env_name}${e.env_name!==e.env_slug?` (${e.env_slug})`:""}`}async function h(e){let{project:t,env:n}=e.global;if(t!==void 0||n!==void 0){let i=await xe(e.studio),o;if(t!==void 0)o=mt(i,t);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw Js("-E names an environment of the project chosen here, and none is chosen: add -P <project>");o=i.filter(p=>p.project_id===l.target.project_id)}if(n!==void 0){let l=o.find(p=>p.slug===n||p.ref===n);if(!l)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(n)}`,{hint:`its environments: ${o.map(p=>p.slug).join(", ")}`});return ue(l)}return o.length===1?ue(o[0]):ue(await $a(e,o,"Which environment?"))}let s=await e.state.targetFor(e.io.cwd);if(s)return s.target;if(!e.interactive)throw Js("no environment is chosen for this directory");let r=await xe(e.studio);if(r.length===0)throw new m("no_environments","your account reaches no environment yet",{hint:"make one: palbase projects:create <name>"});let a=ue(await $a(e,r,"Which environment?"));return e.out.note(`Acting on ${x(a)}. palbase use remembers it for this directory.`),a}function mt(e,t){let n=e.filter(l=>l.project_id===t);if(n.length>0)return n;let s=t.indexOf("/"),[r,a]=s>=0?[t.slice(0,s),t.slice(s+1)]:[void 0,t],i=e.filter(l=>Aa(l.project_name,a)&&(r===void 0||Aa(l.org_name,r)||l.organization_id===r)),o=new Set(i.map(l=>l.project_id));if(o.size===0)throw new m("no_such_project",`no project ${JSON.stringify(t)} among yours`,{hint:"palbase projects:list shows them"});if(o.size>1){let l=[...new Set(i.map(p=>`${p.org_name}/${p.project_name}`))].join(", ");throw new m("ambiguous_project",`${JSON.stringify(t)} is a project in more than one organization: ${l}`,{hint:"name it as org/project"})}return i}async function b(e,t,n,s){e.out.note(`${x(t)}: ${n}`);let r=t.env_slug===Pe;if(!r&&s===void 0||e.global.yes)return;if(!e.interactive)throw new m("confirmation_needed",r?`${t.project_name} \u203A ${t.env_name} is the project's production environment`:s,{hint:"confirm with -y"});let a=(await e.io.prompt(G(`${r?`This is ${t.project_name}'s production environment`:s}. Go ahead? [y/N] `))).trim().toLowerCase();if(a!=="y"&&a!=="yes")throw new m("cancelled","nothing was changed")}async function $a(e,t,n){if(!e.interactive)throw Js(`${n.replace(/\?$/,"")}: ${t.map(s=>`${s.project_name}/${s.slug}`).join(", ")}`);for(t.forEach((s,r)=>e.out.note(` ${String(r+1).padStart(2)}) ${x(ue(s))}`));;){let s=(await e.io.prompt(`${n} [1-${t.length}] `)).trim(),r=Number(s);if(Number.isInteger(r)&&r>=1&&r<=t.length)return t[r-1];e.out.note(`answer with a number from 1 to ${t.length}`)}}function Js(e){return new m("no_target",e,{hint:"choose one here with palbase use, or pass -P <project> -E <env>"})}function Aa(e,t){return e.localeCompare(t,void 0,{sensitivity:"accent"})===0}import{randomUUID as Eu}from"node:crypto";import{readFile as Cu}from"node:fs/promises";import{resolve as ju}from"node:path";var Ou=6,zs=0,Pa=[];async function Du(e){zs<Ou?zs++:await new Promise(t=>Pa.push(t));try{return await e()}finally{let t=Pa.shift();t?t():zs--}}async function we(e,t,n,s,r,a){return Du(()=>Ae(e.studio,t.env_ref,n,s,r,a))}async function c(e,t,n,s,r,a){return(await we(e,t,n,s,r,a)).body}async function ce(e,t,n){let s=await we(e,t,"GET",n);return{body:s.body,etag:s.etag}}function oe(e){return e?{"if-match":e}:void 0}function R(){return{"idempotency-key":Eu()}}function _(e){return encodeURIComponent(e)}function et(e){return e.map(t=>{let n=t.indexOf("=");if(n<=0)throw g(`a setting is key=value, not ${JSON.stringify(t)}`);let s=t.slice(0,n).split(".");if(s.some(r=>r===""||r==="__proto__"||r==="constructor"||r==="prototype"))throw g(`${JSON.stringify(t.slice(0,n))} is not a key`);return{path:s,value:tt(t.slice(n+1))}})}function tt(e){try{return JSON.parse(e)}catch{return e}}function nt(e,t){let n=structuredClone(e);for(let{path:s,value:r}of t){let a=n;for(let i of s.slice(0,-1)){let o=a[i];(typeof o!="object"||o===null)&&(a[i]={}),a=a[i]}a[s[s.length-1]]=r}return n}function ze(e,t){return[...new Set([...Object.keys(e),...Object.keys(t)])].filter(n=>!Re(e[n],t[n])).sort()}function Ra(e){return JSON.stringify(e,(t,n)=>n!==null&&typeof n=="object"&&!Array.isArray(n)?Object.fromEntries(Object.entries(n).sort(([s],[r])=>s<r?-1:s>r?1:0)):n)}function Re(e,t){return Ra(e)===Ra(t)}function le(e){return e===void 0?"(not set)":typeof e=="string"&&e!==""?e:JSON.stringify(e)}function Pt(e,t){let n=Object.keys(t).sort(),s=Math.max(0,...n.map(r=>r.length));for(let r of n)e.out.line(`${r.padEnd(s)} ${le(t[r])}`)}function We(e,t,n){e.out.result({settings:n,changed:[]},()=>e.out.line(`Nothing to change: ${t} already ${t.endsWith("s")?"are":"is"} so.`))}async function ve(e,t,n){let s;if(t!==void 0&&t!=="-")try{s=await Cu(ju(e.io.cwd,t),"utf8")}catch(r){throw new m("unreadable_file",`${t} could not be read: ${r instanceof Error?r.message:String(r)}`)}else e.io.interactive&&e.out.note(`Reading ${n} from stdin; end it with Ctrl-D.`),s=await e.io.readStdin();if(s.trim()==="")throw g(`no ${n} was given`,t===void 0?"pass --file <path>, or pipe it in":void 0);return s}function he(e,t){try{return JSON.parse(e)}catch(n){throw g(`${t} is not JSON: ${n instanceof Error?n.message:String(n)}`)}}async function fe(e,t){e.io.interactive&&e.out.note(`Paste ${t}, then Ctrl-D (it is read from stdin, never from the command line):`);let n=(await e.io.readStdin()).trim();if(n==="")throw g(`no ${t} was piped in`,"pipe it in: palbase \u2026 < file");return n}function Rt(e){if(e.flags.enable===!0&&e.flags.disable===!0)throw g("--enable and --disable are one or the other");if(e.flags.enable===!0)return!0;if(e.flags.disable===!0)return!1}function Tt(e,t){if(e!==void 0){if(e==="yes"||e==="true")return!0;if(e==="no"||e==="false")return!1;throw g(`--${t} is yes or no`)}}function me(e){return e===void 0?void 0:(Array.isArray(e)?e:[e]).flatMap(n=>String(n).split(",")).map(n=>n.trim()).filter(n=>n!=="")}function Me(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw g(`--${t} is a whole number`);return n}var ln={enable:{type:"boolean",description:"turn it on"},disable:{type:"boolean",description:"turn it off"}};async function U(e,t,n){try{await n(),e.done(t)}catch(s){if(!(s instanceof Y))throw s;e.left(`${t}: refused \u2014 ${s.message}`)}}function Q(e,t){return e.filter(n=>n.key.startsWith(t)).map(n=>({...n,name:n.key.slice(t.length)}))}function Ws(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>!(t==null||t===""||Array.isArray(t)&&t.length===0)))}import{randomBytes as Ia}from"node:crypto";import{chmod as Ku,lstat as Gu,readFile as Ea,rename as Bu,writeFile as Ca}from"node:fs/promises";var u0=1024*1024;var g0=8*1024;var _0=16*1024,S0=10*6e4;var xu=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Mu=/^(PALBASE_|NODE_|BUN_|NPM_|LD_|DYLD_|GIT_|SSH_)|^(PATH|HOME|SHELL|USER|TMPDIR|TMP|TEMP|PWD|IFS|ENV|BASH_ENV|SSL_CERT_FILE|SSL_CERT_DIR|CURL_CA_BUNDLE|REQUESTS_CA_BUNDLE|NO_PROXY|ALL_PROXY|HTTP_PROXY|HTTPS_PROXY|PG\w*)$/i;function Fs(e){return!xu.test(e)||Mu.test(e)||/_PROXY$/i.test(e)}var Uu=/^(PORT|PALBASE_(URL|PROJECT_ID|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function Vs(e){return Uu.test(e)}import"node:os";import{join as Ju,resolve as zu}from"node:path";var a_=10080*60*1e3,Ta="# >>> palbase (palbase env:pull writes this block; edit outside it)",Hs="# <<< palbase";async function Wu(e,t){let n=await on(e.studio,t.env_ref),{keys:s}=await e.studio.get(`/environments/${encodeURIComponent(t.env_ref)}/keys`),r=s.find(a=>a.id===n.id);return r&&!r.revoked_at?n:on(e.studio,t.env_ref,!0)}async function Nn(e,t){let n=await e.studio.get(`/environments/${t.env_ref}`);return{url:ct(e.cloud,t.env_ref),projectId:n.platform_id,key:await Wu(e,t)}}function qn(e){let t=e.flags.secrets??"development";if(t!=="development"&&t!=="production")throw g("--secrets is development or production");return t}function xn(e,t){return t==="production"?`the app reads ${e.project_name} \u203A ${e.env_name}'s production secrets here`:void 0}var Mn={type:"string",value:"set",description:"the project's secrets the app reads at boot: development (default) or production"},ja={name:"env:pull",summary:"write what the backend needs to run here into .env.local: the environment's address and your own key \u2014 no secret's value",usage:"[--file <path>] [--secrets development|production]",details:["The app reads the project's secrets at boot (PALBASE_SECRETS); their names are listed as comments: set one in the file to override it.","The key is yours (CLI \u2014 you (this computer)), good for 30 days, made again by env:pull and run; palbase logout revokes it.","Only the block between the palbase markers is written; the rest of the file stays as it is."],flags:{file:{type:"string",value:"path",description:"the file (default .env.local)"},secrets:Mn},scope:"environment",async run(e){let t=await h(e),n=qn(e);(t.env_slug===Pe||n==="production")&&await b(e,t,`write a key of ${t.env_slug===Pe?"the production environment":`${t.env_slug}`} into a file on this computer`,xn(t,n));let s=await Nn(e,t),r=await Ae(e.studio,t.env_ref,"GET",`/secrets/${n}`).then(y=>(y.body.secrets??y.body.items??[]).map(v=>v.name).filter(v=>typeof v=="string"&&!Fs(v)).sort(),()=>[]),a=zu(e.io.cwd,e.flags.file??".env.local"),i=[Ta,`# ${Xs(x(t))} \u2014 ${new Date(e.io.now()).toISOString()}`,`# PALBASE_SECRET_KEY is your own: ${Xs(s.key.label)}, good until ${Xs(s.key.expires_at)}.`,`PALBASE_URL=${s.url}`,`PALBASE_PROJECT_ID=${s.projectId}`,`PALBASE_SECRET_KEY=${s.key.key}`,`PALBASE_SECRETS=${n}`,...r.length>0?[`# The ${n} secrets, read at boot \u2014 set one here to override it:`,...r.map(y=>`# ${y}=`)]:[],Hs].join(`
20
+ `),o="";try{if((await Gu(a)).isSymbolicLink())throw new m("symlink",`${a} is a link: env:pull writes the key into a file of its own`,{hint:"remove the link, or pass --file <path>"});o=await Ea(a,"utf8")}catch(y){if(y.code!=="ENOENT")throw y}let l=o.indexOf(Ta),p=o.indexOf(Hs),u=l>=0&&p>l?`${o.slice(0,l)}${i}${o.slice(p+Hs.length)}`:`${o}${o===""||o.endsWith(`
21
21
  `)?"":`
22
22
  `}${i}
23
- `;if((await e.io.run("git",["ls-files","--error-unmatch","--",a],{cwd:e.io.cwd})).code===0)throw new m("tracked",`${a} is tracked by git: the key would go into a commit with it`,{hint:`git rm --cached ${a}, then run this again \u2014 or pass --file <path>`});let p=`${a}.${process.pid}.${Date.now()}.tmp`;await Ca(p,u,{mode:384,flag:"wx"}),await Ku(p,384),await Bu(p,a);let f=await Fu(e,a);e.out.result({file:a,secrets:n,secret_names:r,key:{id:s.key.id,expires_at:s.key.expires_at},gitignore:f},()=>{e.out.line(`Wrote ${a} for ${M(t)}: the address, your own key, and the ${n} secrets' names (${r.length}).`),f==="added"&&e.out.line("Added it to .gitignore."),f==="not-a-repo"&&e.out.warn("this directory is not in a git repository: keep the file out of any you put it in")})}};function Xs(e){return e.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g," ")}async function Fu(e,t){let n=await e.io.run("git",["rev-parse","--show-toplevel"],{cwd:e.io.cwd});if(n.code!==0)return"not-a-repo";if((await e.io.run("git",["check-ignore","-q",t],{cwd:e.io.cwd})).code===0)return"ignored";let r=n.stdout.trim(),a=t.startsWith(`${r}/`)?t.slice(r.length+1):t,i=Ju(r,".gitignore"),o="";try{o=await Ea(i,"utf8")}catch(l){if(l.code!=="ENOENT")throw l}return await Ca(i,`${o}${o===""||o.endsWith(`
23
+ `;if((await e.io.run("git",["ls-files","--error-unmatch","--",a],{cwd:e.io.cwd})).code===0)throw new m("tracked",`${a} is tracked by git: the key would go into a commit with it`,{hint:`git rm --cached ${a}, then run this again \u2014 or pass --file <path>`});let d=`${a}.${process.pid}.${Date.now()}.tmp`;await Ca(d,u,{mode:384,flag:"wx"}),await Ku(d,384),await Bu(d,a);let f=await Fu(e,a);e.out.result({file:a,secrets:n,secret_names:r,key:{id:s.key.id,expires_at:s.key.expires_at},gitignore:f},()=>{e.out.line(`Wrote ${a} for ${x(t)}: the address, your own key, and the ${n} secrets' names (${r.length}).`),f==="added"&&e.out.line("Added it to .gitignore."),f==="not-a-repo"&&e.out.warn("this directory is not in a git repository: keep the file out of any you put it in")})}};function Xs(e){return e.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g," ")}async function Fu(e,t){let n=await e.io.run("git",["rev-parse","--show-toplevel"],{cwd:e.io.cwd});if(n.code!==0)return"not-a-repo";if((await e.io.run("git",["check-ignore","-q",t],{cwd:e.io.cwd})).code===0)return"ignored";let r=n.stdout.trim(),a=t.startsWith(`${r}/`)?t.slice(r.length+1):t,i=Ju(r,".gitignore"),o="";try{o=await Ea(i,"utf8")}catch(l){if(l.code!=="ENOENT")throw l}return await Ca(i,`${o}${o===""||o.endsWith(`
24
24
  `)?"":`
25
25
  `}# palbase env:pull: your own key
26
26
  /${a}
27
- `),"added"}var Oa={name:"run",summary:"run a command with the environment's address and your own key in its environment, nothing written to disk",usage:"[--secrets development|production] -- <command> [args\u2026]",details:["palbase run -- bun dev, palbase run -- npm test: the app reads the project's secrets at boot (PALBASE_SECRETS)."],args:{min:1,max:1e3},flags:{secrets:Un},scope:"environment",async run(e){let t=await h(e),n=qn(e);(t.env_slug===Pe||n==="production")&&await b(e,t,`run ${e.args[0]} with a key of ${t.env_slug}`,Mn(t,n));let s=await Nn(e,t);e.out.note(`${M(t)}: ${e.args.join(" ")}`);let r=await e.io.run(e.args[0],e.args.slice(1),{cwd:e.io.cwd,env:{PALBASE_URL:s.url,PALBASE_PROJECT_ID:s.projectId,PALBASE_SECRET_KEY:s.key.key,PALBASE_SECRETS:n},inherit:!0});if(r.code===127)throw new m("not_found",`${e.args[0]}: no such program`);return r.code}};var Vu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/,Da=20;async function Hu(e,t,n,s){if(n.length===0)return{};let r=n.map(i=>({email:`${i.toLowerCase()}.${s}@test.invalid`,password:Ia(18).toString("base64url")})),a=await c(e,t,"POST","/admin/test-users",{users:r,with_tokens:!0},R());if(a.users.length!==n.length)throw new m("unexpected_answer",`auth made ${a.users.length} test users of the ${n.length} asked for`);return Object.fromEntries(n.map((i,o)=>{let l=a.users[o];return[i,{id:l.user_id,email:l.email??r[o].email,password:r[o].password,...l.access_token?{accessToken:l.access_token}:{}}]}))}async function Xu(e,t,n){let s=[];for(let r of Object.values(n))try{await c(e,t,"DELETE",`/admin/test-users/${_(r.id)}`)}catch(a){a.status!==404&&s.push(r.id)}return s}async function Yu(e,t,n){if(e.out.note(`${t}'s test command, as the platform holds it: ${Zu(n)}`),e.global.yes)return;if(!e.interactive)throw new m("confirmation_needed",`palbase test would run ${t}'s test command on this computer \u2014 a setting on the platform, not code of this checkout`,{hint:"read it above, then confirm with -y"});let s=(await e.io.prompt("Run it on this computer? [y/N] ")).trim().toLowerCase();if(s!=="y"&&s!=="yes")throw new m("cancelled","nothing was run")}function Zu(e){return j(e).replace(/\p{Cf}/gu,t=>`\\u${t.codePointAt(0).toString(16).padStart(4,"0")}`)}function Ys(e){let t=[...new Set(me(e)??[])];for(let s of t)if(!Vu.test(s))throw g(`${JSON.stringify(s)} is no test user's name: letters, digits, _ and -, 64 at most`);let n=new Map;for(let s of t){let r=n.get(s.toLowerCase());if(r!==void 0)throw g(`${JSON.stringify(r)} and ${JSON.stringify(s)} are one test user (its address is in lower case): name it once`);n.set(s.toLowerCase(),s)}if(t.length>Da)throw g(`--users names ${t.length}; a run makes ${Da} at most`);return t}var La={SIGHUP:1,SIGINT:2,SIGTERM:15};function Qu(){let e,t=Object.keys(La).map(n=>[n,()=>{e??=n}]);for(let[n,s]of t)process.on(n,s);return{got:()=>e,release:()=>{for(let[n,s]of t)process.off(n,s)}}}var Na={name:"test",summary:"run the backend's tests as the platform does before a build goes out: test users made for the run and deleted after, PALBASE_TEST_* in the command's environment",usage:"[--machine <name>] [--users <names>] [--base-url <url>] [--secrets development|production] [-- <command> [args\u2026]]",details:["palbase test --machine backend --base-url http://localhost:8080: the machine's own test command and test users, as the platform runs them before each build of it rolls out.",'palbase test --users alice,bob --base-url http://localhost:8080 -- bun test: api.signInAs("alice") acts as a test user made for this run.',"The command gets what palbase run gives it, and PALBASE_TEST_AUTH_URL (the environment's address), PALBASE_TEST_IDENTITIES (the run's test users, each with its token) and, with --base-url, PALBASE_TEST_BASE_URL (where the app under test answers; without it HTTP tests skip themselves).","The test users are deleted when the command ends, however it ends \u2014 Ctrl-C too: the command is stopped, they are deleted, and palbase test ends as the signal would have."],args:{min:0,max:1e3},flags:{machine:{type:"string",value:"name",description:"a machine whose test command (when no command is given) and test users (when --users is not) are run"},users:{type:"string",value:"names",description:"the test users to make for the run, comma-separated"},"base-url":{type:"string",value:"url",description:"where the app under test answers (http://localhost:8080)"},secrets:Un},scope:"environment",async run(e){let t=Ys(e.flags.users),n=e.flags["base-url"];if(n!==void 0&&!/^https?:\/\/[^\s/]+/.test(n))throw g(`--base-url is an http(s) address, not ${JSON.stringify(n)}`);let s=e.flags.machine;if(e.args.length===0&&s===void 0)throw g("which tests? give their command after --, or --machine <name> to run its own","palbase test -- bun test");let r=await h(e),a=e.args.length>0?{cmd:e.args[0],args:e.args.slice(1)}:void 0;if(s!==void 0){let y=await c(e,r,"GET",`/machines/${_(s)}`);if(e.flags.users===void 0&&(t=y.test_users??[]),a===void 0){if(!y.test_command)throw g(`the machine ${s} runs no tests`,`give their command after --, or set it: palbase machines:update ${s} --test-command "bun test"`);await Yu(e,s,y.test_command),a={cmd:"sh",args:["-c",y.test_command]}}}let i=a,o=qn(e);(r.env_slug===Pe||o==="production")&&await b(e,r,`run ${i.cmd} with a key of ${r.env_slug}${t.length?` and ${t.length} test user(s) made there`:""}`,Mn(r,o));let l=await Nn(e,r),d=Ia(4).toString("hex"),u=t.length>0?Qu():void 0,p=0;try{let y=await Hu(e,r,t,d);t.length>0&&e.out.note(`${M(r)}: test users ${t.join(", ")} made for this run (${d})`);try{if(u?.got()===void 0){let v=await e.io.run(i.cmd,i.args,{cwd:e.io.cwd,env:{PALBASE_URL:l.url,PALBASE_PROJECT_ID:l.projectId,PALBASE_SECRET_KEY:l.key.key,PALBASE_SECRETS:o,PALBASE_TEST_AUTH_URL:l.url,PALBASE_TEST_IDENTITIES:JSON.stringify(y),...n!==void 0?{PALBASE_TEST_BASE_URL:n}:{}},inherit:!0});if(v.code===127)throw new m("not_found",`${i.cmd}: no such program`);p=v.code}}finally{if(t.length>0){let v=await Xu(e,r,y);v.length>0?e.out.warn(`test users not deleted: ${v.join(", ")} \u2014 palbase auth:test-users:delete <id> deletes one`):e.out.note(`the run's ${t.length} test user(s) deleted`)}}}finally{u?.release()}let f=u?.got();return f===void 0?p:128+La[f]}};var qa={name:"auth:settings:get",summary:"the environment's auth settings: passwords, email confirmation, sessions, tokens, passkeys, risk",scope:"environment",async run(e){let t=await h(e),{body:n}=await ce(e,t,"/admin/auth-settings");e.out.result(n,()=>Pt(e,n))}},Ma={name:"auth:settings:set",summary:'change auth settings: key=value, a value read as JSON when it is JSON (12, true, ["https://\u2026"])',usage:"<key=value>\u2026",details:[`Text needs no quotes (site_url=https://shop.example); quote text that reads as JSON: from_name='"123"'.`,"The change is made on the revision read: one made beside it in the meantime (Studio, another terminal) is refused, not overwritten.","palbase auth:settings:get lists the keys."],args:{min:1,max:50},scope:"environment",async run(e){let t=await h(e),n=et(e.args),{body:s,etag:r}=await ce(e,t,"/admin/auth-settings"),a=nt(s,n),i=ze(s,a);if(i.length===0)return We(e,"the auth settings",s);await b(e,t,`set the auth settings ${i.join(", ")}`);let o=await c(e,t,"PUT","/admin/auth-settings",Object.fromEntries(i.map(l=>[l,a[l]])),oe(r));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${le(s[l])} \u2192 ${le(o[l])}`)})}},Et=["google","apple","github","microsoft"],ec={google:"web_client_id",apple:"services_id",microsoft:"application_id",github:"oauth_client_id"},tc=new Set(["organizations","consumers","common"]),Ua={name:"auth:providers:list",summary:"the sign-in methods: email, phone and the social providers, whether each is on, their clients and stored secrets (by name)",scope:"environment",async run(e){let t=await h(e),[n,s]=await Promise.all([c(e,t,"GET","/admin/providers"),c(e,t,"GET","/admin/social-auth")]),r=[...n.providers.map(a=>({provider:a.id,on:a.enabled,detail:a.settings?`codes by ${a.settings.sender??"sms"}, ${a.settings.otp_ttl_seconds}s, ${a.settings.otp_max_attempts} tries, sign-up ${a.settings.allow_signups?"on":"off"}`:"",secrets:""})),...Et.map(a=>{let i=s.providers[a]??{enabled:!1,browser_clients:[],native_clients:[]},o=[...i.browser_clients.map(l=>`${l.key} (${l.platform})`),...i.native_clients.map(l=>`${l.key} (${l.platform}, native)`)];return{provider:a,on:i.enabled,detail:o.length===0?"no client":o.join(", "),secrets:s.credentials.filter(l=>l.provider===a).map(l=>`${l.key} ${l.configured?"set":"not set"}`).join(", ")}})];e.out.result({providers:n.providers,social:s.providers,credentials:s.credentials},()=>e.out.table(r,[{header:"provider",value:a=>a.provider},{header:"on",value:a=>a.on?"yes":"no"},{header:"clients / settings",value:a=>a.detail},{header:"secrets",value:a=>a.secrets}]))}},xa={name:"auth:providers:set",summary:"turn a sign-in provider on or off and set it up: its client, its secret (from stdin), phone codes",usage:"<provider> [--enable|--disable] [setting=value\u2026] [--client-id <id>] [--secret-stdin] \u2026",details:["email is always on. phone: --enable/--disable, and its code settings as setting=value (allow_signups, otp_ttl_seconds, otp_max_attempts, sender, sms_second_factor).","google, apple, github, microsoft: --client-id sets the browser client --client names (default web), made when the provider has none of that name.","--secret-stdin reads the client's secret from stdin (Apple: the .p8 signing key, with --team-id and --key-id) and keeps it under --credential (default: the provider's name). It is never taken from the command line and never printed.",`--clients-file replaces the provider's clients with the file's {"browser_clients": [\u2026], "native_clients": [\u2026]} (native apps, several clients).`],args:{min:1,max:20},flags:{...ln,client:{type:"string",value:"key",description:"the browser client --client-id, --return-uri and the secret go to (default web)"},"client-id":{type:"string",value:"id",description:"the provider's id of your app (Google web client id, Apple services id, Microsoft application id, GitHub client id)"},"return-uri":{type:"string",multiple:!0,value:"url",description:"where your app is sent back after sign-in (repeat it; replaces the list)"},application:{type:"string",value:"key",description:"the application the client is for (default app)"},variant:{type:"string",value:"name",description:"the build variant the client is for (default release)"},directory:{type:"string",value:"tenant",description:"microsoft: a tenant id, organizations, consumers or common (default common)"},"secret-stdin":{type:"boolean",description:"read the client's secret (Apple: the .p8 key) from stdin"},credential:{type:"string",value:"key",description:"the name the secret is kept under (default: the provider's)"},"team-id":{type:"string",value:"id",description:"apple: your team's id, with --secret-stdin"},"key-id":{type:"string",value:"id",description:"apple: the signing key's id, with --secret-stdin"},"clients-file":{type:"string",value:"path",description:"replace the provider's clients with the file's (JSON)"}},scope:"environment",async run(e){let t=e.args[0],n=await h(e);if(t==="email")throw new m("always_on","email sign-in is always on, and has no settings of its own here",{hint:"its policy is in palbase auth:settings:set"});if(t==="phone")return nc(e,n,e.args.slice(1));if(!Et.includes(t))throw g(`no provider ${JSON.stringify(t)}`,`the providers: email, phone, ${Et.join(", ")}`);if(e.args.length>1)throw g(`${t} takes flags, not settings: palbase help auth:providers:set`);await rc(e,n,t)}};async function nc(e,t,n){let s=Rt(e),r=et(n);if(s===void 0&&r.length===0)throw g("nothing to change: --enable, --disable or setting=value","palbase help auth:providers:set");let a=await c(e,t,"GET","/admin/providers/phone/settings"),i=nt(a,r),o=ze(a,i);if(s===void 0&&o.length===0)return We(e,"the phone settings",a);await b(e,t,`phone sign-in: ${[...s===void 0?[]:[s?"turn it on":"turn it off"],...o.map(u=>`set ${u}`)].join(", ")}`);let l=o.length>0?await c(e,t,"PUT","/admin/providers/phone/settings",i):a,d=s===void 0?void 0:await c(e,t,"POST","/admin/providers/phone",{enabled:s});e.out.result({provider:"phone",...d?{enabled:d.enabled}:{},settings:l},()=>{d&&e.out.line(`phone sign-in is ${d.enabled?"on":"off"}.`);for(let u of o)e.out.line(`${u}: ${le(a[u])} \u2192 ${le(l[u])}`)})}function sc(e){if(tc.has(e))return{mode:e};if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return{mode:"tenant",tenant_id:e};throw g("--directory is a tenant id, organizations, consumers or common")}async function rc(e,t,n){let s=e.flags,r=Rt(e),a=await ce(e,t,"/admin/social-auth"),i=a.etag,o=a.body.providers[n]??{enabled:!1,browser_clients:[],native_clients:[]},l={},d=[],u=s.credential??n,p;if(s["secret-stdin"]===!0){if(n==="apple"&&(s["team-id"]===void 0||s["key-id"]===void 0))throw g("Apple's signing key goes with --team-id and --key-id");p=await fe(e,n==="apple"?"Apple's .p8 signing key":`${n}'s client secret`),d.push(`keep its secret as ${u}`)}let f=["client","client-id","return-uri","application","variant","directory","credential"].filter(A=>s[A]!==void 0);if(s["clients-file"]!==void 0){if(f.length>0)throw g(`--clients-file and --${f[0]} are one or the other`);let A=he(await ve(e,s["clients-file"],"the clients"),"the clients file");if(typeof A!="object"||A===null||A.browser_clients===void 0&&A.native_clients===void 0)throw g('the clients file is {"browser_clients": [\u2026], "native_clients": [\u2026]}');l.browser_clients=A.browser_clients??o.browser_clients,l.native_clients=A.native_clients??o.native_clients,d.push(`set its clients from ${s["clients-file"]}`)}else if(f.length>0||p!==void 0){let A=s.client??"web",P=[...o.browser_clients],S=P.findIndex($=>$.key===A),C=p!==void 0&&f.every($=>$==="credential");if(S>=0||!C){let $=S>=0?{...P[S]}:{key:A,enabled:!0,application_key:"app",platform:"web",variant:"release",return_uris:[]},K=ec[n];if(s["client-id"]!==void 0&&($[K]=s["client-id"]),!$[K])throw g(`the ${n} client ${A} is new: give it --client-id`);if(s["return-uri"]!==void 0&&($.return_uris=s["return-uri"]),s.application!==void 0&&($.application_key=s.application),s.variant!==void 0&&($.variant=s.variant),n==="microsoft")$.directory=s.directory!==void 0?sc(s.directory):$.directory??{mode:"common"};else if(s.directory!==void 0)throw g("--directory is a Microsoft client's");(p!==void 0||s.credential!==void 0||!$.credential_ref)&&($.credential_ref=u),P[S>=0?S:P.length]=$,l.browser_clients=P,d.push(`${S>=0?"change":"add"} its browser client ${A}`)}}if(r!==void 0&&(l.enabled=r,d.push(r?"turn it on":"turn it off")),d.length===0)throw g("nothing to change: give a flag","palbase help auth:providers:set");await b(e,t,`${n} sign-in: ${d.join(", ")}`);let y=a.body;if(p!==void 0){let A=n==="apple"?{provider:n,kind:"apple_signing_key",team_id:s["team-id"],key_id:s["key-id"],private_key:p}:{provider:n,kind:"oauth_client_secret",client_secret:p};i=(await we(e,t,"PUT",`/admin/social-auth/credentials/${_(u)}`,A,oe(i))).etag??i}Object.keys(l).length>0?y=await c(e,t,"PATCH",`/admin/social-auth/providers/${_(n)}`,l,oe(i)):y=await c(e,t,"GET","/admin/social-auth");let v=y.providers[n],T=y.credentials.filter(A=>A.provider===n);e.out.result({provider:n,...v,credentials:T},()=>{e.out.line(`${n} sign-in is ${v.enabled?"on":"off"}: ${v.browser_clients.length} browser client(s), ${v.native_clients.length} native.`);for(let A of T)e.out.line(` secret ${A.key}: ${A.configured?"set":"not set"}`)})}var Ka={name:"auth:providers:secret:delete",summary:"delete a sign-in provider's stored secret by its name; one a client still uses is refused",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await h(e),{body:s,etag:r}=await ce(e,n,"/admin/social-auth");if(!s.credentials.some(a=>a.key===t))throw g(`the environment holds no provider secret ${JSON.stringify(t)}`,"palbase auth:providers:list names them");await b(e,n,`delete the provider secret ${t}`),await c(e,n,"DELETE",`/admin/social-auth/credentials/${_(t)}`,void 0,oe(r)),e.out.result({deleted:t},()=>e.out.line(`Deleted the provider secret ${t}.`))}},Ga={name:"auth:providers:disable",summary:"turn a sign-in provider off (phone, google, apple, github, microsoft); its clients and secrets stay",usage:"<provider>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await h(e);if(t==="email")throw new m("always_on","email sign-in cannot be turned off");if(t==="phone"){await b(e,n,"turn phone sign-in off");let a=await c(e,n,"POST","/admin/providers/phone",{enabled:!1});e.out.result(a,()=>e.out.line("phone sign-in is off."));return}if(!Et.includes(t))throw g(`no provider ${JSON.stringify(t)}`,`the providers: phone, ${Et.join(", ")}`);let{etag:s}=await ce(e,n,"/admin/social-auth");await b(e,n,`turn ${t} sign-in off`);let r=await c(e,n,"PATCH",`/admin/social-auth/providers/${_(t)}`,{enabled:!1},oe(s));e.out.result({provider:t,...r.providers[t]},()=>e.out.line(`${t} sign-in is off; its clients and secrets stay.`))}},Ba={name:"auth:roles:list",summary:"the environment's roles: their permissions, which one new users get, how many users hold each",scope:"environment",async run(e){let t=await h(e),{roles:n}=await c(e,t,"GET","/admin/roles");e.out.result({roles:n},()=>{n.length===0&&e.out.line("No role yet: palbase auth:roles:set <name> --permissions \u2026 makes one."),e.out.table(n,[{header:"role",value:s=>`${s.name}${s.is_default?" (default)":""}`},{header:"permissions",value:s=>s.permissions.join(", ")},{header:"users",value:s=>String(s.user_count??0)},{header:"description",value:s=>s.description}])})}},Ja={name:"auth:roles:set",summary:"make a role, or change one: its permissions (the list is replaced), description, whether new users get it",usage:"<name> [--permissions <a.b,c.d>] [--description <text>] [--default yes|no]",args:{min:1,max:1},flags:{permissions:{type:"string",multiple:!0,value:"list",description:'resource.action permissions, comma-separated or repeated; replaces the list ("" for none)'},description:{type:"string",value:"text",description:"what the role is for"},default:{type:"string",value:"yes|no",description:"whether new users get it (one role at most)"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],{roles:s}=await c(e,t,"GET","/admin/roles"),r=s.find(u=>u.name===n),a=me(e.flags.permissions),i=Tt(e.flags.default,"default");if(r&&a===void 0&&e.flags.description===void 0&&i===void 0)throw g("nothing to change: give a flag","palbase help auth:roles:set");let o={permissions:a??r?.permissions??[],description:e.flags.description??r?.description??"",is_default:i??r?.is_default??!1};await b(e,t,`${r?"change":"make"} the role ${n}${r?.user_count?` (${r.user_count} users hold it)`:""}`);let l=await c(e,t,"PUT",`/admin/roles/${_(n)}`,o),d=l.roles.find(u=>u.name===n);e.out.result(d??l,()=>e.out.line(`${r?"Changed":"Made"} the role ${n}: ${(d?.permissions??o.permissions).join(", ")||"no permission"}${d?.is_default?", given to new users":""}.`))}},za={name:"auth:roles:delete",summary:"delete a role; one users hold is deleted only with --force, and is taken from them",usage:"<name> [--force]",args:{min:1,max:1},flags:{force:{type:"boolean",description:"delete it although users hold it"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];await b(e,t,`delete the role ${n}${e.flags.force===!0?", taking it from the users who hold it":""}`),await c(e,t,"DELETE",`/admin/roles/${_(n)}${e.flags.force===!0?"?confirm=true":""}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the role ${n}.`))}},Wa={event:{type:"string",value:"event",description:"before.user.create, before.login, before.token.issue, \u2026 (the module lists them)"},url:{type:"string",value:"url",description:"the https endpoint auth calls and waits for"},"failure-mode":{type:"string",value:"deny|allow",description:"what auth does when the hook does not answer in time (default deny)"},timeout:{type:"string",value:"ms",description:"how long auth waits, 100\u20135000 ms (default 5000)"}},Fa={name:"auth:hooks:list",summary:"the blocking hooks: what auth calls, and waits for, before a sign-up, a sign-in, a token",scope:"environment",async run(e){let t=await h(e),{hooks:n}=await c(e,t,"GET","/admin/hooks");e.out.result({hooks:n},()=>{n.length===0&&e.out.line("No hook: palbase auth:hooks:create --event \u2026 --url \u2026 registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"event",value:s=>s.event},{header:"url",value:s=>s.url},{header:"on failure",value:s=>s.failure_mode},{header:"timeout",value:s=>`${s.timeout_ms} ms`},{header:"on",value:s=>s.enabled?"yes":"no"}])})}},Va={name:"auth:hooks:create",summary:"register a blocking hook; its signing secret is printed this once",usage:"--event <event> --url <url> [--failure-mode deny|allow] [--timeout <ms>] [--disable]",flags:{...Wa,disable:{type:"boolean",description:"register it turned off"}},scope:"environment",async run(e){let t=await h(e);if(e.flags.event===void 0||e.flags.url===void 0)throw g("a hook needs --event and --url");let n=Ws({event:e.flags.event,url:e.flags.url,failure_mode:e.flags["failure-mode"],timeout_ms:Ue(e.flags.timeout,"timeout"),...e.flags.disable===!0?{enabled:!1}:{}});await b(e,t,`register a hook on ${String(n.event)} calling ${String(n.url)}`);let s=await c(e,t,"POST","/admin/hooks",n,R());e.out.result(s,()=>{e.out.line(`Registered ${s.id}: ${s.event} \u2192 ${s.url}.`),s.secret&&e.out.line(`signing secret (shown this once): ${s.secret}`)})}},Ha={name:"auth:hooks:update",summary:"change a blocking hook: its event, endpoint, timeout, failure mode, on or off (its secret stays)",usage:"<id> [--event \u2026] [--url \u2026] [--failure-mode \u2026] [--timeout \u2026] [--enable|--disable]",args:{min:1,max:1},flags:{...Wa,...ln},scope:"environment",async run(e){let t=await h(e),{hooks:n}=await c(e,t,"GET","/admin/hooks"),s=n.find(o=>o.id===e.args[0]);if(!s)throw new m("no_such_hook",`no hook ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:hooks:list shows them"});let r={event:e.flags.event??s.event,url:e.flags.url??s.url,failure_mode:e.flags["failure-mode"]??s.failure_mode,timeout_ms:Ue(e.flags.timeout,"timeout")??s.timeout_ms,enabled:Rt(e)??s.enabled},a=ze(ac(s),r);if(a.length===0)throw g("nothing to change: give a flag","palbase help auth:hooks:update");await b(e,t,`change the hook ${s.id}: ${a.join(", ")}`);let i=await c(e,t,"PUT",`/admin/hooks/${_(s.id)}`,r);e.out.result(i,()=>e.out.line(`${i.id}: ${i.event} \u2192 ${i.url}, ${i.failure_mode} on failure, ${i.timeout_ms} ms, ${i.enabled?"on":"off"}.`))}};function ac(e){return{event:e.event,url:e.url,failure_mode:e.failure_mode,timeout_ms:e.timeout_ms,enabled:e.enabled}}var Xa={name:"auth:hooks:delete",summary:"delete a blocking hook; auth stops calling it",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`delete the hook ${e.args[0]}`),await c(e,t,"DELETE",`/admin/hooks/${_(e.args[0])}`),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the hook ${e.args[0]}.`))}},Ya={name:"auth:oauth-clients:list",summary:"the OAuth clients of the environment's OpenID provider (apps that sign in with your users' accounts)",scope:"environment",async run(e){let t=await h(e),{clients:n}=await c(e,t,"GET","/admin/oauth-clients");e.out.result({clients:n},()=>{n.length===0&&e.out.line("No OAuth client: palbase auth:oauth-clients:create <name> registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"client id",value:s=>s.client_id},{header:"name",value:s=>s.client_name},{header:"kind",value:s=>s.is_public?"public":s.token_endpoint_auth_method},{header:"grants",value:s=>s.grant_types.join(", ")},{header:"redirect uris",value:s=>s.redirect_uris.join(", ")}])})}},Za={name:"auth:oauth-clients:create",summary:"register an OAuth client; a confidential client's secret is printed this once",usage:"<name> [--public] [--redirect-uri <url>\u2026] [--grant <type>\u2026] [--scope <scope>\u2026] [--client-id <id>]",args:{min:1,max:1},flags:{public:{type:"boolean",description:"a public client (a CLI, a mobile app): it has no secret"},"redirect-uri":{type:"string",multiple:!0,value:"url",description:"where it may send the browser back (repeat it)"},grant:{type:"string",multiple:!0,value:"type",description:"authorization_code (default), refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code"},scope:{type:"string",multiple:!0,value:"scope",description:"what it may ask for (default openid, profile, email)"},"client-id":{type:"string",value:"id",description:"a client_id to use as it is (default: a random one)"}},scope:"environment",async run(e){let t=await h(e),n=Ws({client_name:e.args[0],client_id:e.flags["client-id"],is_public:e.flags.public===!0?!0:void 0,redirect_uris:e.flags["redirect-uri"],grant_types:me(e.flags.grant),scopes:me(e.flags.scope)});await b(e,t,`register the OAuth client ${e.args[0]}`);let s=await c(e,t,"POST","/admin/oauth-clients",n,R());e.out.result(s,()=>{e.out.line(`Registered ${s.client_name}: client_id ${s.client_id} (${s.id}).`),s.client_secret&&e.out.line(`client secret (shown this once): ${s.client_secret}`)})}},Qa={name:"auth:oauth-clients:delete",summary:"delete an OAuth client (by its id or its client_id)",usage:"<id|client-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),{clients:n}=await c(e,t,"GET","/admin/oauth-clients"),s=n.find(r=>r.id===e.args[0]||r.client_id===e.args[0]);if(!s)throw new m("no_such_client",`no OAuth client ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:oauth-clients:list shows them"});await b(e,t,`delete the OAuth client ${s.client_name} (${s.client_id})`),await c(e,t,"DELETE",`/admin/oauth-clients/${_(s.id)}`),e.out.result({deleted:s.id},()=>e.out.line(`Deleted the OAuth client ${s.client_name}.`))}};function ic(e){return{...e.client_id.startsWith("oidc_")?{}:{client_id:e.client_id},is_public:e.is_public,grant_types:[...e.grant_types].sort(),redirect_uris:e.redirect_uris,post_logout_redirect_uris:e.post_logout_redirect_uris,response_types:e.response_types,scopes:e.scopes,token_endpoint_auth_method:e.token_endpoint_auth_method,id_token_lifetime_secs:e.id_token_lifetime_secs,dpop_bound_access_tokens:e.dpop_bound_access_tokens,...e.jwks?{jwks:e.jwks}:{}}}var ei={name:"auth",async read(e,t){let[n,s,r,a,i,o]=await Promise.all([c(e,t,"GET","/admin/auth-settings"),c(e,t,"GET","/admin/providers"),c(e,t,"GET","/admin/social-auth"),c(e,t,"GET","/admin/roles"),c(e,t,"GET","/admin/hooks"),c(e,t,"GET","/admin/oauth-clients")]),l={};for(let[u,p]of Object.entries(n))l[`settings.${u}`]=p;let d=s.providers.find(u=>u.id==="phone");d&&(l["phone.enabled"]=d.enabled,d.settings&&(l["phone.settings"]=d.settings));for(let u of Et){let p=r.providers[u];p&&(l[`social.${u}`]={enabled:p.enabled,browser_clients:p.browser_clients,native_clients:p.native_clients})}for(let u of r.credentials)l[`secrets.${u.key}`]={provider:u.provider,kind:u.kind,value:u.configured?"set":"not set"};for(let u of a.roles)l[`roles.${u.name}`]={description:u.description,is_default:u.is_default,permissions:[...u.permissions].sort()};for(let u of i.hooks)l[`hooks.${u.event} ${u.url}`]={failure_mode:u.failure_mode,timeout_ms:u.timeout_ms,enabled:u.enabled};for(let u of o.clients)l[`oauth_clients.${u.client_name}`]=ic(u);return l},async copy(e,t,n,s){await oc(e,t,Q(n,"settings."),s);for(let a of Q(n,"phone."))a.name==="enabled"&&await x(s,`turn phone sign-in ${a.from?"on":"off"}`,async()=>{await c(e,t,"POST","/admin/providers/phone",{enabled:a.from===!0})}),a.name==="settings"&&a.from!==void 0&&await x(s,"set the phone code settings",async()=>{await c(e,t,"PUT","/admin/providers/phone/settings",a.from)});await lc(e,t,Q(n,"social."),Q(n,"secrets."),s);let r=Q(n,"roles.").sort((a,i)=>+(a.from?.is_default===!0)-+(i.from?.is_default===!0));for(let a of r)a.from===void 0?await x(s,`delete the role ${a.name}`,async()=>{await c(e,t,"DELETE",`/admin/roles/${_(a.name)}`)}):await x(s,`set the role ${a.name}`,async()=>{await c(e,t,"PUT",`/admin/roles/${_(a.name)}`,a.from)});await dc(e,t,Q(n,"hooks."),s),await pc(e,t,Q(n,"oauth_clients."),s)}};async function oc(e,t,n,s){let r=n.filter(i=>i.from!==void 0);for(let i of n.filter(o=>o.from===void 0))s.left(`auth settings ${i.name}: set in the target only, and a setting is not taken away (set it with auth:settings:set)`);if(r.length===0)return;let{etag:a}=await ce(e,t,"/admin/auth-settings");await x(s,`set the auth settings ${r.map(i=>i.name).join(", ")}`,async()=>{await c(e,t,"PUT","/admin/auth-settings",Object.fromEntries(r.map(i=>[i.name,i.from])),oe(a))})}async function lc(e,t,n,s,r){for(let l of s){let d=l.from;d===void 0?r.left(`secret ${l.name}: only in the target, left as it is (delete it with palbase auth:providers:secret:delete ${l.name})`):r.left(`secret ${l.name} (${d.provider}): secrets are not copied \u2014 set it with palbase auth:providers:set ${d.provider} --secret-stdin --credential ${l.name}`)}if(n.length===0)return;let a=await ce(e,t,"/admin/social-auth"),i=a.etag,o=new Set(a.body.credentials.filter(l=>l.configured).map(l=>l.key));for(let l of n){let d=l.from,u=[...new Set(d.browser_clients.map(p=>p.credential_ref).filter(p=>typeof p=="string"&&!o.has(p)))];if(u.length>0){r.left(`${l.name} sign-in: its clients use the secret ${u.join(", ")}, which the target does not hold \u2014 set it first (auth:providers:set ${l.name} --secret-stdin --credential ${u[0]})`);continue}await x(r,`set ${l.name} sign-in (${d.enabled?"on":"off"}, ${d.browser_clients.length+d.native_clients.length} client(s))`,async()=>{i=(await we(e,t,"PATCH",`/admin/social-auth/providers/${_(l.name)}`,d,oe(i))).etag??i})}}async function dc(e,t,n,s){if(n.length===0)return;let{hooks:r}=await c(e,t,"GET","/admin/hooks");for(let a of n){let i=a.name.indexOf(" "),o=a.name.slice(0,i),l=a.name.slice(i+1),d=r.find(u=>u.event===o&&u.url===l);if(a.from===void 0)d&&await x(s,`delete the hook ${o} \u2192 ${l}`,async()=>{await c(e,t,"DELETE",`/admin/hooks/${_(d.id)}`)});else if(d)await x(s,`change the hook ${o} \u2192 ${l}`,async()=>{await c(e,t,"PUT",`/admin/hooks/${_(d.id)}`,{event:o,url:l,...a.from})});else{let u;await x(s,`register the hook ${o} \u2192 ${l}`,async()=>{u=(await c(e,t,"POST","/admin/hooks",{event:o,url:l,...a.from},R())).secret}),u&&s.shown(`the hook ${o} \u2192 ${l} signs with`,u)}}}async function pc(e,t,n,s){if(n.length===0)return;let{clients:r}=await c(e,t,"GET","/admin/oauth-clients");for(let a of n){let i=r.find(l=>l.client_name===a.name),o=a.from;if(o===void 0)i&&await x(s,`delete the OAuth client ${a.name}`,async()=>{await c(e,t,"DELETE",`/admin/oauth-clients/${_(i.id)}`)});else if(i)if(!Re(o.is_public,i.is_public)||o.client_id!==void 0&&o.client_id!==i.client_id)s.left(`OAuth client ${a.name}: its client_id or kind (public or not) differs, which a client cannot change \u2014 delete it in the target and run the copy again`);else{let{client_id:l,is_public:d,...u}=o;await x(s,`change the OAuth client ${a.name}`,async()=>{await c(e,t,"PATCH",`/admin/oauth-clients/${_(i.id)}`,u)})}else{let l;await x(s,`register the OAuth client ${a.name}`,async()=>{l=await c(e,t,"POST","/admin/oauth-clients",{client_name:a.name,...o},R())}),l?.client_secret&&s.shown(`the OAuth client ${a.name} (${l.client_id}) has the secret`,l.client_secret)}}}var ti=["bool","number","string","object"];async function rt(e,t){return(await c(e,t,"GET","/flags/templates/capabilities")).publish_enabled}async function st(e,t,n){let s=await ce(e,t,"/flags/templates/draft");if(s.body.draft||!n)return{draft:s.body.draft,etag:s.etag};let r=await ce(e,t,"/flags/templates/active"),a=await we(e,t,"POST","/flags/templates/draft",{},oe(r.etag));return{draft:a.body.draft,etag:a.etag}}function ni(e){return{value_type:e.value_type,value:e.value,...e.description?{description:e.description}:{},...e.variants&&e.variants.length>0?{variants:e.variants}:{},...e.conditions&&e.conditions.length>0?{conditions:e.conditions}:{}}}function uc(e){return typeof e=="boolean"?"bool":typeof e=="number"?"number":typeof e=="string"?"string":"object"}function si(e,t){switch(t){case"bool":if(e==="true"||e==="false")return e==="true";throw g(`a bool flag's value is true or false, not ${JSON.stringify(e)}`);case"number":{let n=Number(e);if(e.trim()===""||!Number.isFinite(n))throw g(`a number flag's value is a number, not ${JSON.stringify(e)}`);return n}case"string":{let n=tt(e);return typeof n=="string"?n:e}default:{let n=tt(e);if(typeof n!="object"||n===null)throw g(`an object flag's value is a JSON object or list, not ${JSON.stringify(e)}`);return n}}}function cc(e,t){let n=e.lastIndexOf("=>");if(n<=0)throw g(`a condition is "<expression> => <value>", not ${JSON.stringify(e)}`,`--condition "client.platform == 'ios' => true"`);return{when:e.slice(0,n).trim(),value:si(e.slice(n+2).trim(),t)}}var ri={name:"flags:list",summary:"the environment's flags as they are live: type, default, targeting (and the draft's state when it publishes)",scope:"environment",async run(e){let t=await h(e),n=await rt(e,t),s,r,a;if(n){let[i,o]=await Promise.all([c(e,t,"GET","/flags/templates/active"),st(e,t,!1)]);s=Object.entries(i.definitions).map(([l,d])=>({key:l,...d})),r=i.version,a=o.draft}else s=await c(e,t,"GET","/flags/system");s.sort((i,o)=>i.key<o.key?-1:1),e.out.result({publishing:n,...r?{version:r}:{},flags:s,...a?{draft:{revision:a.revision,base_version:a.base_version}}:{}},()=>{n&&e.out.line(`Version ${r} is live${a?`; a draft (from ${a.base_version}) is in progress: palbase flags:publish shows and publishes it`:""}.`),s.length===0&&e.out.line("No flag yet: palbase flags:set <key> <value> makes one."),e.out.table(s,[{header:"flag",value:i=>i.key},{header:"type",value:i=>i.value_type},{header:"value",value:i=>JSON.stringify(i.value)},{header:"targeting",value:i=>i.conditions?.length?`${i.conditions.length} condition(s)`:""},{header:"description",value:i=>i.description??""}])})}},ai={name:"flags:set",summary:"define a flag or change its default and targeting (into the draft when the environment publishes)",usage:'<key> <value> [--type bool|number|string|object] [--condition "<expression> => <value>"\u2026] [--description <text>]',details:["The value is read as the flag's type: true/false, a number, text, or a JSON object; a new flag's type is the value's unless --type says it.","--condition replaces the targeting (first match wins, 8 at most); --clear-conditions takes it away; without either it stays.","An environment that publishes takes the change into its shared draft (palbase flags:publish makes it live); one that does not, live at once."],args:{min:2,max:2},flags:{type:{type:"string",value:"type",description:"bool, number, string or object"},condition:{type:"string",multiple:!0,value:"rule",description:'"<expression> => <value>" (repeat it, in order)'},"clear-conditions":{type:"boolean",description:"take its targeting away"},variants:{type:"string",value:"list",description:"a string flag's allowed values, comma-separated"},description:{type:"string",value:"text",description:"what the flag is for"}},scope:"environment",async run(e){let t=await h(e),[n,s]=e.args,r=e.flags.type;if(r!==void 0&&!ti.includes(r))throw g(`--type is one of ${ti.join(", ")}`);if(e.flags.condition!==void 0&&e.flags["clear-conditions"]===!0)throw g("--condition and --clear-conditions are one or the other");let a=await rt(e,t),i,o;a?(o=await st(e,t,!1),i=o.draft?.definitions[n]??(await c(e,t,"GET","/flags/templates/active")).definitions[n]):i=(await c(e,t,"GET","/flags/system")).find(p=>p.key===n);let l=r??i?.value_type??uc(tt(s)),d=e.flags["clear-conditions"]===!0?[]:e.flags.condition!==void 0?e.flags.condition.map(p=>cc(p,l)):i?.value_type===l?i.conditions??[]:[],u=ni({value_type:l,value:si(s,l),description:e.flags.description??i?.description,variants:me(e.flags.variants)??(i?.value_type===l?i.variants:void 0),conditions:d});if(await b(e,t,a?`put ${n} = ${le(u.value)} into the flags draft`:`set the flag ${n} = ${le(u.value)}, live at once`),a){o?.draft||(o=await st(e,t,!0));let p=await we(e,t,"PUT",`/flags/templates/draft/parameters/${_(n)}`,u,oe(o.etag));e.out.result({key:n,...u,draft:{revision:p.body.draft?.revision}},()=>e.out.line(`${n} = ${JSON.stringify(u.value)} is in the draft: palbase flags:publish makes it live.`))}else{let p=await c(e,t,"PUT",`/flags/system/${_(n)}`,u);e.out.result(p,()=>e.out.line(`${n} = ${JSON.stringify(p.value)} (${p.value_type}) is live.`))}}},ii={name:"flags:delete",summary:"delete a flag, with every user's own value of it (from the draft when the environment publishes)",usage:"<key>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s=await rt(e,t);if(await b(e,t,s?`take ${n} out of the flags draft`:`delete the flag ${n} and every user's value of it, live at once`),s){let r=await st(e,t,!0);await c(e,t,"DELETE",`/flags/templates/draft/parameters/${_(n)}`,void 0,oe(r.etag)),e.out.result({deleted:n,draft:!0},()=>e.out.line(`${n} is out of the draft: palbase flags:publish makes it so.`))}else await c(e,t,"DELETE",`/flags/system/${_(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the flag ${n}.`))}};function Zs(){return new m("publishing_off","this environment's flags are not published through drafts: flags:set and flags:delete change them live",{hint:"versions, publishing and rollback are for an environment that publishes"})}function mc(e,t){t.changes.length===0&&e.out.line("The draft changes nothing.");for(let n of t.changes){let s=n.override_count>0?` (${n.override_count} users' own values)`:"";n.kind==="added"?e.out.line(` + ${n.key} = ${JSON.stringify(n.after?.value)}`):n.kind==="removed"?e.out.line(` - ${n.key}${s}`):e.out.line(` ~ ${n.key}: ${n.changed_fields.join(", ")}${n.changed_fields.includes("value")?` \u2192 ${JSON.stringify(n.after?.value)}`:""}${s}`)}for(let n of t.issues)e.out.line(` ! ${n.key?`${n.key}: `:""}${n.message}`)}async function Qs(e,t,n,s){return c(e,t,"POST","/flags/templates/publish",s===""?{}:{note:s},{...oe(n),...R()})}var oi={name:"flags:publish",summary:"show the flags draft's changes against the live version, then publish it as the next version",usage:"[-m <note>]",flags:{note:{type:"string",short:"m",value:"note",description:"why: kept with the version"}},scope:"environment",async run(e){let t=await h(e);if(!await rt(e,t))throw Zs();if(!(await st(e,t,!1)).draft)throw new m("no_draft","there is no draft to publish",{hint:"palbase flags:set puts a change into one"});let s=await ce(e,t,"/flags/templates/draft/review");if(e.out.json||mc(e,s.body),!s.body.can_publish)throw new m("cannot_publish",`the draft cannot be published: ${s.body.issues.map(a=>a.message).join("; ")}`);if(s.body.changes.length===0){e.out.result({unchanged:!0,review:s.body},()=>e.out.line("Nothing to publish."));return}await b(e,t,`publish the flags draft (${s.body.changes.length} change(s)) as the version after ${s.body.active_version}`);let r=await Qs(e,t,s.etag,e.flags.note??"");e.out.result({...r,review:s.body},()=>e.out.line(r.unchanged?"The draft changed nothing; nothing was published.":`Published version ${r.template.version}.`))}},li={name:"flags:publishing:enable",summary:"turn publishing on for the environment's flags: changes go into a shared draft, live once published \u2014 for good",details:["What is live now becomes version 1. From then on flags:set and flags:delete change the draft, flags:publish makes it live as the next version, and flags:rollback brings an earlier one back. It cannot be turned off."],scope:"environment",async run(e){let t=await h(e);if(await rt(e,t)){e.out.result({publishing:!0,changed:!1},()=>e.out.line("The environment's flags are published through drafts already."));return}await b(e,t,"turn publishing on for the flags \u2014 what is live becomes version 1, and it cannot be turned off");let n=await c(e,t,"POST","/flags/templates/activation",{});e.out.result({publishing:!0,changed:!0,version:n.version},()=>e.out.line(`Publishing is on: what was live is version ${n.version}; flags:set changes the draft, flags:publish makes it live.`))}},di={name:"flags:versions",summary:"the published versions of the flags, newest first, and which one is live",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await h(e);if(!await rt(e,t))throw Zs();let n=Number(e.flags.limit??20);if(!Number.isInteger(n)||n<=0)throw g("--limit is a whole number");let s=await c(e,t,"GET",`/flags/templates/versions?limit=${n}`);e.out.result(s,()=>e.out.table(s.versions,[{header:"",value:r=>r.version===s.active_version?"*":""},{header:"version",value:r=>r.version},{header:"kind",value:r=>`${r.kind}${r.restored_from?` of ${r.restored_from}`:""}`},{header:"published",value:r=>r.created_at},{header:"by",value:r=>r.actor},{header:"note",value:r=>r.note}]))}},pi={name:"flags:rollback",summary:"make an earlier version of the flags live again (published as the next version)",usage:"<version> [-m <note>]",args:{min:1,max:1},flags:{note:{type:"string",short:"m",value:"note",description:"why (default: rollback to <version>)"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(!await rt(e,t))throw Zs();if((await st(e,t,!1)).draft)throw new m("draft_in_progress","a draft is in progress: publish it (flags:publish) before a rollback");let s=await ce(e,t,"/flags/templates/active");await b(e,t,`make version ${n} of the flags live again (now ${s.body.version})`);let r=await we(e,t,"POST",`/flags/templates/restore/${_(n)}`,{},oe(s.etag)),a=await Qs(e,t,r.etag,e.flags.note??`rollback to ${n}`);e.out.result(a,()=>e.out.line(a.unchanged?`Version ${n} is what is live already.`:`Version ${n} is live again, as version ${a.template.version}.`))}},ui={name:"flags",async read(e,t){let n=await c(e,t,"GET","/flags/system"),s={};for(let r of n)s[`flags.${r.key}`]=ni(r);return s},async copy(e,t,n,s){let r=Q(n,"flags.");if(r.length===0)return;if(!await rt(e,t)){for(let i of r)i.from===void 0?await x(s,`delete the flag ${i.name}`,async()=>{await c(e,t,"DELETE",`/flags/system/${_(i.name)}`)}):await x(s,`set the flag ${i.name}`,async()=>{await c(e,t,"PUT",`/flags/system/${_(i.name)}`,i.from)});return}if((await st(e,t,!1)).draft){s.left("flags: the target has a draft in progress \u2014 publish it (flags:publish) and copy again");return}let a=(await st(e,t,!0)).etag;for(let i of r)await x(s,`${i.from===void 0?"take":"put"} the flag ${i.name} ${i.from===void 0?"out of":"into"} the draft`,async()=>{let o=`/flags/templates/draft/parameters/${_(i.name)}`;a=(i.from===void 0?await we(e,t,"DELETE",o,void 0,oe(a)):await we(e,t,"PUT",o,i.from,oe(a))).etag??a});await x(s,"publish the flags draft",async()=>{await Qs(e,t,a,"config:copy")})}};var ci=["email","sms","verify","whatsapp","push"],er=["email","sms","whatsapp"],mi={name:"notify:providers:list",summary:"the environment's own providers, by channel and the order they are tried (never their credentials)",scope:"environment",async run(e){let t=await h(e),{providers:n}=await c(e,t,"GET","/notify/providers");e.out.result({providers:n},()=>{n.length===0&&e.out.line("No provider of the environment's own: email goes through the platform's sender; texts need one (notify:providers:set)."),e.out.table(n,[{header:"channel",value:s=>s.channel},{header:"provider",value:s=>s.provider},{header:"on",value:s=>s.active?"yes":"no"},{header:"priority",value:s=>String(s.priority)},{header:"credentials",value:()=>"set"}])})}},gi={name:"notify:providers:set",summary:"bring a provider for a channel: its credentials are piped in as JSON, checked as the sender will use them, never shown",usage:"<channel> <provider> [--priority <n>] [--enable|--disable] < credentials.json",details:["email: acs {connection_string, from_email}, ses {region, access_key_id, secret_access_key, from_domain}, sendgrid {api_key, from_domain}, smtp {host, port, username, password, from_email, use_starttls}.","sms: twilio {account_sid, auth_token, from_number}, vonage {api_key, api_secret, from}, acs {connection_string, from_number}. verify: twilio_verify {account_sid, auth_token, service_sid}.","whatsapp: meta {phone_number_id, access_token, api_version, app_secret, verify_token}. push: fcm (the service account JSON), apns {private_key, team_id, key_id, bundle_id, is_production}, expo {access_token}.","The credentials are read from stdin only: never from the command line, where the shell would keep them."],args:{min:2,max:2},flags:{priority:{type:"string",value:"n",description:"lower is tried first"},...ln},scope:"environment",async run(e){let[t,n]=e.args;if(!ci.includes(t))throw g(`the channels: ${ci.join(", ")}`);let s=await h(e),r=he(await fe(e,`${n}'s credentials (JSON)`),"the credentials");if(typeof r!="object"||r===null||Array.isArray(r))throw g("the credentials are a JSON object");let a=Rt(e),i=Ue(e.flags.priority,"priority");await b(e,s,`send ${t} through ${n}`);let o=await c(e,s,"PUT",`/notify/providers/${_(t)}/${_(n)}`,{credentials:r,...a!==void 0?{active:a}:{},...i!==void 0?{priority:i}:{}},R());e.out.result(o,()=>e.out.line(`${o.channel} goes through ${o.provider} (${o.active?"on":"off"}, priority ${o.priority}); its credentials are kept sealed.`))}},hi={name:"notify:providers:remove",summary:"forget a provider and its credentials",usage:"<channel> <provider>",args:{min:2,max:2},scope:"environment",async run(e){let[t,n]=e.args,s=await h(e);await b(e,s,`forget the ${t} provider ${n} and its credentials`),await c(e,s,"DELETE",`/notify/providers/${_(t)}/${_(n)}`,void 0,R()),e.out.result({removed:{channel:t,provider:n}},()=>e.out.line(`${t} no longer goes through ${n}.`))}},nr={channel:{type:"string",value:"channel",description:"email (default), sms or whatsapp"},locale:{type:"string",value:"locale",description:"the template's locale: en, tr, pt-BR"}};function dn(e){let t=e.flags.channel??"email";if(!er.includes(t))throw g(`--channel is one of ${er.join(", ")}`);return t}function sr(e,t){let n=e.flags.locale;if(n===void 0)throw g("which locale? pass --locale <locale>");return`/notify/templates/${dn(e)}/${_(t)}/${_(n)}`}function gc(e,t){if(e==="sms"){let s=hc(t);return typeof s=="object"&&s!==null&&typeof s.body=="string"?{body:s.body}:{body:t.replace(/\n$/,"")}}let n=he(t,`the ${e} template`);if(e==="email"){for(let s of["subject","html_body","text_body"])if(typeof n[s]!="string")throw g(`an email template is {"subject", "html_body", "text_body"}: ${s} is missing`);return{subject:n.subject,html_body:n.html_body,text_body:n.text_body}}if(typeof n.name!="string"||typeof n.language!="string")throw g('a WhatsApp template is {"name", "language", "variables", "code_button"}: the template approved on your Meta account');return{name:n.name,language:n.language,...n.variables!==void 0?{variables:n.variables}:{},...n.code_button!==void 0?{code_button:n.code_button}:{}}}function hc(e){try{return JSON.parse(e)}catch{return e}}var fi={name:"notify:templates:list",summary:"the templates messages render from: the platform's defaults and the ones the environment overrides",usage:"[--channel email|sms|whatsapp]",flags:{channel:{type:"string",value:"channel",description:"only this channel's (default: all three)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.channel===void 0?er:[dn(e)],s=await Promise.all(n.map(a=>c(e,t,"GET",`/notify/templates/${a}`))),r=n.flatMap((a,i)=>s[i].templates.map(o=>({channel:a,...o})));e.out.result({templates:r},()=>e.out.table(r,[{header:"channel",value:a=>a.channel},{header:"slug",value:a=>a.slug},{header:"locale",value:a=>a.locale},{header:"yours",value:a=>"customized"in a?a.customized?"yes":"default":"yes"},{header:"subject / text / template",value:a=>"subject"in a?a.subject:"body"in a?a.body.split(`
27
+ `),"added"}var Oa={name:"run",summary:"run a command with the environment's address and your own key in its environment, nothing written to disk",usage:"[--secrets development|production] -- <command> [args\u2026]",details:["palbase run -- bun dev, palbase run -- npm test: the app reads the project's secrets at boot (PALBASE_SECRETS)."],args:{min:1,max:1e3},flags:{secrets:Mn},scope:"environment",async run(e){let t=await h(e),n=qn(e);(t.env_slug===Pe||n==="production")&&await b(e,t,`run ${e.args[0]} with a key of ${t.env_slug}`,xn(t,n));let s=await Nn(e,t);e.out.note(`${x(t)}: ${e.args.join(" ")}`);let r=await e.io.run(e.args[0],e.args.slice(1),{cwd:e.io.cwd,env:{PALBASE_URL:s.url,PALBASE_PROJECT_ID:s.projectId,PALBASE_SECRET_KEY:s.key.key,PALBASE_SECRETS:n},inherit:!0});if(r.code===127)throw new m("not_found",`${e.args[0]}: no such program`);return r.code}};var Vu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/,Da=20;async function Hu(e,t,n,s){if(n.length===0)return{};let r=n.map(i=>({email:`${i.toLowerCase()}.${s}@test.invalid`,password:Ia(18).toString("base64url")})),a=await c(e,t,"POST","/admin/test-users",{users:r,with_tokens:!0},R());if(a.users.length!==n.length)throw new m("unexpected_answer",`auth made ${a.users.length} test users of the ${n.length} asked for`);return Object.fromEntries(n.map((i,o)=>{let l=a.users[o];return[i,{id:l.user_id,email:l.email??r[o].email,password:r[o].password,...l.access_token?{accessToken:l.access_token}:{}}]}))}async function Xu(e,t,n){let s=[];for(let r of Object.values(n))try{await c(e,t,"DELETE",`/admin/test-users/${_(r.id)}`)}catch(a){a.status!==404&&s.push(r.id)}return s}async function Yu(e,t,n){if(e.out.note(`${t}'s test command, as the platform holds it: ${Zu(n)}`),e.global.yes)return;if(!e.interactive)throw new m("confirmation_needed",`palbase test would run ${t}'s test command on this computer \u2014 a setting on the platform, not code of this checkout`,{hint:"read it above, then confirm with -y"});let s=(await e.io.prompt("Run it on this computer? [y/N] ")).trim().toLowerCase();if(s!=="y"&&s!=="yes")throw new m("cancelled","nothing was run")}function Zu(e){return j(e).replace(/\p{Cf}/gu,t=>`\\u${t.codePointAt(0).toString(16).padStart(4,"0")}`)}function Ys(e){let t=[...new Set(me(e)??[])];for(let s of t)if(!Vu.test(s))throw g(`${JSON.stringify(s)} is no test user's name: letters, digits, _ and -, 64 at most`);let n=new Map;for(let s of t){let r=n.get(s.toLowerCase());if(r!==void 0)throw g(`${JSON.stringify(r)} and ${JSON.stringify(s)} are one test user (its address is in lower case): name it once`);n.set(s.toLowerCase(),s)}if(t.length>Da)throw g(`--users names ${t.length}; a run makes ${Da} at most`);return t}var La={SIGHUP:1,SIGINT:2,SIGTERM:15};function Qu(){let e,t=Object.keys(La).map(n=>[n,()=>{e??=n}]);for(let[n,s]of t)process.on(n,s);return{got:()=>e,release:()=>{for(let[n,s]of t)process.off(n,s)}}}var Na={name:"test",summary:"run the backend's tests as the platform does before a build goes out: test users made for the run and deleted after, PALBASE_TEST_* in the command's environment",usage:"[--machine <name>] [--users <names>] [--base-url <url>] [--secrets development|production] [-- <command> [args\u2026]]",details:["palbase test --machine backend --base-url http://localhost:8080: the machine's own test command and test users, as the platform runs them before each build of it rolls out.",'palbase test --users alice,bob --base-url http://localhost:8080 -- bun test: api.signInAs("alice") acts as a test user made for this run.',"The command gets what palbase run gives it, and PALBASE_TEST_AUTH_URL (the environment's address), PALBASE_TEST_IDENTITIES (the run's test users, each with its token) and, with --base-url, PALBASE_TEST_BASE_URL (where the app under test answers; without it HTTP tests skip themselves).","The test users are deleted when the command ends, however it ends \u2014 Ctrl-C too: the command is stopped, they are deleted, and palbase test ends as the signal would have."],args:{min:0,max:1e3},flags:{machine:{type:"string",value:"name",description:"a machine whose test command (when no command is given) and test users (when --users is not) are run"},users:{type:"string",value:"names",description:"the test users to make for the run, comma-separated"},"base-url":{type:"string",value:"url",description:"where the app under test answers (http://localhost:8080)"},secrets:Mn},scope:"environment",async run(e){let t=Ys(e.flags.users),n=e.flags["base-url"];if(n!==void 0&&!/^https?:\/\/[^\s/]+/.test(n))throw g(`--base-url is an http(s) address, not ${JSON.stringify(n)}`);let s=e.flags.machine;if(e.args.length===0&&s===void 0)throw g("which tests? give their command after --, or --machine <name> to run its own","palbase test -- bun test");let r=await h(e),a=e.args.length>0?{cmd:e.args[0],args:e.args.slice(1)}:void 0;if(s!==void 0){let y=await c(e,r,"GET",`/machines/${_(s)}`);if(e.flags.users===void 0&&(t=y.test_users??[]),a===void 0){if(!y.test_command)throw g(`the machine ${s} runs no tests`,`give their command after --, or set it: palbase machines:update ${s} --test-command "bun test"`);await Yu(e,s,y.test_command),a={cmd:"sh",args:["-c",y.test_command]}}}let i=a,o=qn(e);(r.env_slug===Pe||o==="production")&&await b(e,r,`run ${i.cmd} with a key of ${r.env_slug}${t.length?` and ${t.length} test user(s) made there`:""}`,xn(r,o));let l=await Nn(e,r),p=Ia(4).toString("hex"),u=t.length>0?Qu():void 0,d=0;try{let y=await Hu(e,r,t,p);t.length>0&&e.out.note(`${x(r)}: test users ${t.join(", ")} made for this run (${p})`);try{if(u?.got()===void 0){let v=await e.io.run(i.cmd,i.args,{cwd:e.io.cwd,env:{PALBASE_URL:l.url,PALBASE_PROJECT_ID:l.projectId,PALBASE_SECRET_KEY:l.key.key,PALBASE_SECRETS:o,PALBASE_TEST_AUTH_URL:l.url,PALBASE_TEST_IDENTITIES:JSON.stringify(y),...n!==void 0?{PALBASE_TEST_BASE_URL:n}:{}},inherit:!0});if(v.code===127)throw new m("not_found",`${i.cmd}: no such program`);d=v.code}}finally{if(t.length>0){let v=await Xu(e,r,y);v.length>0?e.out.warn(`test users not deleted: ${v.join(", ")} \u2014 palbase auth:test-users:delete <id> deletes one`):e.out.note(`the run's ${t.length} test user(s) deleted`)}}}finally{u?.release()}let f=u?.got();return f===void 0?d:128+La[f]}};var qa={name:"auth:settings:get",summary:"the environment's auth settings: passwords, email confirmation, sessions, tokens, passkeys, risk",scope:"environment",async run(e){let t=await h(e),{body:n}=await ce(e,t,"/admin/auth-settings");e.out.result(n,()=>Pt(e,n))}},xa={name:"auth:settings:set",summary:'change auth settings: key=value, a value read as JSON when it is JSON (12, true, ["https://\u2026"])',usage:"<key=value>\u2026",details:[`Text needs no quotes (site_url=https://shop.example); quote text that reads as JSON: from_name='"123"'.`,"The change is made on the revision read: one made beside it in the meantime (Studio, another terminal) is refused, not overwritten.","palbase auth:settings:get lists the keys."],args:{min:1,max:50},scope:"environment",async run(e){let t=await h(e),n=et(e.args),{body:s,etag:r}=await ce(e,t,"/admin/auth-settings"),a=nt(s,n),i=ze(s,a);if(i.length===0)return We(e,"the auth settings",s);await b(e,t,`set the auth settings ${i.join(", ")}`);let o=await c(e,t,"PUT","/admin/auth-settings",Object.fromEntries(i.map(l=>[l,a[l]])),oe(r));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${le(s[l])} \u2192 ${le(o[l])}`)})}},Et=["google","apple","github","microsoft"],ec={google:"web_client_id",apple:"services_id",microsoft:"application_id",github:"oauth_client_id"},tc=new Set(["organizations","consumers","common"]),Ma={name:"auth:providers:list",summary:"the sign-in methods: email, phone and the social providers, whether each is on, their clients and stored secrets (by name)",scope:"environment",async run(e){let t=await h(e),[n,s]=await Promise.all([c(e,t,"GET","/admin/providers"),c(e,t,"GET","/admin/social-auth")]),r=[...n.providers.map(a=>({provider:a.id,on:a.enabled,detail:a.settings?`codes by ${a.settings.sender??"sms"}, ${a.settings.otp_ttl_seconds}s, ${a.settings.otp_max_attempts} tries, sign-up ${a.settings.allow_signups?"on":"off"}`:"",secrets:""})),...Et.map(a=>{let i=s.providers[a]??{enabled:!1,browser_clients:[],native_clients:[]},o=[...i.browser_clients.map(l=>`${l.key} (${l.platform})`),...i.native_clients.map(l=>`${l.key} (${l.platform}, native)`)];return{provider:a,on:i.enabled,detail:o.length===0?"no client":o.join(", "),secrets:s.credentials.filter(l=>l.provider===a).map(l=>`${l.key} ${l.configured?"set":"not set"}`).join(", ")}})];e.out.result({providers:n.providers,social:s.providers,credentials:s.credentials},()=>e.out.table(r,[{header:"provider",value:a=>a.provider},{header:"on",value:a=>a.on?"yes":"no"},{header:"clients / settings",value:a=>a.detail},{header:"secrets",value:a=>a.secrets}]))}},Ua={name:"auth:providers:set",summary:"turn a sign-in provider on or off and set it up: its client, its secret (from stdin), phone codes",usage:"<provider> [--enable|--disable] [setting=value\u2026] [--client-id <id>] [--secret-stdin] \u2026",details:["email is always on. phone: --enable/--disable, and its code settings as setting=value (allow_signups, otp_ttl_seconds, otp_max_attempts, sender, sms_second_factor).","google, apple, github, microsoft: --client-id sets the browser client --client names (default web), made when the provider has none of that name.","--secret-stdin reads the client's secret from stdin (Apple: the .p8 signing key, with --team-id and --key-id) and keeps it under --credential (default: the provider's name). It is never taken from the command line and never printed.",`--clients-file replaces the provider's clients with the file's {"browser_clients": [\u2026], "native_clients": [\u2026]} (native apps, several clients).`],args:{min:1,max:20},flags:{...ln,client:{type:"string",value:"key",description:"the browser client --client-id, --return-uri and the secret go to (default web)"},"client-id":{type:"string",value:"id",description:"the provider's id of your app (Google web client id, Apple services id, Microsoft application id, GitHub client id)"},"return-uri":{type:"string",multiple:!0,value:"url",description:"where your app is sent back after sign-in (repeat it; replaces the list)"},application:{type:"string",value:"key",description:"the application the client is for (default app)"},variant:{type:"string",value:"name",description:"the build variant the client is for (default release)"},directory:{type:"string",value:"tenant",description:"microsoft: a tenant id, organizations, consumers or common (default common)"},"secret-stdin":{type:"boolean",description:"read the client's secret (Apple: the .p8 key) from stdin"},credential:{type:"string",value:"key",description:"the name the secret is kept under (default: the provider's)"},"team-id":{type:"string",value:"id",description:"apple: your team's id, with --secret-stdin"},"key-id":{type:"string",value:"id",description:"apple: the signing key's id, with --secret-stdin"},"clients-file":{type:"string",value:"path",description:"replace the provider's clients with the file's (JSON)"}},scope:"environment",async run(e){let t=e.args[0],n=await h(e);if(t==="email")throw new m("always_on","email sign-in is always on, and has no settings of its own here",{hint:"its policy is in palbase auth:settings:set"});if(t==="phone")return nc(e,n,e.args.slice(1));if(!Et.includes(t))throw g(`no provider ${JSON.stringify(t)}`,`the providers: email, phone, ${Et.join(", ")}`);if(e.args.length>1)throw g(`${t} takes flags, not settings: palbase help auth:providers:set`);await rc(e,n,t)}};async function nc(e,t,n){let s=Rt(e),r=et(n);if(s===void 0&&r.length===0)throw g("nothing to change: --enable, --disable or setting=value","palbase help auth:providers:set");let a=await c(e,t,"GET","/admin/providers/phone/settings"),i=nt(a,r),o=ze(a,i);if(s===void 0&&o.length===0)return We(e,"the phone settings",a);await b(e,t,`phone sign-in: ${[...s===void 0?[]:[s?"turn it on":"turn it off"],...o.map(u=>`set ${u}`)].join(", ")}`);let l=o.length>0?await c(e,t,"PUT","/admin/providers/phone/settings",i):a,p=s===void 0?void 0:await c(e,t,"POST","/admin/providers/phone",{enabled:s});e.out.result({provider:"phone",...p?{enabled:p.enabled}:{},settings:l},()=>{p&&e.out.line(`phone sign-in is ${p.enabled?"on":"off"}.`);for(let u of o)e.out.line(`${u}: ${le(a[u])} \u2192 ${le(l[u])}`)})}function sc(e){if(tc.has(e))return{mode:e};if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e))return{mode:"tenant",tenant_id:e};throw g("--directory is a tenant id, organizations, consumers or common")}async function rc(e,t,n){let s=e.flags,r=Rt(e),a=await ce(e,t,"/admin/social-auth"),i=a.etag,o=a.body.providers[n]??{enabled:!1,browser_clients:[],native_clients:[]},l={},p=[],u=s.credential??n,d;if(s["secret-stdin"]===!0){if(n==="apple"&&(s["team-id"]===void 0||s["key-id"]===void 0))throw g("Apple's signing key goes with --team-id and --key-id");d=await fe(e,n==="apple"?"Apple's .p8 signing key":`${n}'s client secret`),p.push(`keep its secret as ${u}`)}let f=["client","client-id","return-uri","application","variant","directory","credential"].filter(A=>s[A]!==void 0);if(s["clients-file"]!==void 0){if(f.length>0)throw g(`--clients-file and --${f[0]} are one or the other`);let A=he(await ve(e,s["clients-file"],"the clients"),"the clients file");if(typeof A!="object"||A===null||A.browser_clients===void 0&&A.native_clients===void 0)throw g('the clients file is {"browser_clients": [\u2026], "native_clients": [\u2026]}');l.browser_clients=A.browser_clients??o.browser_clients,l.native_clients=A.native_clients??o.native_clients,p.push(`set its clients from ${s["clients-file"]}`)}else if(f.length>0||d!==void 0){let A=s.client??"web",P=[...o.browser_clients],S=P.findIndex($=>$.key===A),C=d!==void 0&&f.every($=>$==="credential");if(S>=0||!C){let $=S>=0?{...P[S]}:{key:A,enabled:!0,application_key:"app",platform:"web",variant:"release",return_uris:[]},K=ec[n];if(s["client-id"]!==void 0&&($[K]=s["client-id"]),!$[K])throw g(`the ${n} client ${A} is new: give it --client-id`);if(s["return-uri"]!==void 0&&($.return_uris=s["return-uri"]),s.application!==void 0&&($.application_key=s.application),s.variant!==void 0&&($.variant=s.variant),n==="microsoft")$.directory=s.directory!==void 0?sc(s.directory):$.directory??{mode:"common"};else if(s.directory!==void 0)throw g("--directory is a Microsoft client's");(d!==void 0||s.credential!==void 0||!$.credential_ref)&&($.credential_ref=u),P[S>=0?S:P.length]=$,l.browser_clients=P,p.push(`${S>=0?"change":"add"} its browser client ${A}`)}}if(r!==void 0&&(l.enabled=r,p.push(r?"turn it on":"turn it off")),p.length===0)throw g("nothing to change: give a flag","palbase help auth:providers:set");await b(e,t,`${n} sign-in: ${p.join(", ")}`);let y=a.body;if(d!==void 0){let A=n==="apple"?{provider:n,kind:"apple_signing_key",team_id:s["team-id"],key_id:s["key-id"],private_key:d}:{provider:n,kind:"oauth_client_secret",client_secret:d};i=(await we(e,t,"PUT",`/admin/social-auth/credentials/${_(u)}`,A,oe(i))).etag??i}Object.keys(l).length>0?y=await c(e,t,"PATCH",`/admin/social-auth/providers/${_(n)}`,l,oe(i)):y=await c(e,t,"GET","/admin/social-auth");let v=y.providers[n],T=y.credentials.filter(A=>A.provider===n);e.out.result({provider:n,...v,credentials:T},()=>{e.out.line(`${n} sign-in is ${v.enabled?"on":"off"}: ${v.browser_clients.length} browser client(s), ${v.native_clients.length} native.`);for(let A of T)e.out.line(` secret ${A.key}: ${A.configured?"set":"not set"}`)})}var Ka={name:"auth:providers:secret:delete",summary:"delete a sign-in provider's stored secret by its name; one a client still uses is refused",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await h(e),{body:s,etag:r}=await ce(e,n,"/admin/social-auth");if(!s.credentials.some(a=>a.key===t))throw g(`the environment holds no provider secret ${JSON.stringify(t)}`,"palbase auth:providers:list names them");await b(e,n,`delete the provider secret ${t}`),await c(e,n,"DELETE",`/admin/social-auth/credentials/${_(t)}`,void 0,oe(r)),e.out.result({deleted:t},()=>e.out.line(`Deleted the provider secret ${t}.`))}},Ga={name:"auth:providers:disable",summary:"turn a sign-in provider off (phone, google, apple, github, microsoft); its clients and secrets stay",usage:"<provider>",args:{min:1,max:1},scope:"environment",async run(e){let t=e.args[0],n=await h(e);if(t==="email")throw new m("always_on","email sign-in cannot be turned off");if(t==="phone"){await b(e,n,"turn phone sign-in off");let a=await c(e,n,"POST","/admin/providers/phone",{enabled:!1});e.out.result(a,()=>e.out.line("phone sign-in is off."));return}if(!Et.includes(t))throw g(`no provider ${JSON.stringify(t)}`,`the providers: phone, ${Et.join(", ")}`);let{etag:s}=await ce(e,n,"/admin/social-auth");await b(e,n,`turn ${t} sign-in off`);let r=await c(e,n,"PATCH",`/admin/social-auth/providers/${_(t)}`,{enabled:!1},oe(s));e.out.result({provider:t,...r.providers[t]},()=>e.out.line(`${t} sign-in is off; its clients and secrets stay.`))}},Ba={name:"auth:roles:list",summary:"the environment's roles: their permissions, which one new users get, how many users hold each",scope:"environment",async run(e){let t=await h(e),{roles:n}=await c(e,t,"GET","/admin/roles");e.out.result({roles:n},()=>{n.length===0&&e.out.line("No role yet: palbase auth:roles:set <name> --permissions \u2026 makes one."),e.out.table(n,[{header:"role",value:s=>`${s.name}${s.is_default?" (default)":""}`},{header:"permissions",value:s=>s.permissions.join(", ")},{header:"users",value:s=>String(s.user_count??0)},{header:"description",value:s=>s.description}])})}},Ja={name:"auth:roles:set",summary:"make a role, or change one: its permissions (the list is replaced), description, whether new users get it",usage:"<name> [--permissions <a.b,c.d>] [--description <text>] [--default yes|no]",args:{min:1,max:1},flags:{permissions:{type:"string",multiple:!0,value:"list",description:'resource.action permissions, comma-separated or repeated; replaces the list ("" for none)'},description:{type:"string",value:"text",description:"what the role is for"},default:{type:"string",value:"yes|no",description:"whether new users get it (one role at most)"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],{roles:s}=await c(e,t,"GET","/admin/roles"),r=s.find(u=>u.name===n),a=me(e.flags.permissions),i=Tt(e.flags.default,"default");if(r&&a===void 0&&e.flags.description===void 0&&i===void 0)throw g("nothing to change: give a flag","palbase help auth:roles:set");let o={permissions:a??r?.permissions??[],description:e.flags.description??r?.description??"",is_default:i??r?.is_default??!1};await b(e,t,`${r?"change":"make"} the role ${n}${r?.user_count?` (${r.user_count} users hold it)`:""}`);let l=await c(e,t,"PUT",`/admin/roles/${_(n)}`,o),p=l.roles.find(u=>u.name===n);e.out.result(p??l,()=>e.out.line(`${r?"Changed":"Made"} the role ${n}: ${(p?.permissions??o.permissions).join(", ")||"no permission"}${p?.is_default?", given to new users":""}.`))}},za={name:"auth:roles:delete",summary:"delete a role; one users hold is deleted only with --force, and is taken from them",usage:"<name> [--force]",args:{min:1,max:1},flags:{force:{type:"boolean",description:"delete it although users hold it"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];await b(e,t,`delete the role ${n}${e.flags.force===!0?", taking it from the users who hold it":""}`),await c(e,t,"DELETE",`/admin/roles/${_(n)}${e.flags.force===!0?"?confirm=true":""}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the role ${n}.`))}},Wa={event:{type:"string",value:"event",description:"before.user.create, before.login, before.token.issue, \u2026 (the module lists them)"},url:{type:"string",value:"url",description:"the https endpoint auth calls and waits for"},"failure-mode":{type:"string",value:"deny|allow",description:"what auth does when the hook does not answer in time (default deny)"},timeout:{type:"string",value:"ms",description:"how long auth waits, 100\u20135000 ms (default 5000)"}},Fa={name:"auth:hooks:list",summary:"the blocking hooks: what auth calls, and waits for, before a sign-up, a sign-in, a token",scope:"environment",async run(e){let t=await h(e),{hooks:n}=await c(e,t,"GET","/admin/hooks");e.out.result({hooks:n},()=>{n.length===0&&e.out.line("No hook: palbase auth:hooks:create --event \u2026 --url \u2026 registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"event",value:s=>s.event},{header:"url",value:s=>s.url},{header:"on failure",value:s=>s.failure_mode},{header:"timeout",value:s=>`${s.timeout_ms} ms`},{header:"on",value:s=>s.enabled?"yes":"no"}])})}},Va={name:"auth:hooks:create",summary:"register a blocking hook; its signing secret is printed this once",usage:"--event <event> --url <url> [--failure-mode deny|allow] [--timeout <ms>] [--disable]",flags:{...Wa,disable:{type:"boolean",description:"register it turned off"}},scope:"environment",async run(e){let t=await h(e);if(e.flags.event===void 0||e.flags.url===void 0)throw g("a hook needs --event and --url");let n=Ws({event:e.flags.event,url:e.flags.url,failure_mode:e.flags["failure-mode"],timeout_ms:Me(e.flags.timeout,"timeout"),...e.flags.disable===!0?{enabled:!1}:{}});await b(e,t,`register a hook on ${String(n.event)} calling ${String(n.url)}`);let s=await c(e,t,"POST","/admin/hooks",n,R());e.out.result(s,()=>{e.out.line(`Registered ${s.id}: ${s.event} \u2192 ${s.url}.`),s.secret&&e.out.line(`signing secret (shown this once): ${s.secret}`)})}},Ha={name:"auth:hooks:update",summary:"change a blocking hook: its event, endpoint, timeout, failure mode, on or off (its secret stays)",usage:"<id> [--event \u2026] [--url \u2026] [--failure-mode \u2026] [--timeout \u2026] [--enable|--disable]",args:{min:1,max:1},flags:{...Wa,...ln},scope:"environment",async run(e){let t=await h(e),{hooks:n}=await c(e,t,"GET","/admin/hooks"),s=n.find(o=>o.id===e.args[0]);if(!s)throw new m("no_such_hook",`no hook ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:hooks:list shows them"});let r={event:e.flags.event??s.event,url:e.flags.url??s.url,failure_mode:e.flags["failure-mode"]??s.failure_mode,timeout_ms:Me(e.flags.timeout,"timeout")??s.timeout_ms,enabled:Rt(e)??s.enabled},a=ze(ac(s),r);if(a.length===0)throw g("nothing to change: give a flag","palbase help auth:hooks:update");await b(e,t,`change the hook ${s.id}: ${a.join(", ")}`);let i=await c(e,t,"PUT",`/admin/hooks/${_(s.id)}`,r);e.out.result(i,()=>e.out.line(`${i.id}: ${i.event} \u2192 ${i.url}, ${i.failure_mode} on failure, ${i.timeout_ms} ms, ${i.enabled?"on":"off"}.`))}};function ac(e){return{event:e.event,url:e.url,failure_mode:e.failure_mode,timeout_ms:e.timeout_ms,enabled:e.enabled}}var Xa={name:"auth:hooks:delete",summary:"delete a blocking hook; auth stops calling it",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`delete the hook ${e.args[0]}`),await c(e,t,"DELETE",`/admin/hooks/${_(e.args[0])}`),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the hook ${e.args[0]}.`))}},Ya={name:"auth:oauth-clients:list",summary:"the OAuth clients of the environment's OpenID provider (apps that sign in with your users' accounts)",scope:"environment",async run(e){let t=await h(e),{clients:n}=await c(e,t,"GET","/admin/oauth-clients");e.out.result({clients:n},()=>{n.length===0&&e.out.line("No OAuth client: palbase auth:oauth-clients:create <name> registers one."),e.out.table(n,[{header:"id",value:s=>s.id},{header:"client id",value:s=>s.client_id},{header:"name",value:s=>s.client_name},{header:"kind",value:s=>s.is_public?"public":s.token_endpoint_auth_method},{header:"grants",value:s=>s.grant_types.join(", ")},{header:"redirect uris",value:s=>s.redirect_uris.join(", ")}])})}},Za={name:"auth:oauth-clients:create",summary:"register an OAuth client; a confidential client's secret is printed this once",usage:"<name> [--public] [--redirect-uri <url>\u2026] [--grant <type>\u2026] [--scope <scope>\u2026] [--client-id <id>]",args:{min:1,max:1},flags:{public:{type:"boolean",description:"a public client (a CLI, a mobile app): it has no secret"},"redirect-uri":{type:"string",multiple:!0,value:"url",description:"where it may send the browser back (repeat it)"},grant:{type:"string",multiple:!0,value:"type",description:"authorization_code (default), refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code"},scope:{type:"string",multiple:!0,value:"scope",description:"what it may ask for (default openid, profile, email)"},"client-id":{type:"string",value:"id",description:"a client_id to use as it is (default: a random one)"}},scope:"environment",async run(e){let t=await h(e),n=Ws({client_name:e.args[0],client_id:e.flags["client-id"],is_public:e.flags.public===!0?!0:void 0,redirect_uris:e.flags["redirect-uri"],grant_types:me(e.flags.grant),scopes:me(e.flags.scope)});await b(e,t,`register the OAuth client ${e.args[0]}`);let s=await c(e,t,"POST","/admin/oauth-clients",n,R());e.out.result(s,()=>{e.out.line(`Registered ${s.client_name}: client_id ${s.client_id} (${s.id}).`),s.client_secret&&e.out.line(`client secret (shown this once): ${s.client_secret}`)})}},Qa={name:"auth:oauth-clients:delete",summary:"delete an OAuth client (by its id or its client_id)",usage:"<id|client-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),{clients:n}=await c(e,t,"GET","/admin/oauth-clients"),s=n.find(r=>r.id===e.args[0]||r.client_id===e.args[0]);if(!s)throw new m("no_such_client",`no OAuth client ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:oauth-clients:list shows them"});await b(e,t,`delete the OAuth client ${s.client_name} (${s.client_id})`),await c(e,t,"DELETE",`/admin/oauth-clients/${_(s.id)}`),e.out.result({deleted:s.id},()=>e.out.line(`Deleted the OAuth client ${s.client_name}.`))}};function ic(e){return{...e.client_id.startsWith("oidc_")?{}:{client_id:e.client_id},is_public:e.is_public,grant_types:[...e.grant_types].sort(),redirect_uris:e.redirect_uris,post_logout_redirect_uris:e.post_logout_redirect_uris,response_types:e.response_types,scopes:e.scopes,token_endpoint_auth_method:e.token_endpoint_auth_method,id_token_lifetime_secs:e.id_token_lifetime_secs,dpop_bound_access_tokens:e.dpop_bound_access_tokens,...e.jwks?{jwks:e.jwks}:{}}}var ei={name:"auth",async read(e,t){let[n,s,r,a,i,o]=await Promise.all([c(e,t,"GET","/admin/auth-settings"),c(e,t,"GET","/admin/providers"),c(e,t,"GET","/admin/social-auth"),c(e,t,"GET","/admin/roles"),c(e,t,"GET","/admin/hooks"),c(e,t,"GET","/admin/oauth-clients")]),l={};for(let[u,d]of Object.entries(n))l[`settings.${u}`]=d;let p=s.providers.find(u=>u.id==="phone");p&&(l["phone.enabled"]=p.enabled,p.settings&&(l["phone.settings"]=p.settings));for(let u of Et){let d=r.providers[u];d&&(l[`social.${u}`]={enabled:d.enabled,browser_clients:d.browser_clients,native_clients:d.native_clients})}for(let u of r.credentials)l[`secrets.${u.key}`]={provider:u.provider,kind:u.kind,value:u.configured?"set":"not set"};for(let u of a.roles)l[`roles.${u.name}`]={description:u.description,is_default:u.is_default,permissions:[...u.permissions].sort()};for(let u of i.hooks)l[`hooks.${u.event} ${u.url}`]={failure_mode:u.failure_mode,timeout_ms:u.timeout_ms,enabled:u.enabled};for(let u of o.clients)l[`oauth_clients.${u.client_name}`]=ic(u);return l},async copy(e,t,n,s){await oc(e,t,Q(n,"settings."),s);for(let a of Q(n,"phone."))a.name==="enabled"&&await U(s,`turn phone sign-in ${a.from?"on":"off"}`,async()=>{await c(e,t,"POST","/admin/providers/phone",{enabled:a.from===!0})}),a.name==="settings"&&a.from!==void 0&&await U(s,"set the phone code settings",async()=>{await c(e,t,"PUT","/admin/providers/phone/settings",a.from)});await lc(e,t,Q(n,"social."),Q(n,"secrets."),s);let r=Q(n,"roles.").sort((a,i)=>+(a.from?.is_default===!0)-+(i.from?.is_default===!0));for(let a of r)a.from===void 0?await U(s,`delete the role ${a.name}`,async()=>{await c(e,t,"DELETE",`/admin/roles/${_(a.name)}`)}):await U(s,`set the role ${a.name}`,async()=>{await c(e,t,"PUT",`/admin/roles/${_(a.name)}`,a.from)});await pc(e,t,Q(n,"hooks."),s),await dc(e,t,Q(n,"oauth_clients."),s)}};async function oc(e,t,n,s){let r=n.filter(i=>i.from!==void 0);for(let i of n.filter(o=>o.from===void 0))s.left(`auth settings ${i.name}: set in the target only, and a setting is not taken away (set it with auth:settings:set)`);if(r.length===0)return;let{etag:a}=await ce(e,t,"/admin/auth-settings");await U(s,`set the auth settings ${r.map(i=>i.name).join(", ")}`,async()=>{await c(e,t,"PUT","/admin/auth-settings",Object.fromEntries(r.map(i=>[i.name,i.from])),oe(a))})}async function lc(e,t,n,s,r){for(let l of s){let p=l.from;p===void 0?r.left(`secret ${l.name}: only in the target, left as it is (delete it with palbase auth:providers:secret:delete ${l.name})`):r.left(`secret ${l.name} (${p.provider}): secrets are not copied \u2014 set it with palbase auth:providers:set ${p.provider} --secret-stdin --credential ${l.name}`)}if(n.length===0)return;let a=await ce(e,t,"/admin/social-auth"),i=a.etag,o=new Set(a.body.credentials.filter(l=>l.configured).map(l=>l.key));for(let l of n){let p=l.from,u=[...new Set(p.browser_clients.map(d=>d.credential_ref).filter(d=>typeof d=="string"&&!o.has(d)))];if(u.length>0){r.left(`${l.name} sign-in: its clients use the secret ${u.join(", ")}, which the target does not hold \u2014 set it first (auth:providers:set ${l.name} --secret-stdin --credential ${u[0]})`);continue}await U(r,`set ${l.name} sign-in (${p.enabled?"on":"off"}, ${p.browser_clients.length+p.native_clients.length} client(s))`,async()=>{i=(await we(e,t,"PATCH",`/admin/social-auth/providers/${_(l.name)}`,p,oe(i))).etag??i})}}async function pc(e,t,n,s){if(n.length===0)return;let{hooks:r}=await c(e,t,"GET","/admin/hooks");for(let a of n){let i=a.name.indexOf(" "),o=a.name.slice(0,i),l=a.name.slice(i+1),p=r.find(u=>u.event===o&&u.url===l);if(a.from===void 0)p&&await U(s,`delete the hook ${o} \u2192 ${l}`,async()=>{await c(e,t,"DELETE",`/admin/hooks/${_(p.id)}`)});else if(p)await U(s,`change the hook ${o} \u2192 ${l}`,async()=>{await c(e,t,"PUT",`/admin/hooks/${_(p.id)}`,{event:o,url:l,...a.from})});else{let u;await U(s,`register the hook ${o} \u2192 ${l}`,async()=>{u=(await c(e,t,"POST","/admin/hooks",{event:o,url:l,...a.from},R())).secret}),u&&s.shown(`the hook ${o} \u2192 ${l} signs with`,u)}}}async function dc(e,t,n,s){if(n.length===0)return;let{clients:r}=await c(e,t,"GET","/admin/oauth-clients");for(let a of n){let i=r.find(l=>l.client_name===a.name),o=a.from;if(o===void 0)i&&await U(s,`delete the OAuth client ${a.name}`,async()=>{await c(e,t,"DELETE",`/admin/oauth-clients/${_(i.id)}`)});else if(i)if(!Re(o.is_public,i.is_public)||o.client_id!==void 0&&o.client_id!==i.client_id)s.left(`OAuth client ${a.name}: its client_id or kind (public or not) differs, which a client cannot change \u2014 delete it in the target and run the copy again`);else{let{client_id:l,is_public:p,...u}=o;await U(s,`change the OAuth client ${a.name}`,async()=>{await c(e,t,"PATCH",`/admin/oauth-clients/${_(i.id)}`,u)})}else{let l;await U(s,`register the OAuth client ${a.name}`,async()=>{l=await c(e,t,"POST","/admin/oauth-clients",{client_name:a.name,...o},R())}),l?.client_secret&&s.shown(`the OAuth client ${a.name} (${l.client_id}) has the secret`,l.client_secret)}}}var ti=["bool","number","string","object"];async function rt(e,t){return(await c(e,t,"GET","/flags/templates/capabilities")).publish_enabled}async function st(e,t,n){let s=await ce(e,t,"/flags/templates/draft");if(s.body.draft||!n)return{draft:s.body.draft,etag:s.etag};let r=await ce(e,t,"/flags/templates/active"),a=await we(e,t,"POST","/flags/templates/draft",{},oe(r.etag));return{draft:a.body.draft,etag:a.etag}}function ni(e){return{value_type:e.value_type,value:e.value,...e.description?{description:e.description}:{},...e.variants&&e.variants.length>0?{variants:e.variants}:{},...e.conditions&&e.conditions.length>0?{conditions:e.conditions}:{}}}function uc(e){return typeof e=="boolean"?"bool":typeof e=="number"?"number":typeof e=="string"?"string":"object"}function si(e,t){switch(t){case"bool":if(e==="true"||e==="false")return e==="true";throw g(`a bool flag's value is true or false, not ${JSON.stringify(e)}`);case"number":{let n=Number(e);if(e.trim()===""||!Number.isFinite(n))throw g(`a number flag's value is a number, not ${JSON.stringify(e)}`);return n}case"string":{let n=tt(e);return typeof n=="string"?n:e}default:{let n=tt(e);if(typeof n!="object"||n===null)throw g(`an object flag's value is a JSON object or list, not ${JSON.stringify(e)}`);return n}}}function cc(e,t){let n=e.lastIndexOf("=>");if(n<=0)throw g(`a condition is "<expression> => <value>", not ${JSON.stringify(e)}`,`--condition "client.platform == 'ios' => true"`);return{when:e.slice(0,n).trim(),value:si(e.slice(n+2).trim(),t)}}var ri={name:"flags:list",summary:"the environment's flags as they are live: type, default, targeting (and the draft's state when it publishes)",scope:"environment",async run(e){let t=await h(e),n=await rt(e,t),s,r,a;if(n){let[i,o]=await Promise.all([c(e,t,"GET","/flags/templates/active"),st(e,t,!1)]);s=Object.entries(i.definitions).map(([l,p])=>({key:l,...p})),r=i.version,a=o.draft}else s=await c(e,t,"GET","/flags/system");s.sort((i,o)=>i.key<o.key?-1:1),e.out.result({publishing:n,...r?{version:r}:{},flags:s,...a?{draft:{revision:a.revision,base_version:a.base_version}}:{}},()=>{n&&e.out.line(`Version ${r} is live${a?`; a draft (from ${a.base_version}) is in progress: palbase flags:publish shows and publishes it`:""}.`),s.length===0&&e.out.line("No flag yet: palbase flags:set <key> <value> makes one."),e.out.table(s,[{header:"flag",value:i=>i.key},{header:"type",value:i=>i.value_type},{header:"value",value:i=>JSON.stringify(i.value)},{header:"targeting",value:i=>i.conditions?.length?`${i.conditions.length} condition(s)`:""},{header:"description",value:i=>i.description??""}])})}},ai={name:"flags:set",summary:"define a flag or change its default and targeting (into the draft when the environment publishes)",usage:'<key> <value> [--type bool|number|string|object] [--condition "<expression> => <value>"\u2026] [--description <text>]',details:["The value is read as the flag's type: true/false, a number, text, or a JSON object; a new flag's type is the value's unless --type says it.","--condition replaces the targeting (first match wins, 8 at most); --clear-conditions takes it away; without either it stays.","An environment that publishes takes the change into its shared draft (palbase flags:publish makes it live); one that does not, live at once."],args:{min:2,max:2},flags:{type:{type:"string",value:"type",description:"bool, number, string or object"},condition:{type:"string",multiple:!0,value:"rule",description:'"<expression> => <value>" (repeat it, in order)'},"clear-conditions":{type:"boolean",description:"take its targeting away"},variants:{type:"string",value:"list",description:"a string flag's allowed values, comma-separated"},description:{type:"string",value:"text",description:"what the flag is for"}},scope:"environment",async run(e){let t=await h(e),[n,s]=e.args,r=e.flags.type;if(r!==void 0&&!ti.includes(r))throw g(`--type is one of ${ti.join(", ")}`);if(e.flags.condition!==void 0&&e.flags["clear-conditions"]===!0)throw g("--condition and --clear-conditions are one or the other");let a=await rt(e,t),i,o;a?(o=await st(e,t,!1),i=o.draft?.definitions[n]??(await c(e,t,"GET","/flags/templates/active")).definitions[n]):i=(await c(e,t,"GET","/flags/system")).find(d=>d.key===n);let l=r??i?.value_type??uc(tt(s)),p=e.flags["clear-conditions"]===!0?[]:e.flags.condition!==void 0?e.flags.condition.map(d=>cc(d,l)):i?.value_type===l?i.conditions??[]:[],u=ni({value_type:l,value:si(s,l),description:e.flags.description??i?.description,variants:me(e.flags.variants)??(i?.value_type===l?i.variants:void 0),conditions:p});if(await b(e,t,a?`put ${n} = ${le(u.value)} into the flags draft`:`set the flag ${n} = ${le(u.value)}, live at once`),a){o?.draft||(o=await st(e,t,!0));let d=await we(e,t,"PUT",`/flags/templates/draft/parameters/${_(n)}`,u,oe(o.etag));e.out.result({key:n,...u,draft:{revision:d.body.draft?.revision}},()=>e.out.line(`${n} = ${JSON.stringify(u.value)} is in the draft: palbase flags:publish makes it live.`))}else{let d=await c(e,t,"PUT",`/flags/system/${_(n)}`,u);e.out.result(d,()=>e.out.line(`${n} = ${JSON.stringify(d.value)} (${d.value_type}) is live.`))}}},ii={name:"flags:delete",summary:"delete a flag, with every user's own value of it (from the draft when the environment publishes)",usage:"<key>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s=await rt(e,t);if(await b(e,t,s?`take ${n} out of the flags draft`:`delete the flag ${n} and every user's value of it, live at once`),s){let r=await st(e,t,!0);await c(e,t,"DELETE",`/flags/templates/draft/parameters/${_(n)}`,void 0,oe(r.etag)),e.out.result({deleted:n,draft:!0},()=>e.out.line(`${n} is out of the draft: palbase flags:publish makes it so.`))}else await c(e,t,"DELETE",`/flags/system/${_(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the flag ${n}.`))}};function Zs(){return new m("publishing_off","this environment's flags are not published through drafts: flags:set and flags:delete change them live",{hint:"versions, publishing and rollback are for an environment that publishes"})}function mc(e,t){t.changes.length===0&&e.out.line("The draft changes nothing.");for(let n of t.changes){let s=n.override_count>0?` (${n.override_count} users' own values)`:"";n.kind==="added"?e.out.line(` + ${n.key} = ${JSON.stringify(n.after?.value)}`):n.kind==="removed"?e.out.line(` - ${n.key}${s}`):e.out.line(` ~ ${n.key}: ${n.changed_fields.join(", ")}${n.changed_fields.includes("value")?` \u2192 ${JSON.stringify(n.after?.value)}`:""}${s}`)}for(let n of t.issues)e.out.line(` ! ${n.key?`${n.key}: `:""}${n.message}`)}async function Qs(e,t,n,s){return c(e,t,"POST","/flags/templates/publish",s===""?{}:{note:s},{...oe(n),...R()})}var oi={name:"flags:publish",summary:"show the flags draft's changes against the live version, then publish it as the next version",usage:"[-m <note>]",flags:{note:{type:"string",short:"m",value:"note",description:"why: kept with the version"}},scope:"environment",async run(e){let t=await h(e);if(!await rt(e,t))throw Zs();if(!(await st(e,t,!1)).draft)throw new m("no_draft","there is no draft to publish",{hint:"palbase flags:set puts a change into one"});let s=await ce(e,t,"/flags/templates/draft/review");if(e.out.json||mc(e,s.body),!s.body.can_publish)throw new m("cannot_publish",`the draft cannot be published: ${s.body.issues.map(a=>a.message).join("; ")}`);if(s.body.changes.length===0){e.out.result({unchanged:!0,review:s.body},()=>e.out.line("Nothing to publish."));return}await b(e,t,`publish the flags draft (${s.body.changes.length} change(s)) as the version after ${s.body.active_version}`);let r=await Qs(e,t,s.etag,e.flags.note??"");e.out.result({...r,review:s.body},()=>e.out.line(r.unchanged?"The draft changed nothing; nothing was published.":`Published version ${r.template.version}.`))}},li={name:"flags:publishing:enable",summary:"turn publishing on for the environment's flags: changes go into a shared draft, live once published \u2014 for good",details:["What is live now becomes version 1. From then on flags:set and flags:delete change the draft, flags:publish makes it live as the next version, and flags:rollback brings an earlier one back. It cannot be turned off."],scope:"environment",async run(e){let t=await h(e);if(await rt(e,t)){e.out.result({publishing:!0,changed:!1},()=>e.out.line("The environment's flags are published through drafts already."));return}await b(e,t,"turn publishing on for the flags \u2014 what is live becomes version 1, and it cannot be turned off");let n=await c(e,t,"POST","/flags/templates/activation",{});e.out.result({publishing:!0,changed:!0,version:n.version},()=>e.out.line(`Publishing is on: what was live is version ${n.version}; flags:set changes the draft, flags:publish makes it live.`))}},pi={name:"flags:versions",summary:"the published versions of the flags, newest first, and which one is live",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await h(e);if(!await rt(e,t))throw Zs();let n=Number(e.flags.limit??20);if(!Number.isInteger(n)||n<=0)throw g("--limit is a whole number");let s=await c(e,t,"GET",`/flags/templates/versions?limit=${n}`);e.out.result(s,()=>e.out.table(s.versions,[{header:"",value:r=>r.version===s.active_version?"*":""},{header:"version",value:r=>r.version},{header:"kind",value:r=>`${r.kind}${r.restored_from?` of ${r.restored_from}`:""}`},{header:"published",value:r=>r.created_at},{header:"by",value:r=>r.actor},{header:"note",value:r=>r.note}]))}},di={name:"flags:rollback",summary:"make an earlier version of the flags live again (published as the next version)",usage:"<version> [-m <note>]",args:{min:1,max:1},flags:{note:{type:"string",short:"m",value:"note",description:"why (default: rollback to <version>)"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(!await rt(e,t))throw Zs();if((await st(e,t,!1)).draft)throw new m("draft_in_progress","a draft is in progress: publish it (flags:publish) before a rollback");let s=await ce(e,t,"/flags/templates/active");await b(e,t,`make version ${n} of the flags live again (now ${s.body.version})`);let r=await we(e,t,"POST",`/flags/templates/restore/${_(n)}`,{},oe(s.etag)),a=await Qs(e,t,r.etag,e.flags.note??`rollback to ${n}`);e.out.result(a,()=>e.out.line(a.unchanged?`Version ${n} is what is live already.`:`Version ${n} is live again, as version ${a.template.version}.`))}},ui={name:"flags",async read(e,t){let n=await c(e,t,"GET","/flags/system"),s={};for(let r of n)s[`flags.${r.key}`]=ni(r);return s},async copy(e,t,n,s){let r=Q(n,"flags.");if(r.length===0)return;if(!await rt(e,t)){for(let i of r)i.from===void 0?await U(s,`delete the flag ${i.name}`,async()=>{await c(e,t,"DELETE",`/flags/system/${_(i.name)}`)}):await U(s,`set the flag ${i.name}`,async()=>{await c(e,t,"PUT",`/flags/system/${_(i.name)}`,i.from)});return}if((await st(e,t,!1)).draft){s.left("flags: the target has a draft in progress \u2014 publish it (flags:publish) and copy again");return}let a=(await st(e,t,!0)).etag;for(let i of r)await U(s,`${i.from===void 0?"take":"put"} the flag ${i.name} ${i.from===void 0?"out of":"into"} the draft`,async()=>{let o=`/flags/templates/draft/parameters/${_(i.name)}`;a=(i.from===void 0?await we(e,t,"DELETE",o,void 0,oe(a)):await we(e,t,"PUT",o,i.from,oe(a))).etag??a});await U(s,"publish the flags draft",async()=>{await Qs(e,t,a,"config:copy")})}};var ci=["email","sms","verify","whatsapp","push"],er=["email","sms","whatsapp"],mi={name:"notify:providers:list",summary:"the environment's own providers, by channel and the order they are tried (never their credentials)",scope:"environment",async run(e){let t=await h(e),{providers:n}=await c(e,t,"GET","/notify/providers");e.out.result({providers:n},()=>{n.length===0&&e.out.line("No provider of the environment's own: email goes through the platform's sender; texts need one (notify:providers:set)."),e.out.table(n,[{header:"channel",value:s=>s.channel},{header:"provider",value:s=>s.provider},{header:"on",value:s=>s.active?"yes":"no"},{header:"priority",value:s=>String(s.priority)},{header:"credentials",value:()=>"set"}])})}},gi={name:"notify:providers:set",summary:"bring a provider for a channel: its credentials are piped in as JSON, checked as the sender will use them, never shown",usage:"<channel> <provider> [--priority <n>] [--enable|--disable] < credentials.json",details:["email: acs {connection_string, from_email}, ses {region, access_key_id, secret_access_key, from_domain}, sendgrid {api_key, from_domain}, smtp {host, port, username, password, from_email, use_starttls}.","sms: twilio {account_sid, auth_token, from_number}, vonage {api_key, api_secret, from}, acs {connection_string, from_number}. verify: twilio_verify {account_sid, auth_token, service_sid}.","whatsapp: meta {phone_number_id, access_token, api_version, app_secret, verify_token}. push: fcm (the service account JSON), apns {private_key, team_id, key_id, bundle_id, is_production}, expo {access_token}.","The credentials are read from stdin only: never from the command line, where the shell would keep them."],args:{min:2,max:2},flags:{priority:{type:"string",value:"n",description:"lower is tried first"},...ln},scope:"environment",async run(e){let[t,n]=e.args;if(!ci.includes(t))throw g(`the channels: ${ci.join(", ")}`);let s=await h(e),r=he(await fe(e,`${n}'s credentials (JSON)`),"the credentials");if(typeof r!="object"||r===null||Array.isArray(r))throw g("the credentials are a JSON object");let a=Rt(e),i=Me(e.flags.priority,"priority");await b(e,s,`send ${t} through ${n}`);let o=await c(e,s,"PUT",`/notify/providers/${_(t)}/${_(n)}`,{credentials:r,...a!==void 0?{active:a}:{},...i!==void 0?{priority:i}:{}},R());e.out.result(o,()=>e.out.line(`${o.channel} goes through ${o.provider} (${o.active?"on":"off"}, priority ${o.priority}); its credentials are kept sealed.`))}},hi={name:"notify:providers:remove",summary:"forget a provider and its credentials",usage:"<channel> <provider>",args:{min:2,max:2},scope:"environment",async run(e){let[t,n]=e.args,s=await h(e);await b(e,s,`forget the ${t} provider ${n} and its credentials`),await c(e,s,"DELETE",`/notify/providers/${_(t)}/${_(n)}`,void 0,R()),e.out.result({removed:{channel:t,provider:n}},()=>e.out.line(`${t} no longer goes through ${n}.`))}},nr={channel:{type:"string",value:"channel",description:"email (default), sms or whatsapp"},locale:{type:"string",value:"locale",description:"the template's locale: en, tr, pt-BR"}};function pn(e){let t=e.flags.channel??"email";if(!er.includes(t))throw g(`--channel is one of ${er.join(", ")}`);return t}function sr(e,t){let n=e.flags.locale;if(n===void 0)throw g("which locale? pass --locale <locale>");return`/notify/templates/${pn(e)}/${_(t)}/${_(n)}`}function gc(e,t){if(e==="sms"){let s=hc(t);return typeof s=="object"&&s!==null&&typeof s.body=="string"?{body:s.body}:{body:t.replace(/\n$/,"")}}let n=he(t,`the ${e} template`);if(e==="email"){for(let s of["subject","html_body","text_body"])if(typeof n[s]!="string")throw g(`an email template is {"subject", "html_body", "text_body"}: ${s} is missing`);return{subject:n.subject,html_body:n.html_body,text_body:n.text_body}}if(typeof n.name!="string"||typeof n.language!="string")throw g('a WhatsApp template is {"name", "language", "variables", "code_button"}: the template approved on your Meta account');return{name:n.name,language:n.language,...n.variables!==void 0?{variables:n.variables}:{},...n.code_button!==void 0?{code_button:n.code_button}:{}}}function hc(e){try{return JSON.parse(e)}catch{return e}}var fi={name:"notify:templates:list",summary:"the templates messages render from: the platform's defaults and the ones the environment overrides",usage:"[--channel email|sms|whatsapp]",flags:{channel:{type:"string",value:"channel",description:"only this channel's (default: all three)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.channel===void 0?er:[pn(e)],s=await Promise.all(n.map(a=>c(e,t,"GET",`/notify/templates/${a}`))),r=n.flatMap((a,i)=>s[i].templates.map(o=>({channel:a,...o})));e.out.result({templates:r},()=>e.out.table(r,[{header:"channel",value:a=>a.channel},{header:"slug",value:a=>a.slug},{header:"locale",value:a=>a.locale},{header:"yours",value:a=>"customized"in a?a.customized?"yes":"default":"yes"},{header:"subject / text / template",value:a=>"subject"in a?a.subject:"body"in a?a.body.split(`
28
28
  `)[0].slice(0,60):`${a.name} (${a.language})`}]))}},yi={name:"notify:templates:get",summary:"a template as it renders now in a locale (yours, or the platform's default); --json gives the document templates:set takes",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:nr,scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",sr(e,e.args[0]));e.out.result(n,()=>{e.out.line(`${String(n.slug)} (${String(n.locale)}) \u2014 ${n.customized===!1?"the platform's default":"yours"}`),Array.isArray(n.variables)&&n.variables.length>0&&e.out.line(`variables: ${n.variables.join(", ")}`);for(let s of["subject","name","language"])typeof n[s]=="string"&&e.out.line(`${s}: ${String(n[s])}`);for(let s of["text_body","body","html_body"])if(typeof n[s]=="string"){e.out.line(),e.out.line(`\u2500\u2500 ${s} \u2500\u2500`);for(let r of n[s].split(`
29
- `))e.out.line(r)}})}},bi={name:"notify:templates:set",summary:"override a template in a locale, from a file or stdin (email: {subject, html_body, text_body} as JSON)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp] [--file <path>]",details:['email: {"subject", "html_body", "text_body"}; sms: the text, or {"body"}; whatsapp: {"name", "language", "variables", "code_button"} of a template approved on your Meta account.',"notify:templates:get \u2026 --json > t.json gives a document to edit and set back. Handlebars: {{name}}, {{#if}}, {{#each}}."],args:{min:1,max:1},flags:{...nr,file:{type:"string",value:"path",description:"the template (default: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=dn(e),s=sr(e,e.args[0]),r=gc(n,await ve(e,e.flags.file,"the template"));await b(e,t,`override the ${n} template ${e.args[0]} (${e.flags.locale})`);let a=await c(e,t,"PUT",s,r,R());e.out.result(a,()=>e.out.line(`The ${n} template ${e.args[0]} (${e.flags.locale}) is yours now${typeof a.subject=="string"?`: "${a.subject}"`:""}.`))}},wi={name:"notify:templates:delete",summary:"remove an override: the platform's default renders again (a WhatsApp mapping is forgotten)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:nr,scope:"environment",async run(e){let t=await h(e),n=sr(e,e.args[0]);await b(e,t,`remove the ${dn(e)} template ${e.args[0]} (${e.flags.locale})`),await c(e,t,"DELETE",n,void 0,R()),e.out.result({deleted:{channel:dn(e),slug:e.args[0],locale:e.flags.locale}},()=>e.out.line(`Removed the override of ${e.args[0]} (${e.flags.locale}).`))}};async function tr(e,t){let n=[],s;do{let r=await c(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${_(s)}`:""}`);n.push(...r.topics),s=r.next_cursor}while(s);return n}var vi={name:"notify:topics:list",summary:"the topics users follow, how many follow each, whether they follow it from the app",scope:"environment",async run(e){let t=await h(e),n=await tr(e,t);e.out.result({topics:n},()=>{n.length===0&&e.out.line("No topic: palbase notify:topics:create <name> makes one."),e.out.table(n,[{header:"topic",value:s=>s.name},{header:"followers",value:s=>String(s.subscribers)},{header:"from the app",value:s=>s.self_subscribe?"yes":"no: the project adds them"}])})}},_i={name:"notify:topics:create",summary:"make a topic users can follow; a push to it reaches its followers",usage:"<name> [--self-subscribe yes|no]",args:{min:1,max:1},flags:{"self-subscribe":{type:"string",value:"yes|no",description:"users follow it from the app (default no: the project adds them)"}},scope:"environment",async run(e){let t=await h(e),n=Tt(e.flags["self-subscribe"],"self-subscribe");await b(e,t,`make the topic ${e.args[0]}`);let s=await c(e,t,"POST","/notify/topics",{name:e.args[0],...n!==void 0?{self_subscribe:n}:{}},R());e.out.result(s,()=>e.out.line(`Made the topic ${s.name}${s.self_subscribe?", followed from the app":""}.`))}},Si={name:"notify:topics:delete",summary:"delete a topic and who follows it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`delete the topic ${e.args[0]} and who follows it`),await c(e,t,"DELETE",`/notify/topics/${_(e.args[0])}`,void 0,R()),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the topic ${e.args[0]}.`))}},ki={name:"notify:settings:get",summary:"the environment's message settings: the default locale, who email is from, where texts may go",scope:"environment",async run(e){let t=await h(e),{body:n}=await ce(e,t,"/notify/settings");e.out.result(n,()=>Pt(e,n))}},$i={name:"notify:settings:set",summary:'change message settings: key=value (from_email=\u2026, sms_countries=["+90","+44"], mx_check=true)',usage:"<key=value>\u2026",details:["The change is made on the revision read: one made beside it in the meantime is refused, not overwritten."],args:{min:1,max:20},scope:"environment",async run(e){let t=await h(e),n=et(e.args),{body:s,etag:r}=await ce(e,t,"/notify/settings"),a=nt(s,n),i=ze(s,a);if(i.length===0)return We(e,"the message settings",s);await b(e,t,`set the message settings ${i.join(", ")}`);let o=await c(e,t,"PUT","/notify/settings",a,{...oe(r),...R()});e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${le(s[l])} \u2192 ${le(o[l])}`)})}},fc=new Set(["sms_countries","sms_blocked_countries"]);function yc(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Ai={name:"notify",async read(e,t){let[n,s,r,a,i,o]=await Promise.all([c(e,t,"GET","/notify/settings"),c(e,t,"GET","/notify/providers"),c(e,t,"GET","/notify/templates/email"),c(e,t,"GET","/notify/templates/sms"),c(e,t,"GET","/notify/templates/whatsapp"),tr(e,t)]),l={};for(let[p,f]of Object.entries(yc(n)))l[`settings.${p}`]=f;for(let p of s.providers)l[`providers.${p.channel}/${p.provider}`]={active:p.active,priority:p.priority,credentials:"set"};let d=r.templates.filter(p=>p.customized),u=await Promise.all(d.map(p=>c(e,t,"GET",`/notify/templates/email/${_(p.slug)}/${_(p.locale)}`)));for(let p of u)l[`templates.email/${p.slug}/${p.locale}`]={subject:p.subject,html_body:p.html_body,text_body:p.text_body};for(let p of a.templates.filter(f=>f.customized))l[`templates.sms/${p.slug}/${p.locale}`]={body:p.body};for(let p of i.templates)l[`templates.whatsapp/${p.slug}/${p.locale}`]={name:p.name,language:p.language,variables:p.variables,code_button:p.code_button};for(let p of o)l[`topics.${p.name}`]={self_subscribe:p.self_subscribe};return l},async copy(e,t,n,s){let r=Q(n,"settings.");if(r.length>0){let{body:o,etag:l}=await ce(e,t,"/notify/settings"),d={...o};for(let u of r)d[u.name]=fc.has(u.name)&&Array.isArray(u.from)&&u.from.length===0?null:u.from;await x(s,`set the message settings ${r.map(u=>u.name).join(", ")}`,async()=>{await c(e,t,"PUT","/notify/settings",d,{...oe(l),...R()})})}for(let o of Q(n,"providers.")){let[l,d]=o.name.split("/");o.from===void 0?s.left(`the ${l} provider ${d}: only in the target, left as it is (it holds credentials)`):s.left(`the ${l} provider ${d}: credentials are not copied \u2014 set them with palbase notify:providers:set ${l} ${d} < credentials.json`)}for(let o of Q(n,"templates.")){let l=`/notify/templates/${o.name.split("/").map(_).join("/")}`;o.from===void 0?await x(s,`remove the template override ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())}):await x(s,`override the template ${o.name}`,async()=>{await c(e,t,"PUT",l,o.from,R())})}let a=Q(n,"topics."),i=a.some(o=>o.from===void 0)?new Map((await tr(e,t)).map(o=>[o.name,o.subscribers])):new Map;for(let o of a){let l=`/notify/topics/${_(o.name)}`,d=o.from?.self_subscribe;if(o.from===void 0){let u=i.get(o.name)??0;u>0?s.left(`the topic ${o.name}: only in the target, and ${u} users follow it \u2014 delete it with notify:topics:delete`):await x(s,`delete the topic ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())})}else o.change==="only_in_from"?await x(s,`make the topic ${o.name}`,async()=>{await c(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:d},R())}):await x(s,`set the topic ${o.name} ${d?"followed from the app":"followed by the project's adding"}`,async()=>{await c(e,t,"PATCH",l,{self_subscribe:d},R())})}}};var Ct=["max_connections","max_events_per_second","max_joins_per_second","max_channels_per_connection"];function bc(e){if(e.kind==="owner")return"the user it names: everything";let t=[e.publish?"publish":"",e.state_read?"read state":"",e.state_write?"write state":""].filter(n=>n!=="");return`any signed-in user: subscribe${t.length>0?`, ${t.join(", ")}`:""}`}function rr(e){return Object.fromEntries(Ct.map(t=>[t,e[t]??null]))}var Pi={name:"realtime:channels:get",summary:"the channel declaration, in the order patterns are matched: who may join each, publish, read and write its state",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/realtime/channels");e.out.result(n,()=>{n.channels.length===0&&e.out.line("No channel is declared: a channel no pattern matches belongs to no one."),e.out.table(n.channels,[{header:"pattern",value:s=>s.pattern},{header:"kind",value:s=>s.kind},{header:"who",value:s=>bc(s)}])})}},Ri={name:"realtime:channels:set",summary:'replace the channel declaration with a JSON file or stdin ({"channels": [\u2026]} or the list)',usage:"[--file <path>]",details:['Each channel: {"pattern": "room:{id}", "kind": "public", "publish": true, "state_read": true, "state_write": false}, or {"pattern": "user:{uid}", "kind": "owner"}.',"realtime:channels:get --json gives the document to edit. Open sockets are decided again within a heartbeat."],flags:{file:{type:"string",value:"path",description:"the declaration (default: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=he(await ve(e,e.flags.file,"the channel declaration"),"the channel declaration"),s=Array.isArray(n)?n:n.channels;if(!Array.isArray(s))throw g('the declaration is {"channels": [\u2026]} or a list of channels');let r=await c(e,t,"GET","/realtime/channels");if(Re(r.channels,s))return We(e,"the channel declaration",r);await b(e,t,`declare ${s.length} channel pattern(s) (now ${r.channels.length})`);let a=await c(e,t,"PUT","/realtime/channels",{channels:s});e.out.result(a,()=>e.out.line(a.channels.length===0?"No channel is declared now.":`Declared ${a.channels.length} channel pattern(s): ${a.channels.map(i=>i.pattern).join(", ")}.`))}},Ti={name:"realtime:limits:get",summary:"the limits the environment holds its clients to (null: the platform's), and the platform's",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/realtime/limits");e.out.result(n,()=>{let s=n.platform??{},r=Math.max(...Ct.map(a=>a.length));for(let a of Ct)e.out.line(`${a.padEnd(r)} ${n[a]===void 0||n[a]===null?"the platform's":le(n[a])}${s[a]!==void 0?` (platform ${s[a]===0?"no ceiling":le(s[a])})`:""}`)})}},Ei={name:"realtime:limits:set",summary:"change realtime limits: key=value (max_connections=500), null for the platform's",usage:"<key=value>\u2026",details:[`The keys: ${Ct.join(", ")}. A value above the platform's has no effect beyond it.`],args:{min:1,max:4},scope:"environment",async run(e){let t=await h(e),n=et(e.args);for(let o of n)if(o.path.length!==1||!Ct.includes(o.path[0]))throw g(`no limit ${JSON.stringify(o.path.join("."))}`,`the limits: ${Ct.join(", ")}`);let s=rr(await c(e,t,"GET","/realtime/limits")),r=nt(s,n),a=ze(s,r);if(a.length===0)return We(e,"the realtime limits",s);await b(e,t,`set the realtime limits ${a.join(", ")}`);let i=await c(e,t,"PUT","/realtime/limits",r);e.out.result({limits:i,changed:a},()=>{for(let o of a)e.out.line(`${o}: ${le(s[o]??"the platform's")} \u2192 ${le(i[o]??"the platform's")}`)})}},Ci={name:"realtime",async read(e,t){let[n,s]=await Promise.all([c(e,t,"GET","/realtime/channels"),c(e,t,"GET","/realtime/limits")]),r={channels:n.channels};for(let[a,i]of Object.entries(rr(s)))r[`limits.${a}`]=i;return r},async copy(e,t,n,s){let r=n.find(i=>i.key==="channels");r&&await x(s,"declare the channels",async()=>{await c(e,t,"PUT","/realtime/channels",{channels:r.from??[]})});let a=Q(n,"limits.");if(a.length>0){let i=rr(await c(e,t,"GET","/realtime/limits"));for(let o of a)i[o.name]=o.from??null;await x(s,`set the realtime limits ${a.map(o=>o.name).join(", ")}`,async()=>{await c(e,t,"PUT","/realtime/limits",i)})}}},ji={name:"analytics:settings:get",summary:"how long analytics keeps events and console records, the share of performance samples kept, whether bodies are kept",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/analytics/settings");e.out.result(n,()=>Pt(e,n))}},Oi={name:"analytics:settings:set",summary:"change analytics settings: key=value (retention_days=30, perf_sample_pct=10, console_bodies=false)",usage:"<key=value>\u2026",details:["A shorter retention removes what is older within minutes."],args:{min:1,max:4},scope:"environment",async run(e){let t=await h(e),n=et(e.args),s=await c(e,t,"GET","/analytics/settings"),r=nt(s,n),a=ze(s,r);if(a.length===0)return We(e,"the analytics settings",s);await b(e,t,`set the analytics settings ${a.join(", ")}`);let i=await c(e,t,"PUT","/analytics/settings",r);e.out.result({settings:i,changed:a},()=>{for(let o of a)e.out.line(`${o}: ${le(s[o])} \u2192 ${le(i[o])}`)})}},Di={name:"analytics",async read(e,t){let n=await c(e,t,"GET","/analytics/settings");return Object.fromEntries(Object.entries(n).map(([s,r])=>[`settings.${s}`,r]))},async copy(e,t,n,s){let r=Q(n,"settings.");if(r.length===0)return;let a=await c(e,t,"GET","/analytics/settings");for(let i of r)i.from!==void 0&&(a[i.name]=i.from);await x(s,`set the analytics settings ${r.map(i=>i.name).join(", ")}`,async()=>{await c(e,t,"PUT","/analytics/settings",a)})}};function wc(e){return{source:{repository:e.source.repository.toLowerCase(),branch:e.source.branch,path:e.source.path??"",context:e.source.context??""},size:e.size,replicas:e.replicas,port:e.port,health_path:e.health_path??"",env:e.env??{},secrets:e.secrets??"",database:e.database?{name:e.database.name,migrations:e.database.migrations===!0}:null,deploy_after_checks:e.deploy_after_checks===!0,deploy_checks:[...e.deploy_checks??[]].sort(),deploy_checks_app:e.deploy_checks_app??"github-actions"}}var Ii={name:"machines",async read(e,t){let{machines:n}=await c(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.map(s=>[`machines.${s.name}`,wc(s)]))},async copy(e,t,n,s){for(let r of Q(n,"machines.")){if(r.change==="only_in_to"){s.left(`the machine ${r.name}: only the target has it \u2014 it serves on (palbase machines:delete ${r.name} takes it away)`);continue}let a=r.from;if(r.change==="only_in_from"){await x(s,`opened the machine ${r.name}, built from ${a.source.branch}'s head`,()=>c(e,t,"POST","/machines",{name:r.name,...a,database:a.database??void 0},R()));continue}let i=r.to,o=Object.fromEntries(Object.keys(a).filter(l=>!Re(a[l],i[l])).map(l=>[l,l==="database"&&a[l]===null?{name:""}:a[l]]));await x(s,`changed the machine ${r.name}: ${Object.keys(o).join(", ")} (a new version rolls out beside the one serving)`,()=>c(e,t,"PATCH",`/machines/${_(r.name)}`,o))}}};function vc(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function _c(e,t,n,s){let r=await c(e,t,"POST","/databases",{name:n,...s},R());await e.state.keepDatabasePasswords(t.env_ref,r.name,{owner:r.credentials.owner.password,app:r.credentials.app.password})}var Li={name:"databases",async read(e,t){let{databases:n}=await c(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.map(s=>[`databases.${s.name}`,vc(s)]))},async copy(e,t,n,s){for(let r of Q(n,"databases.")){if(r.change==="only_in_to"){s.left(`the database ${r.name}: only the target has it \u2014 its data stays (palbase databases:delete ${r.name} takes it away)`);continue}let a=r.from;if(r.change==="only_in_from"){await x(s,`opened the database ${r.name} (${a.size}; its passwords kept on this computer \u2014 palbase db:copy brings its data)`,()=>_c(e,t,r.name,a));continue}let i=r.to,o=Object.fromEntries(["size","allow","backup_days"].filter(d=>!Re(a[d],i[d])).map(d=>[d,a[d]]));if(Object.keys(o).length===0)continue;let l=o.size===void 0?"":` (moving to ${o.size}, serving on)`;await x(s,`changed the database ${r.name}: ${Object.keys(o).join(", ")}${l}`,()=>c(e,t,"PATCH",`/databases/${_(r.name)}`,o))}}};var Sc=/^[a-z][a-z0-9-]{1,62}$/;function jt(e){if(!Sc.test(e))throw g(`${JSON.stringify(e)} is not a bucket's name: lowercase letters, digits and dashes, starting with a letter (2\u201363)`);return`/storage/buckets/${_(e)}`}function ir(e){if(e==null)return"platform (5 GiB)";for(let[t,n]of[["GiB",2**30],["MiB",2**20],["KiB",2**10]])if(e>=n&&e%n===0)return`${e/n} ${t}`;return`${e} B`}function kc(e){if(e===""||e==="platform")return null;let t=/^(\d+(?:\.\d+)?)\s*(b|kb|kib|mb|mib|gb|gib)?$/i.exec(e.trim());if(!t)throw g(`--max-size ${JSON.stringify(e)} is not a size: 10MB, 512KiB, 1GB, a number of bytes, or platform`);let n=(t[2]??"b").toLowerCase(),s={b:1,kb:1e3,kib:2**10,mb:1e6,mib:2**20,gb:1e9,gib:2**30};return Math.round(Number(t[1])*s[n])}function ar(e){return{public:e.public,file_size_limit:e.file_size_limit??null,allowed_mime_types:e.allowed_mime_types??[],variants:e.variants??[]}}async function $c(e,t,n){try{return await c(e,t,"GET",jt(n))}catch(s){if(s instanceof Y&&s.status===404)return;throw s}}var Ni={name:"storage:buckets:list",summary:"the environment's buckets: who may read each, the largest file and the types it takes",scope:"environment",async run(e){let t=await h(e),{buckets:n}=await c(e,t,"GET","/storage/buckets");e.out.result({buckets:n},()=>{n.length===0&&e.out.line("No bucket yet: palbase storage:buckets:set <name> makes one."),e.out.table(n,[{header:"bucket",value:s=>s.name},{header:"read by",value:s=>s.public?"anyone":"signed URLs, rules"},{header:"largest file",value:s=>ir(s.file_size_limit)},{header:"types",value:s=>s.allowed_mime_types.length===0?"any":s.allowed_mime_types.join(", ")},{header:"renditions",value:s=>s.variants.map(r=>r.name).join(", ")}])})}},qi={name:"storage:buckets:get",summary:"a bucket's declaration in full, with its renditions",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",jt(e.args[0]));e.out.result(n,()=>{e.out.line(n.name),e.out.line(` read by ${n.public?"anyone, at /storage/public/":"signed URLs and the rules"}`),e.out.line(` largest file ${ir(n.file_size_limit)}`),e.out.line(` types ${n.allowed_mime_types.length===0?"any":n.allowed_mime_types.join(", ")}`);for(let s of n.variants)e.out.line(` rendition ${s.name}: ${s.width}\xD7${s.height} ${s.fit}, ${s.format}${s.quality?` q${s.quality}`:""}`)})}},Mi={name:"storage:buckets:set",summary:"make a bucket, or change one: public or private, the largest file, the content types, its renditions",usage:"<bucket> [--public|--private] [--max-size <size>] [--types <a/b,c/*>] [--variants-file <path>]",details:["What is not given is kept as it is (a new bucket: private, the platform's size, any type, no rendition)."],args:{min:1,max:1},flags:{public:{type:"boolean",description:"anyone may read its files at /storage/public/"},private:{type:"boolean",description:"its files are read with signed URLs and the rules"},"max-size":{type:"string",value:"size",description:"the largest file: 10MB, 512KiB, 1GB, bytes, or platform"},types:{type:"string",value:"list",description:'the content types it takes, comma-separated (image/* a family); "" for any'},"variants-file":{type:"string",value:"path",description:"its renditions, a JSON list of {name, width, height, fit, format, quality}"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(e.flags.public===!0&&e.flags.private===!0)throw g("--public and --private are one or the other");let s=await $c(e,t,n),r=ar(s??{name:n,public:!1,allowed_mime_types:[],variants:[]});if(e.flags.public===!0&&(r.public=!0),e.flags.private===!0&&(r.public=!1),e.flags["max-size"]!==void 0&&(r.file_size_limit=kc(e.flags["max-size"])),e.flags.types!==void 0&&(r.allowed_mime_types=me(e.flags.types)??[]),e.flags["variants-file"]!==void 0){let i=he(await ve(e,e.flags["variants-file"],"the renditions"),"the renditions file"),o=Array.isArray(i)?i:i.variants;if(!Array.isArray(o))throw g("the renditions file is a JSON list of {name, width, height, fit, format, quality}");r.variants=o}if(s&&Re(ar(s),r)){e.out.result(s,()=>e.out.line(`Nothing to change: ${n} already is so.`));return}await b(e,t,`${s?"change":"make"} the bucket ${n} (${r.public?"public":"private"})`);let a=await c(e,t,"PUT",jt(n),r);e.out.result(a,()=>e.out.line(`${s?"Changed":"Made"} the bucket ${a.name}: ${a.public?"public":"private"}, files up to ${ir(a.file_size_limit)}, ${a.allowed_mime_types.length===0?"any type":a.allowed_mime_types.join(", ")}.`))}},Ui={name:"storage:buckets:delete",summary:"delete an empty bucket (one that still holds files is refused)",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=jt(e.args[0]);await b(e,t,`delete the bucket ${e.args[0]}`),await c(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},xi={name:"storage",async read(e,t){let{buckets:n}=await c(e,t,"GET","/storage/buckets"),s={};for(let r of n){let a=ar(r);s[`buckets.${r.name}`]={...a,allowed_mime_types:[...a.allowed_mime_types].sort()}}return s},async copy(e,t,n,s){for(let r of Q(n,"buckets."))r.from===void 0?await x(s,`delete the bucket ${r.name}`,async()=>{await c(e,t,"DELETE",jt(r.name))}):await x(s,`declare the bucket ${r.name}`,async()=>{await c(e,t,"PUT",jt(r.name),r.from)})}};var Ot=[ei,xi,ui,Ai,Ci,Di],Kn=[Li,Ii],Ki={from:{type:"string",value:"env",description:"the environment compared from (slug or ref)"},to:{type:"string",value:"env",description:"the environment compared to (slug or ref)"},only:{type:"string",value:"modules",description:`only these, comma-separated: ${Ot.map(e=>e.name).join(",")} (the default), and ${Kn.map(e=>e.name).join(",")} when named`}};async function Gi(e){if(e.global.env!==void 0)throw g("the two environments are named with --from and --to, not -E");let t=e.flags.from,n=e.flags.to;if(t===void 0||n===void 0)throw g("name both environments: --from <env> --to <env>");let s=await Me(e.studio),r;if(e.global.project!==void 0)r=mt(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new m("no_target","which project? none is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});r=s.filter(d=>d.project_id===l.target.project_id)}let a=l=>{let d=r.find(u=>u.slug===l||u.ref===l);if(!d)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(l)}`,{hint:`its environments: ${r.map(u=>u.slug).join(", ")}`});return ue(d)},i=a(t),o=a(n);if(i.env_ref===o.env_ref)throw g("--from and --to name the same environment");return{from:i,to:o}}function Bi(e){let t=me(e.flags.only);if(t===void 0)return Ot;let n=[...Ot,...Kn];for(let s of t)if(!n.some(r=>r.name===s))throw g(`no module ${JSON.stringify(s)}`,`the modules: ${n.map(r=>r.name).join(", ")}`);return n.filter(s=>t.includes(s.name))}function Ac(e,t){let n=[...new Set([...Object.keys(e),...Object.keys(t)])].sort(),s=[];for(let r of n){let a=r in e,i=r in t;a&&!i?s.push({key:r,change:"only_in_from",from:e[r]}):!a&&i?s.push({key:r,change:"only_in_to",to:t[r]}):Re(e[r],t[r])||s.push({key:r,change:"differs",from:e[r],to:t[r]})}return s}async function or(e,t,n,s){let r=l=>Promise.all(t.map(d=>d.read(e,l))),[a,i]=await Promise.all([r(n),r(s)]),o=Object.fromEntries(t.map((l,d)=>[l.name,Ac(a[d],i[d])]));return{from:n,to:s,modules:o,count:Object.values(o).reduce((l,d)=>l+d.length,0)}}function lr(e){return{from:{env:e.from.env_slug,ref:e.from.env_ref},to:{env:e.to.env_slug,ref:e.to.env_ref},equal:e.count===0,differences:e.count,modules:e.modules}}function xn(e){let t=JSON.stringify(e)??"undefined";return t.length>140?`${t.slice(0,139)}\u2026`:t}function dr(e,t){let n=t.from.env_slug,s=t.to.env_slug;for(let[a,i]of Object.entries(t.modules))if(i.length!==0){e.out.line(`${a} (${i.length})`);for(let o of i)o.change==="differs"?e.out.line(` ~ ${o.key}: ${n} ${xn(o.from)} \xB7 ${s} ${xn(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${xn(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${xn(o.to)}`)}let r=Object.keys(t.modules).join(", ");e.out.line(t.count===0?`${n} and ${s} have the same settings (${r}).`:`${t.count} difference(s) between ${n} and ${s} (${r}).`)}var Pc={name:"config:diff",summary:"how two environments' module settings differ (auth, storage, flags, notify, realtime, analytics; machines, databases when named)",usage:"--from <env> --to <env> [--only <modules>]",details:["- a key only in --from, + only in --to, ~ in both with other values. Ids, times and versions are not compared; a secret is compared as set or not, never by its value.","Exit code 0 when they are the same, 1 when they differ. --json gives each difference with both values."],flags:Ki,scope:"environment",async run(e){let{from:t,to:n}=await Gi(e),s=await or(e,Bi(e),t,n);return e.out.result(lr(s),()=>dr(e,s)),s.count===0?0:1}},Rc={name:"config:copy",summary:"make one environment's module settings equal to another's (secrets excepted: their names are said)",usage:"--from <env> --to <env> [--only <modules>]",details:["It shows the differences, asks before changing a production environment (-y confirms), changes the target, and compares again.","A secret is never copied: a provider's credentials and a sign-in client's secret are listed by name for you to set. A new hook or OAuth client gets its own secret, printed this once.","What the target holds that its users depend on is not taken by a copy: a bucket with files, a role users hold, a topic users follow are left and said.","--only machines,databases copies what they are declared as \u2014 a machine's source, size, copies, settings, secrets' name, database; a database's size, who may connect, backups \u2014 never a database's data (palbase db:copy) nor a secret's value. A machine or database only the target has is left serving.","Exit code 0 when the two are the same afterwards, 1 when a difference remains."],flags:Ki,scope:"environment",async run(e){let{from:t,to:n}=await Gi(e);return pr(e,Bi(e),t,n)}};async function pr(e,t,n,s,r={}){let a=await or(e,t,n,s);if(a.count===0)return e.out.result({...r,copied:[],left:[],shown_once:[],after:lr(a)},()=>e.out.line(`Nothing to copy: ${n.env_slug} and ${s.env_slug} have the same settings.`)),0;e.out.json||dr(e,a);let i=t.filter(p=>a.modules[p.name].length>0).map(p=>p.name);await b(e,s,`make its ${i.join(", ")} settings equal to ${n.env_slug}'s (${a.count} difference(s))`);let o=[],l=[],d=[];e.out.json||e.out.line();for(let p of t){let f=a.modules[p.name];if(f.length===0)continue;let y={done(v){o.push(`${p.name}: ${v}`),e.out.json||e.out.line(`copied ${p.name}: ${v}`)},left(v){l.push(`${p.name}: ${v}`),e.out.json||e.out.line(`left ${p.name}: ${v}`)},shown(v,T){d.push({what:`${p.name}: ${v}`,value:T}),e.out.json||e.out.line(` ${p.name}: ${v} ${T} (shown this once)`)}};await p.copy(e,s,f,y)}let u=await or(e,t,n,s);return e.out.result({...r,copied:o,left:l,shown_once:d,after:lr(u)},()=>{e.out.line(),e.out.line("Compared again:"),dr(e,u)}),u.count===0?0:1}var Ji=[qa,Ma,Ua,xa,Ga,Ka,Ba,Ja,za,Fa,Va,Ha,Xa,Ya,Za,Qa,Ni,qi,Mi,Ui,ri,ai,ii,oi,di,pi,li,mi,gi,hi,fi,yi,bi,wi,vi,_i,Si,ki,$i,Pi,Ri,Ti,Ei,ji,Oi,Pc,Rc];async function ur(e){return(await e.studio.get("/orgs")).organizations}async function at(e,t){let n=await ur(e);if(t===void 0){if(n.length===1)return n[0];throw g(n.length===0?"you are in no organization yet: make one with palbase orgs:create <name>":"which organization? pass --org <name>",`yours: ${n.map(r=>r.name).join(", ")}`)}let s=n.filter(r=>r.id===t||r.name.localeCompare(t,void 0,{sensitivity:"accent"})===0);if(s.length===0)throw new m("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new m("ambiguous_org",`${JSON.stringify(t)} names more than one of your organizations`,{hint:"pass its id"});return s[0]}var zi={name:"orgs:list",summary:"your organizations and your role in each",scope:"account",async run(e){let t=await ur(e);e.out.result({organizations:t},()=>{t.length===0&&e.out.line("You are in no organization yet: palbase orgs:create <name> makes one."),e.out.table(t,[{header:"name",value:n=>n.name},{header:"role",value:n=>n.role},{header:"id",value:n=>n.id}])})}},Wi={name:"orgs:create",summary:"make an organization; you are its owner",usage:"<name>",args:{min:1,max:1},scope:"account",async run(e){let t=await e.studio.post("/orgs",{name:e.args[0]});e.out.result(t,()=>e.out.line(`Made the organization ${G(t.name)} (${t.id}).`))}},Fi={name:"orgs:members:list",summary:"an organization's members, and the invitations still open (for its admins)",flags:{org:{type:"string",value:"org",description:"the organization (name or id); needed when you are in more than one"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=await e.studio.get(`/orgs/${t.id}/members`);e.out.result(n,()=>{e.out.table(n.members,[{header:"email",value:s=>s.email??""},{header:"role",value:s=>s.role},{header:"user",value:s=>s.user_id}]),n.invitations.length>0&&(e.out.line(),e.out.line("Invited:"),e.out.table(n.invitations,[{header:"email",value:s=>s.email},{header:"role",value:s=>s.role},{header:"invitation",value:s=>s.id}]))})}},Vi={name:"orgs:members:invite",summary:"invite someone to an organization by email; they accept in Studio",usage:"<email> [--role admin|member]",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"admin or member (default member)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw g("--role is admin or member");let s=await e.studio.post(`/orgs/${t.id}/invitations`,{email:e.args[0],role:n});e.out.result(s,()=>e.out.line(`Invited ${G(s.email)} to ${G(t.name)} as ${s.role}.`))}},Hi={name:"orgs:members:remove",summary:"take a member (by email or user id) or an open invitation out of an organization",usage:"<email|user-id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.args[0],s=await e.studio.get(`/orgs/${t.id}/members`),r=s.members.find(i=>i.user_id===n||i.email!==null&&i.email.toLowerCase()===n.toLowerCase());if(r){await e.studio.request("DELETE",`/orgs/${t.id}/members/${encodeURIComponent(r.user_id)}`),e.out.result({removed:r},()=>e.out.line(`${G(r.email??r.user_id)} is no longer in ${G(t.name)}.`));return}let a=s.invitations.find(i=>i.id===n||i.email.toLowerCase()===n.toLowerCase());if(!a)throw new m("no_such_member",`${JSON.stringify(n)} is neither a member of ${t.name} nor invited to it`);await e.studio.request("DELETE",`/orgs/${t.id}/invitations/${encodeURIComponent(a.id)}`),e.out.result({withdrawn:a},()=>e.out.line(`The invitation of ${G(a.email)} is withdrawn.`))}},Xi={name:"tokens:list",summary:"an organization's access tokens (PALBASE_TOKEN for CI and agents): name, role, when they end and were last used \u2014 never their values",flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`);e.out.result({tokens:n},()=>{n.length===0?e.out.line(`${G(t.name)} has no access token yet: palbase tokens:create <name> makes one.`):e.out.table(n,[{header:"name",value:s=>G(s.name)},{header:"role",value:s=>s.role},{header:"token",value:s=>`pbo_\u2026${s.hint}`},{header:"ends",value:s=>s.expires_at.slice(0,10)},{header:"last used",value:s=>s.last_used_at?.slice(0,16).replace("T"," ")??"never"},{header:"id",value:s=>s.id}])})}},Yi={name:"orgs:audit",summary:"who changed what at an organization's environments, through Studio or the CLI, newest first \u2014 its admins'",usage:"[--org <org>] [--limit <n>]",details:["A module keeps a change made through Studio as Studio's key's: this says whose it was. What was sent is not kept."],flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.limit===void 0?50:Number(e.flags.limit);if(!Number.isInteger(n)||n<1||n>1e4)throw g("--limit is a whole number from 1 to 10000");let s=[],r=null;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{before:r}:{}}),i=await e.studio.get(`/orgs/${t.id}/audit?${a}`);s.push(...i.entries),r=i.next_before}while(r&&s.length<n);e.out.result({entries:s},()=>{s.length===0?e.out.line(`Nothing changed at ${G(t.name)}'s environments yet.`):e.out.table(s,[{header:"when",value:a=>a.at.slice(0,16).replace("T"," ")},{header:"who",value:a=>G(a.actor_name??a.actor)},{header:"environment",value:a=>G(a.environment_ref)},{header:"what",value:a=>G(`${a.method} ${a.path}`)},{header:"answer",value:a=>String(a.status)}])})}},Zi={name:"tokens:create",summary:"make an access token of an organization for CI or an agent; its value is shown this once",usage:"<name> [--role member|admin] [--days <n>]",details:["Give it to the CI as the secret PALBASE_TOKEN: the CLI acts with it in place of a sign-in. It reaches its organization's projects and environments at its role, and nothing of its people or tokens.","It ends after --days (default 90, at most 365), and goes when it is revoked or when the one who made it leaves the organization."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"member (default) or admin: what it may change"},days:{type:"string",value:"n",description:"how many days it is good for (default 90)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw g("--role is member or admin");let s=e.flags.days===void 0?90:Number(e.flags.days);if(!Number.isInteger(s)||s<1||s>365)throw g("--days is a whole number from 1 to 365");let r=await e.studio.post(`/orgs/${t.id}/tokens`,{name:e.args[0],role:n,expires_in_days:s});e.out.result(r,()=>{e.out.line(`Made the access token ${G(r.name)} of ${G(t.name)} (${r.role}, until ${r.expires_at.slice(0,10)}).`),e.out.line(`token (shown this once \u2014 keep it as the CI's secret PALBASE_TOKEN): ${r.token}`)})}},Qi={name:"tokens:revoke",summary:"revoke an organization's access token (by its name or id): refused from its next request on",usage:"<name|id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`),s=n.filter(r=>r.id===e.args[0]||r.name===e.args[0]);if(s.length===0)throw new m("no_such_token",`${G(t.name)} has no access token ${JSON.stringify(e.args[0])}`,{hint:"palbase tokens:list shows them"});if(s.length>1)throw new m("ambiguous_token",`${JSON.stringify(e.args[0])} names more than one token`,{hint:"pass its id"});await e.studio.request("DELETE",`/orgs/${t.id}/tokens/${encodeURIComponent(s[0].id)}`),e.out.result({revoked:s[0].id},()=>e.out.line(`Revoked the access token ${G(s[0].name)} of ${G(t.name)}.`))}},eo={name:"projects:list",summary:"every project of your organizations, with its environments",scope:"account",async run(e){let t=await Me(e.studio),n=await e.state.targetFor(e.io.cwd),s=(await e.studio.get("/projects")).projects,r=new Map(t.map(a=>[a.organization_id,a.org_name]));for(let a of await ur(e))r.set(a.id,a.name);e.out.result({projects:s.map(a=>({...a,organization_name:r.get(a.organization_id)??null}))},()=>{s.length===0&&e.out.line("No project yet: palbase projects:create <name> makes one.");let a=s.flatMap(i=>i.environments.length===0?[{org:r.get(i.organization_id)??"",project:i.name,env:"(none)",ref:"",here:""}]:i.environments.map(o=>({org:r.get(i.organization_id)??"",project:i.name,env:o.slug,ref:o.ref,here:n?.target.env_ref===o.ref?"*":""})));e.out.table(a,[{header:"",value:i=>i.here},{header:"organization",value:i=>i.org},{header:"project",value:i=>i.project},{header:"env",value:i=>i.env},{header:"ref",value:i=>i.ref}])})}},to={name:"projects:create",summary:"make a project with its first environment (main), and print its keys",usage:"<name> [--org <org>] [--env-name <name>]",details:["The first environment is the project's production one: its slug is always main.","Its secret key is printed this once; keys:create makes more."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},"env-name":{type:"string",value:"name",description:"what the first environment is called on screen (default Production)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{project:n}=await e.studio.post(`/orgs/${t.id}/projects`,{name:e.args[0]}),s=await e.studio.post(`/projects/${n.id}/environments`,{name:e.flags["env-name"]??"Production"});e.out.result({project:n,environment:s.environment,keys:s.keys},()=>{e.out.line(`Made ${G(t.name)} \u203A ${G(n.name)} with its environment ${s.environment.slug} (ref ${s.environment.ref}).`),s.keys&&(e.out.line(`publishable key: ${s.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${s.keys.secret.key}`)),e.out.line(`palbase use ${n.name}/${s.environment.slug} chooses it for this directory.`)})}},no={name:"projects:delete",summary:"delete a project and every environment of it",usage:"<project> [-y]",details:["Its environments' data goes with them (the platform keeps it a week to bring back)."],args:{min:1,max:1},scope:"account",async run(e){let t=mt(await Me(e.studio),e.args[0]),n=t[0];if(e.out.note(`${n.org_name} \u203A ${n.project_name}: delete the project and its environments ${t.map(s=>s.slug).join(", ")}`),!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting a project is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the project's name to delete it: ")).trim()!==n.project_name)throw new m("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/projects/${n.project_id}`),e.out.result({deleted:{project_id:n.project_id,environments:t.map(s=>s.ref)}},()=>e.out.line(`Deleted ${G(n.project_name)}.`))}},so={name:"envs:list",summary:"a project's environments (the project chosen here, or -P)",scope:"environment",async run(e){let t=await Fe(e);e.out.result({environments:t},()=>e.out.table(t,[{header:"slug",value:n=>n.slug},{header:"name",value:n=>n.name},{header:"ref",value:n=>n.ref},{header:"your role",value:n=>n.role}]))}},ro={name:"envs:create",summary:"add an environment to a project (staging, a developer's own)",usage:"<slug> [--name <name>] [--from <env> [--with-cloud]]",details:["Each environment is a project of its own on the platform: its users, data, machines and keys are its own.","--from opens it with another environment's module settings (palbase config:copy: secrets are named, not copied); --with-cloud its machines and databases too \u2014 declared alike, built from the same branches, with no data (palbase db:copy brings it)."],args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called on screen (default: the slug)"},from:{type:"string",value:"env",description:"open it with this environment's settings (slug or ref)"},"with-cloud":{type:"boolean",description:"with --from: its machines and databases too (the new environment's GitHub link builds the machines)"}},scope:"environment",async run(e){let t=await Tc(e),n=e.args[0],s=e.flags.from;if(e.flags["with-cloud"]===!0&&s===void 0)throw g("--with-cloud goes with --from <env>");let r=s===void 0?[]:(await Me(e.studio)).filter(d=>d.project_id===t.project_id),a=s===void 0?void 0:r.find(d=>d.slug===s||d.ref===s);if(s!==void 0&&!a)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(s)}`,{hint:`its environments: ${r.map(d=>d.slug).join(", ")}`});let i=await e.studio.post(`/projects/${t.project_id}/environments`,{name:e.flags.name??n,slug:n}),o=()=>{e.out.line(`Made ${G(t.project_name)} \u203A ${i.environment.slug} (ref ${i.environment.ref}).`),i.keys&&(e.out.line(`publishable key: ${i.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${i.keys.secret.key}`))};if(!a){e.out.result(i,o);return}e.out.json||o();let l=ue({...a,ref:i.environment.ref,slug:i.environment.slug,name:i.environment.name});return e.out.json||e.out.line(),pr(e,e.flags["with-cloud"]===!0?[...Ot,...Kn]:Ot,ue(a),l,{made:i})}},ao={name:"envs:delete",summary:"delete an environment of a project, with its data",usage:"<slug>",args:{min:1,max:1},scope:"environment",async run(e){let n=(await Fe(e)).find(a=>a.slug===e.args[0]||a.ref===e.args[0]);if(!n)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=ue(n);if(e.out.note(`${M(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting an environment is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the environment's slug to delete it: ")).trim()!==n.slug)throw new m("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/environments/${n.ref}`);let r=await e.state.targetFor(e.io.cwd);r?.target.env_ref===n.ref&&await e.state.clearTarget(r.dir),e.out.result({deleted:n.ref},()=>e.out.line(`Deleted ${M(s)}.`))}},io={name:"use",summary:"choose the environment this directory acts on (kept on this computer)",usage:"[<project>[/<env>] | <org>/<project>/<env>] [--clear]",details:["Commands run in this directory, or a directory under it, then act on that environment unless -P/-E say otherwise.","Nothing is written into the project: two checkouts of it can each act on their own environment.","With no argument and a person at the terminal, it lists your environments to pick from."],args:{min:0,max:1},flags:{clear:{type:"boolean",description:"forget the choice made for this directory"}},scope:"account",async run(e){if(e.flags.clear===!0){let n=await e.state.clearTarget(e.io.cwd);e.out.result({cleared:n},()=>e.out.line(n?"This directory acts on no environment now.":"No environment was chosen for this directory."));return}let t;if(e.args.length===0)t=await h(e);else{let n=await Me(e.studio);t=ue(Ec(n,e.args[0]))}await e.state.setTarget(e.io.cwd,t),e.out.result({target:t,dir:e.io.cwd},()=>e.out.line(`This directory acts on ${M(t)}.`))}},oo={name:"status",summary:"the environment this directory acts on: who you are there, its machines and databases",scope:"environment",async run(e){let t=await h(e),n=await e.studio.get(`/environments/${t.env_ref}`),[s,r]=await Promise.all([e.studio.call(`/environments/${t.env_ref}/call`,{method:"GET",path:"/machines?limit=100"}),e.studio.call(`/environments/${t.env_ref}/call`,{method:"GET",path:"/databases?limit=100"})]),a=s.status===200?s.body.machines??[]:[],i=r.status===200?r.body.databases??[]:[],o=await e.state.targetFor(e.io.cwd);e.out.result({target:t,role:n.role,address:`https://${t.env_ref}.${e.cloud.domain}`,machines:a,databases:i,chosen_for:o?.dir??null},()=>{e.out.line(M(t)),e.out.line(` ref ${t.env_ref} \xB7 your role ${n.role} \xB7 https://${t.env_ref}.${e.cloud.domain}`),o&&o.target.env_ref===t.env_ref&&e.out.line(` chosen for ${o.dir}`),e.out.line(` machines: ${a.length===0?"none":a.map(l=>`${l.name} (${l.size}, ${l.state})`).join(", ")}`),e.out.line(` databases: ${i.length===0?"none":i.map(l=>`${l.name} (${l.size}, ${l.state})`).join(", ")}`)})}},lo={name:"open",summary:"open the environment in Studio (its overview, or a section: auth, storage, flags, \u2026)",usage:"[section]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0]?`/${e.args[0].replace(/^\/+/,"")}`:"",s=`${e.cloud.studioWeb}/projects/${t.project_id}/environments/${t.env_ref}${n}`,r=e.interactive&&await e.io.openBrowser(s);e.out.result({url:s,opened:r},()=>e.out.line(r?`Opened ${s}`:s))}},po={name:"keys:list",summary:"the environment's API keys (never their values)",scope:"environment",async run(e){let t=await h(e),{keys:n}=await e.studio.get(`/environments/${t.env_ref}/keys`);e.out.result({keys:n},()=>e.out.table(n,[{header:"id",value:s=>s.id},{header:"kind",value:s=>s.kind},{header:"label",value:s=>s.label},{header:"ends",value:s=>s.hint?`\u2026${s.hint}`:""},{header:"state",value:s=>s.revoked_at?`revoked ${s.revoked_at}`:s.expires_at?`until ${s.expires_at}`:"active"},{header:"created",value:s=>s.created_at}]))}},uo={name:"keys:create",summary:"make an API key of the environment; its value is shown this once",usage:"--kind publishable|secret [--label <label>]",flags:{kind:{type:"string",value:"kind",description:"publishable (ships in apps) or secret (servers only)"},label:{type:"string",value:"label",description:"what it is for, as Studio lists it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!=="publishable"&&t!=="secret")throw g("--kind is publishable or secret");let n=await h(e);await b(e,n,`make a ${t} key`);let s=await e.studio.post(`/environments/${n.env_ref}/keys`,{kind:t,label:e.flags.label??""});e.out.result(s,()=>{e.out.line(`${s.kind} key ${s.id}${s.kind==="secret"?" (shown this once)":""}:`),e.out.line(s.key)})}},co={name:"keys:revoke",summary:"revoke an API key of the environment; what holds it is refused from then on",usage:"<key-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`revoke the key ${e.args[0]}`),await e.studio.request("DELETE",`/environments/${t.env_ref}/keys/${encodeURIComponent(e.args[0])}`),e.out.result({revoked:e.args[0]},()=>e.out.line(`Revoked ${e.args[0]}.`))}};async function Tc(e){return(await Fe(e))[0]}async function Fe(e){let t=await Me(e.studio);if(e.global.project!==void 0)return mt(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new m("no_target","no project is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});let s=t.filter(r=>r.project_id===n.target.project_id);if(s.length===0)throw new m("no_such_project",`the project chosen here (${n.target.project_name}) is not among yours any more`,{hint:"palbase use another"});return s}function Ec(e,t){let n=t.split("/");if(n.length>3||n.some(a=>a===""))throw g(`${JSON.stringify(t)} is not project, project/env or org/project/env`);let s=n.length===3?[{project:`${n[0]}/${n[1]}`,env:n[2]}]:n.length===2?[{project:n[0],env:n[1]},{project:t}]:[{project:t}],r;for(let a of s){let i;try{i=mt(e,a.project)}catch(o){r=o;continue}if(a.env!==void 0){let o=i.find(l=>l.slug===a.env||l.ref===a.env);if(o)return o;r=new m("no_such_environment",`${i[0].project_name} has no environment ${JSON.stringify(a.env)}`,{hint:`its environments: ${i.map(l=>l.slug).join(", ")}`});continue}if(i.length===1)return i[0];throw new m("which_environment",`${i[0].project_name} has more than one environment`,{hint:`name one: ${i.map(o=>`${o.project_name}/${o.slug}`).join(", ")}`})}throw r}var Cc=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/;async function D(e,t,n,s,r,a){return(await Ae(e.studio,t.env_ref,n,s,r,a)).body}function H(e){if(!Cc.test(e))throw g(`${JSON.stringify(e)} is not a name: lowercase letters, digits and dashes, starting with a letter`);return encodeURIComponent(e)}async function Te(e,t,n){if(n!==void 0)return n;let{machines:s}=await D(e,t,"GET","/machines?limit=100");if(s.length===1)return s[0].name;let r=await Bs(e.io,e.io.cwd),a=r?.repository?s.filter(i=>i.source.repository.toLowerCase()===r.repository?.toLowerCase()&&(i.source.path??"")===r.path):[];if(a.length===1)return a[0].name;throw s.length===0?new m("no_machine","the environment has no machine yet",{hint:"make one with palbase machines:create"}):g("which machine? name it",`the environment's machines: ${s.map(i=>i.name).join(", ")}`)}var mo={name:"machines:sizes",summary:"the machine sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await h(e),{sizes:n}=await D(e,t,"GET","/machines/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"sleeps",value:s=>s.sleeps?"when idle":"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},go={name:"databases:sizes",summary:"the database sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await h(e),{sizes:n}=await D(e,t,"GET","/databases/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"storage",value:s=>`${(s.storage_bytes/1e9).toFixed(0)} GB`},{header:"sleeps",value:s=>s.sleeps?`when idle${s.awake_hours_per_month?`, ${s.awake_hours_per_month} h awake a month`:""}`:"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},ho={name:"machines:list",summary:"the environment's machines: size, state, the version serving, address",scope:"environment",async run(e){let t=await h(e),{machines:n}=await D(e,t,"GET","/machines?limit=100");e.out.result({machines:n},()=>{n.length===0&&e.out.line("No machine yet: palbase machines:create makes one from the code in this directory."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"size",value:s=>`${s.size}${s.replicas>1?` \xD7${s.replicas}`:""}`},{header:"state",value:s=>s.state},{header:"version",value:s=>s.running_version===s.version?`${s.version}`:`${s.running_version} \u2192 ${s.version}`},{header:"source",value:s=>`${s.source.repository}@${s.source.branch}${s.source.path?`/${s.source.path}`:""}`},{header:"url",value:s=>s.url}])})}},fo={name:"machines:get",summary:"a machine in full: its source, size, environment, database and the build serving",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}`);e.out.result(s,()=>{e.out.line(`${s.name} \u2014 ${s.state}${s.state_reason?` (${s.state_reason})`:""}`),e.out.line(` url ${s.url}`),e.out.line(` source ${s.source.repository} @ ${s.source.branch}${s.source.path?`, ${s.source.path}`:""}${s.source.context?` (context ${s.source.context})`:""}`),e.out.line(` size ${s.size} \xD7 ${s.replicas}, port ${s.port}${s.health_path?`, health ${s.health_path}`:""}`),e.out.line(` version ${s.running_version} serving${s.version!==s.running_version?`, ${s.version} ${s.state_reason?"not up":"rolling out"}`:""}${s.build?` (build ${s.build.id}, ${s.build.commit.slice(0,7)})`:""}`),e.out.line(` contract ${s.contract_digest?`sha256:${s.contract_digest.slice(0,12)}\u2026`:"none read"}${s.openapi_path?`, from ${s.openapi_path}`:""}`),e.out.line(` database ${s.database?.name?`${s.database.name}${s.database.migrations?", its migrations run before each release":""}`:"none"}`),e.out.line(` secrets ${s.secrets?`the ${s.secrets} secrets`:"none"}`),e.out.line(` tests ${s.test_command?`${s.test_command}${s.test_users?.length?`, as ${s.test_users.join(", ")}`:""} \u2014 run on each build before it rolls out`:"none"}`),e.out.line(` env ${Object.keys(s.env).length===0?"none":Object.keys(s.env).sort().join(", ")}`)})}},yo={repo:{type:"string",value:"owner/name",description:"the GitHub repository (default: this checkout's origin)"},branch:{type:"string",value:"branch",description:"the branch whose pushes are built and rolled out (default: the one checked out)"},path:{type:"string",value:"dir",description:"the directory built, from the repository's root (default: this directory's place in it)"},context:{type:"string",value:"dir",description:'what the build is given above --path (a monorepo: ".")'},size:{type:"string",value:"size",description:"its size (palbase machines:sizes lists them)"},replicas:{type:"string",value:"n",description:"how many copies run, each on a node of its own"},port:{type:"string",value:"port",description:"the port it listens on (it also finds it in PORT)"},health:{type:"string",value:"path",description:"where it answers 2xx while well; a release that does not is not rolled out"},"openapi-path":{type:"string",value:"path",description:`where its app serves its OpenAPI contract, when it is not a Palbase backend (apps:sync makes its client from it); "" for a Palbase backend's own`},db:{type:"string",value:"database",description:'a database of the environment it is given (PALBASE_DB_URL); "" for none'},migrations:{type:"boolean",description:"run the database's migrations before each release (a Palbase backend's)"},secrets:{type:"string",value:"production|development",description:'the secrets it is started with, by name; "" for none'},var:{type:"string",multiple:!0,value:"K=V",description:"a setting it is started with (repeat it); secrets go in secrets:set"},"test-command":{type:"string",value:"command",description:'what each new build runs before it takes traffic, in its image, against the new version itself once its migration has run (bun test, npm test); a failure fails the build and the machine runs what it ran; "" for none'},"test-users":{type:"string",value:"names",description:'the test users each test run makes for its tests (api.signInAs), comma-separated; "" for none'}};function bo(e,t){e.flags["test-command"]!==void 0&&(t.test_command=e.flags["test-command"]),e.flags["test-users"]!==void 0&&(t.test_users=Ys(e.flags["test-users"]))}function wo(e){if(!e)return;let t={};for(let n of e){let s=n.indexOf("=");if(s<=0)throw g(`a setting is K=V, not ${JSON.stringify(n)}`);t[n.slice(0,s)]=n.slice(s+1)}return t}function gt(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw g(`--${t} is a whole number`);return n}var vo={name:"machines:create",summary:"make a machine from the code in this directory, built on every push to its branch",usage:"[name] [--size <size>] [--db <database> --migrations] [--repo --branch --path --context] \u2026",details:["Run in a checkout, it reads the repository from origin, the branch from HEAD and the directory from where you are; flags say otherwise.","The first build starts at once; --wait follows it until the machine serves, and prints its address.","The environment needs its GitHub link first: palbase github:connect."],args:{min:0,max:1},flags:{...yo,wait:{type:"boolean",description:"follow the first build until the machine serves"}},scope:"environment",async run(e){let t=await h(e),n=await Bs(e.io,e.io.cwd),s=e.flags.repo??n?.repository,r=e.flags.branch??n?.branch;if(!s)throw g("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!r)throw g("which branch? none is checked out here","pass --branch <branch>");let a=e.flags.size;if(a===void 0){let{sizes:f}=await D(e,t,"GET","/machines/sizes");if(a=f.find(y=>y.available)?.name,!a)throw new m("no_size_open","no machine size is open to this environment",{hint:"palbase machines:sizes says why"})}let i=e.flags.path??n?.path??"",o={...e.args[0]?{name:e.args[0]}:{},source:{repository:s,branch:r,...i!==""?{path:i}:{},...e.flags.context?{context:e.flags.context}:{}},size:a},l=gt(e.flags.replicas,"replicas"),d=gt(e.flags.port,"port");l!==void 0&&(o.replicas=l),d!==void 0&&(o.port=d),e.flags.health!==void 0&&(o.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(o.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(o.secrets=e.flags.secrets);let u=wo(e.flags.var);if(u&&(o.env=u),e.flags.db!==void 0)o.database={name:e.flags.db,migrations:e.flags.migrations===!0};else if(e.flags.migrations===!0)throw g("--migrations goes with --db <database>");bo(e,o),await b(e,t,`make a machine from ${s}@${r}${i?`/${i}`:""} (${a})`);let p=await D(e,t,"POST","/machines",o);e.flags.wait===!0&&(p=await Oc(e,t,p.name)),e.out.result(p,()=>e.out.line(`Made the machine ${p.name} (${p.size}) \u2014 ${p.state}; it answers at ${p.url}`))}},_o={name:"machines:update",summary:"change a machine: the change rolls out as a new version beside the one serving",usage:"[name] [--size \u2026] [--replicas \u2026] [--db \u2026] [--repo --branch --path --context] \u2026",args:{min:0,max:1},flags:yo,scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||e.flags.path!==void 0||e.flags.context!==void 0){let o=await D(e,t,"GET",`/machines/${H(n)}`);s.source={repository:e.flags.repo??o.source.repository,branch:e.flags.branch??o.source.branch,path:e.flags.path??o.source.path??"",context:e.flags.context??o.source.context??""}}e.flags.size!==void 0&&(s.size=e.flags.size);let r=gt(e.flags.replicas,"replicas"),a=gt(e.flags.port,"port");if(r!==void 0&&(s.replicas=r),a!==void 0&&(s.port=a),e.flags.health!==void 0&&(s.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(s.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(s.secrets=e.flags.secrets),e.flags.var!==void 0)throw g("machines:update leaves the settings as they are: machines:env:set and machines:env:unset change them");if(e.flags.db!==void 0&&(s.database={name:e.flags.db,migrations:e.flags.migrations===!0}),bo(e,s),Object.keys(s).length===0)throw g("nothing to change: give a flag","palbase help machines:update");await b(e,t,`change the machine ${n}: ${Object.keys(s).join(", ")}`);let i=await D(e,t,"PATCH",`/machines/${H(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: version ${i.version} rolls out beside ${i.running_version}.`))}},So={name:"machines:env:list",summary:"what a machine is started with, and where each comes from (the platform, the machine, its secrets)",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}`),r=s.secrets?await D(e,t,"GET",`/secrets/${encodeURIComponent(s.secrets)}`).then(o=>(o.items??o.secrets??[]).map(l=>l.name),()=>[]):[],a=["PORT","PALBASE_URL","PALBASE_PROJECT_ID","PALBASE_REPLICAS","PALBASE_PLATFORM_SECRET","PALBASE_SECRET_KEY","PALBASE_WEBHOOK_SECRET",...s.database?.name?["PALBASE_DB_URL","PALBASE_DB_CONNECTIONS"]:[]],i=a.map(o=>({name:o,from:"platform",note:r.includes(o)||o in s.env?`in place of the ${o} set by hand`:""}));for(let o of Object.keys(s.env).sort())a.includes(o)||i.push({name:o,from:"machine",note:r.includes(o)?`shadows the secret ${o}`:""});for(let o of r.sort())a.includes(o)||o in s.env||i.push({name:o,from:`secrets (${s.secrets})`,note:""});e.out.result({machine:s.name,env:i},()=>e.out.table(i,[{header:"name",value:o=>o.name},{header:"from",value:o=>o.from},{header:"",value:o=>o.note}]))}};async function ko(e,t){let n=await h(e),s=await Te(e,n,e.flags.machine);for(let r=1;;r++){let a=await Ae(e.studio,n.env_ref,"GET",`/machines/${H(s)}`),i={...a.body.env},o=t(i);if(r===1){if(a.body.secrets){let l=await D(e,n,"GET",`/secrets/${encodeURIComponent(a.body.secrets)}`).catch(()=>({items:[]}));for(let d of Object.keys(i))(l.items??[]).some(u=>u.name===d)&&e.out.warn(`${d} in the machine's env shadows the secret of that name`)}await b(e,n,`${o.join(", ")} on the machine ${s}`)}try{let l=await D(e,n,"PATCH",`/machines/${H(s)}`,{env:i},a.etag?{"if-match":a.etag}:void 0);e.out.result({machine:l.name,env:Object.keys(l.env).sort()},()=>e.out.line(`${l.name}: version ${l.version} rolls out with it.`));return}catch(l){if(!(l instanceof Y&&l.status===412)||r===3)throw l;e.out.note(`${s} changed meanwhile: making the change again on what it is now\u2026`)}}}var $o={name:"machines:env:set",summary:"set settings a machine is started with (not secrets: secrets:set keeps those)",usage:"K=V\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=wo(e.args);await ko(e,n=>(Object.assign(n,t),Object.keys(t).map(s=>`set ${s}`)))}},Ao={name:"machines:env:unset",summary:"take settings away from what a machine is started with",usage:"K\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){await ko(e,t=>{for(let n of e.args){if(!(n in t))throw new m("no_such_setting",`the machine is not started with ${n}`);delete t[n]}return e.args.map(n=>`unset ${n}`)})}},Po={name:"machines:builds",summary:"a machine's builds, newest first",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),{builds:s}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=20`);e.out.result({builds:s},()=>e.out.table(s,[{header:"build",value:r=>r.id},{header:"commit",value:r=>r.commit.slice(0,7)},{header:"state",value:r=>`${r.state}${r.running?" (serving)":""}${r.reason?` \u2014 ${r.reason}`:""}`},{header:"message",value:r=>r.message.split(`
29
+ `))e.out.line(r)}})}},bi={name:"notify:templates:set",summary:"override a template in a locale, from a file or stdin (email: {subject, html_body, text_body} as JSON)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp] [--file <path>]",details:['email: {"subject", "html_body", "text_body"}; sms: the text, or {"body"}; whatsapp: {"name", "language", "variables", "code_button"} of a template approved on your Meta account.',"notify:templates:get \u2026 --json > t.json gives a document to edit and set back. Handlebars: {{name}}, {{#if}}, {{#each}}."],args:{min:1,max:1},flags:{...nr,file:{type:"string",value:"path",description:"the template (default: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=pn(e),s=sr(e,e.args[0]),r=gc(n,await ve(e,e.flags.file,"the template"));await b(e,t,`override the ${n} template ${e.args[0]} (${e.flags.locale})`);let a=await c(e,t,"PUT",s,r,R());e.out.result(a,()=>e.out.line(`The ${n} template ${e.args[0]} (${e.flags.locale}) is yours now${typeof a.subject=="string"?`: "${a.subject}"`:""}.`))}},wi={name:"notify:templates:delete",summary:"remove an override: the platform's default renders again (a WhatsApp mapping is forgotten)",usage:"<slug> --locale <locale> [--channel email|sms|whatsapp]",args:{min:1,max:1},flags:nr,scope:"environment",async run(e){let t=await h(e),n=sr(e,e.args[0]);await b(e,t,`remove the ${pn(e)} template ${e.args[0]} (${e.flags.locale})`),await c(e,t,"DELETE",n,void 0,R()),e.out.result({deleted:{channel:pn(e),slug:e.args[0],locale:e.flags.locale}},()=>e.out.line(`Removed the override of ${e.args[0]} (${e.flags.locale}).`))}};async function tr(e,t){let n=[],s;do{let r=await c(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${_(s)}`:""}`);n.push(...r.topics),s=r.next_cursor}while(s);return n}var vi={name:"notify:topics:list",summary:"the topics users follow, how many follow each, whether they follow it from the app",scope:"environment",async run(e){let t=await h(e),n=await tr(e,t);e.out.result({topics:n},()=>{n.length===0&&e.out.line("No topic: palbase notify:topics:create <name> makes one."),e.out.table(n,[{header:"topic",value:s=>s.name},{header:"followers",value:s=>String(s.subscribers)},{header:"from the app",value:s=>s.self_subscribe?"yes":"no: the project adds them"}])})}},_i={name:"notify:topics:create",summary:"make a topic users can follow; a push to it reaches its followers",usage:"<name> [--self-subscribe yes|no]",args:{min:1,max:1},flags:{"self-subscribe":{type:"string",value:"yes|no",description:"users follow it from the app (default no: the project adds them)"}},scope:"environment",async run(e){let t=await h(e),n=Tt(e.flags["self-subscribe"],"self-subscribe");await b(e,t,`make the topic ${e.args[0]}`);let s=await c(e,t,"POST","/notify/topics",{name:e.args[0],...n!==void 0?{self_subscribe:n}:{}},R());e.out.result(s,()=>e.out.line(`Made the topic ${s.name}${s.self_subscribe?", followed from the app":""}.`))}},Si={name:"notify:topics:delete",summary:"delete a topic and who follows it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`delete the topic ${e.args[0]} and who follows it`),await c(e,t,"DELETE",`/notify/topics/${_(e.args[0])}`,void 0,R()),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the topic ${e.args[0]}.`))}},ki={name:"notify:settings:get",summary:"the environment's message settings: the default locale, who email is from, where texts may go",scope:"environment",async run(e){let t=await h(e),{body:n}=await ce(e,t,"/notify/settings");e.out.result(n,()=>Pt(e,n))}},$i={name:"notify:settings:set",summary:'change message settings: key=value (from_email=\u2026, sms_countries=["+90","+44"], mx_check=true)',usage:"<key=value>\u2026",details:["The change is made on the revision read: one made beside it in the meantime is refused, not overwritten."],args:{min:1,max:20},scope:"environment",async run(e){let t=await h(e),n=et(e.args),{body:s,etag:r}=await ce(e,t,"/notify/settings"),a=nt(s,n),i=ze(s,a);if(i.length===0)return We(e,"the message settings",s);await b(e,t,`set the message settings ${i.join(", ")}`);let o=await c(e,t,"PUT","/notify/settings",a,{...oe(r),...R()});e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${le(s[l])} \u2192 ${le(o[l])}`)})}},fc=new Set(["sms_countries","sms_blocked_countries"]);function yc(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var Ai={name:"notify",async read(e,t){let[n,s,r,a,i,o]=await Promise.all([c(e,t,"GET","/notify/settings"),c(e,t,"GET","/notify/providers"),c(e,t,"GET","/notify/templates/email"),c(e,t,"GET","/notify/templates/sms"),c(e,t,"GET","/notify/templates/whatsapp"),tr(e,t)]),l={};for(let[d,f]of Object.entries(yc(n)))l[`settings.${d}`]=f;for(let d of s.providers)l[`providers.${d.channel}/${d.provider}`]={active:d.active,priority:d.priority,credentials:"set"};let p=r.templates.filter(d=>d.customized),u=await Promise.all(p.map(d=>c(e,t,"GET",`/notify/templates/email/${_(d.slug)}/${_(d.locale)}`)));for(let d of u)l[`templates.email/${d.slug}/${d.locale}`]={subject:d.subject,html_body:d.html_body,text_body:d.text_body};for(let d of a.templates.filter(f=>f.customized))l[`templates.sms/${d.slug}/${d.locale}`]={body:d.body};for(let d of i.templates)l[`templates.whatsapp/${d.slug}/${d.locale}`]={name:d.name,language:d.language,variables:d.variables,code_button:d.code_button};for(let d of o)l[`topics.${d.name}`]={self_subscribe:d.self_subscribe};return l},async copy(e,t,n,s){let r=Q(n,"settings.");if(r.length>0){let{body:o,etag:l}=await ce(e,t,"/notify/settings"),p={...o};for(let u of r)p[u.name]=fc.has(u.name)&&Array.isArray(u.from)&&u.from.length===0?null:u.from;await U(s,`set the message settings ${r.map(u=>u.name).join(", ")}`,async()=>{await c(e,t,"PUT","/notify/settings",p,{...oe(l),...R()})})}for(let o of Q(n,"providers.")){let[l,p]=o.name.split("/");o.from===void 0?s.left(`the ${l} provider ${p}: only in the target, left as it is (it holds credentials)`):s.left(`the ${l} provider ${p}: credentials are not copied \u2014 set them with palbase notify:providers:set ${l} ${p} < credentials.json`)}for(let o of Q(n,"templates.")){let l=`/notify/templates/${o.name.split("/").map(_).join("/")}`;o.from===void 0?await U(s,`remove the template override ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())}):await U(s,`override the template ${o.name}`,async()=>{await c(e,t,"PUT",l,o.from,R())})}let a=Q(n,"topics."),i=a.some(o=>o.from===void 0)?new Map((await tr(e,t)).map(o=>[o.name,o.subscribers])):new Map;for(let o of a){let l=`/notify/topics/${_(o.name)}`,p=o.from?.self_subscribe;if(o.from===void 0){let u=i.get(o.name)??0;u>0?s.left(`the topic ${o.name}: only in the target, and ${u} users follow it \u2014 delete it with notify:topics:delete`):await U(s,`delete the topic ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())})}else o.change==="only_in_from"?await U(s,`make the topic ${o.name}`,async()=>{await c(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:p},R())}):await U(s,`set the topic ${o.name} ${p?"followed from the app":"followed by the project's adding"}`,async()=>{await c(e,t,"PATCH",l,{self_subscribe:p},R())})}}};var Ct=["max_connections","max_events_per_second","max_joins_per_second","max_channels_per_connection"];function bc(e){if(e.kind==="owner")return"the user it names: everything";let t=[e.publish?"publish":"",e.state_read?"read state":"",e.state_write?"write state":""].filter(n=>n!=="");return`any signed-in user: subscribe${t.length>0?`, ${t.join(", ")}`:""}`}function rr(e){return Object.fromEntries(Ct.map(t=>[t,e[t]??null]))}var Pi={name:"realtime:channels:get",summary:"the channel declaration, in the order patterns are matched: who may join each, publish, read and write its state",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/realtime/channels");e.out.result(n,()=>{n.channels.length===0&&e.out.line("No channel is declared: a channel no pattern matches belongs to no one."),e.out.table(n.channels,[{header:"pattern",value:s=>s.pattern},{header:"kind",value:s=>s.kind},{header:"who",value:s=>bc(s)}])})}},Ri={name:"realtime:channels:set",summary:'replace the channel declaration with a JSON file or stdin ({"channels": [\u2026]} or the list)',usage:"[--file <path>]",details:['Each channel: {"pattern": "room:{id}", "kind": "public", "publish": true, "state_read": true, "state_write": false}, or {"pattern": "user:{uid}", "kind": "owner"}.',"realtime:channels:get --json gives the document to edit. Open sockets are decided again within a heartbeat."],flags:{file:{type:"string",value:"path",description:"the declaration (default: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=he(await ve(e,e.flags.file,"the channel declaration"),"the channel declaration"),s=Array.isArray(n)?n:n.channels;if(!Array.isArray(s))throw g('the declaration is {"channels": [\u2026]} or a list of channels');let r=await c(e,t,"GET","/realtime/channels");if(Re(r.channels,s))return We(e,"the channel declaration",r);await b(e,t,`declare ${s.length} channel pattern(s) (now ${r.channels.length})`);let a=await c(e,t,"PUT","/realtime/channels",{channels:s});e.out.result(a,()=>e.out.line(a.channels.length===0?"No channel is declared now.":`Declared ${a.channels.length} channel pattern(s): ${a.channels.map(i=>i.pattern).join(", ")}.`))}},Ti={name:"realtime:limits:get",summary:"the limits the environment holds its clients to (null: the platform's), and the platform's",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/realtime/limits");e.out.result(n,()=>{let s=n.platform??{},r=Math.max(...Ct.map(a=>a.length));for(let a of Ct)e.out.line(`${a.padEnd(r)} ${n[a]===void 0||n[a]===null?"the platform's":le(n[a])}${s[a]!==void 0?` (platform ${s[a]===0?"no ceiling":le(s[a])})`:""}`)})}},Ei={name:"realtime:limits:set",summary:"change realtime limits: key=value (max_connections=500), null for the platform's",usage:"<key=value>\u2026",details:[`The keys: ${Ct.join(", ")}. A value above the platform's has no effect beyond it.`],args:{min:1,max:4},scope:"environment",async run(e){let t=await h(e),n=et(e.args);for(let o of n)if(o.path.length!==1||!Ct.includes(o.path[0]))throw g(`no limit ${JSON.stringify(o.path.join("."))}`,`the limits: ${Ct.join(", ")}`);let s=rr(await c(e,t,"GET","/realtime/limits")),r=nt(s,n),a=ze(s,r);if(a.length===0)return We(e,"the realtime limits",s);await b(e,t,`set the realtime limits ${a.join(", ")}`);let i=await c(e,t,"PUT","/realtime/limits",r);e.out.result({limits:i,changed:a},()=>{for(let o of a)e.out.line(`${o}: ${le(s[o]??"the platform's")} \u2192 ${le(i[o]??"the platform's")}`)})}},Ci={name:"realtime",async read(e,t){let[n,s]=await Promise.all([c(e,t,"GET","/realtime/channels"),c(e,t,"GET","/realtime/limits")]),r={channels:n.channels};for(let[a,i]of Object.entries(rr(s)))r[`limits.${a}`]=i;return r},async copy(e,t,n,s){let r=n.find(i=>i.key==="channels");r&&await U(s,"declare the channels",async()=>{await c(e,t,"PUT","/realtime/channels",{channels:r.from??[]})});let a=Q(n,"limits.");if(a.length>0){let i=rr(await c(e,t,"GET","/realtime/limits"));for(let o of a)i[o.name]=o.from??null;await U(s,`set the realtime limits ${a.map(o=>o.name).join(", ")}`,async()=>{await c(e,t,"PUT","/realtime/limits",i)})}}},ji={name:"analytics:settings:get",summary:"how long analytics keeps events and console records, the share of performance samples kept, whether bodies are kept",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/analytics/settings");e.out.result(n,()=>Pt(e,n))}},Oi={name:"analytics:settings:set",summary:"change analytics settings: key=value (retention_days=30, perf_sample_pct=10, console_bodies=false)",usage:"<key=value>\u2026",details:["A shorter retention removes what is older within minutes."],args:{min:1,max:4},scope:"environment",async run(e){let t=await h(e),n=et(e.args),s=await c(e,t,"GET","/analytics/settings"),r=nt(s,n),a=ze(s,r);if(a.length===0)return We(e,"the analytics settings",s);await b(e,t,`set the analytics settings ${a.join(", ")}`);let i=await c(e,t,"PUT","/analytics/settings",r);e.out.result({settings:i,changed:a},()=>{for(let o of a)e.out.line(`${o}: ${le(s[o])} \u2192 ${le(i[o])}`)})}},Di={name:"analytics",async read(e,t){let n=await c(e,t,"GET","/analytics/settings");return Object.fromEntries(Object.entries(n).map(([s,r])=>[`settings.${s}`,r]))},async copy(e,t,n,s){let r=Q(n,"settings.");if(r.length===0)return;let a=await c(e,t,"GET","/analytics/settings");for(let i of r)i.from!==void 0&&(a[i.name]=i.from);await U(s,`set the analytics settings ${r.map(i=>i.name).join(", ")}`,async()=>{await c(e,t,"PUT","/analytics/settings",a)})}};function wc(e){return{source:{repository:e.source.repository.toLowerCase(),branch:e.source.branch,path:e.source.path??"",context:e.source.context??""},size:e.size,replicas:e.replicas,port:e.port,health_path:e.health_path??"",env:e.env??{},secrets:e.secrets??"",database:e.database?{name:e.database.name,migrations:e.database.migrations===!0}:null,deploy_after_checks:e.deploy_after_checks===!0,deploy_checks:[...e.deploy_checks??[]].sort(),deploy_checks_app:e.deploy_checks_app??"github-actions"}}var Ii={name:"machines",async read(e,t){let{machines:n}=await c(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.map(s=>[`machines.${s.name}`,wc(s)]))},async copy(e,t,n,s){for(let r of Q(n,"machines.")){if(r.change==="only_in_to"){s.left(`the machine ${r.name}: only the target has it \u2014 it serves on (palbase machines:delete ${r.name} takes it away)`);continue}let a=r.from;if(r.change==="only_in_from"){await U(s,`opened the machine ${r.name}, built from ${a.source.branch}'s head`,()=>c(e,t,"POST","/machines",{name:r.name,...a,database:a.database??void 0},R()));continue}let i=r.to,o=Object.fromEntries(Object.keys(a).filter(l=>!Re(a[l],i[l])).map(l=>[l,l==="database"&&a[l]===null?{name:""}:a[l]]));await U(s,`changed the machine ${r.name}: ${Object.keys(o).join(", ")} (a new version rolls out beside the one serving)`,()=>c(e,t,"PATCH",`/machines/${_(r.name)}`,o))}}};function vc(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function _c(e,t,n,s){let r=await c(e,t,"POST","/databases",{name:n,...s},R());await e.state.keepDatabasePasswords(t.env_ref,r.name,{owner:r.credentials.owner.password,app:r.credentials.app.password})}var Li={name:"databases",async read(e,t){let{databases:n}=await c(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.map(s=>[`databases.${s.name}`,vc(s)]))},async copy(e,t,n,s){for(let r of Q(n,"databases.")){if(r.change==="only_in_to"){s.left(`the database ${r.name}: only the target has it \u2014 its data stays (palbase databases:delete ${r.name} takes it away)`);continue}let a=r.from;if(r.change==="only_in_from"){await U(s,`opened the database ${r.name} (${a.size}; its passwords kept on this computer \u2014 palbase db:copy brings its data)`,()=>_c(e,t,r.name,a));continue}let i=r.to,o=Object.fromEntries(["size","allow","backup_days"].filter(p=>!Re(a[p],i[p])).map(p=>[p,a[p]]));if(Object.keys(o).length===0)continue;let l=o.size===void 0?"":` (moving to ${o.size}, serving on)`;await U(s,`changed the database ${r.name}: ${Object.keys(o).join(", ")}${l}`,()=>c(e,t,"PATCH",`/databases/${_(r.name)}`,o))}}};var Sc=/^[a-z][a-z0-9-]{1,62}$/;function jt(e){if(!Sc.test(e))throw g(`${JSON.stringify(e)} is not a bucket's name: lowercase letters, digits and dashes, starting with a letter (2\u201363)`);return`/storage/buckets/${_(e)}`}function ir(e){if(e==null)return"platform (5 GiB)";for(let[t,n]of[["GiB",2**30],["MiB",2**20],["KiB",2**10]])if(e>=n&&e%n===0)return`${e/n} ${t}`;return`${e} B`}function kc(e){if(e===""||e==="platform")return null;let t=/^(\d+(?:\.\d+)?)\s*(b|kb|kib|mb|mib|gb|gib)?$/i.exec(e.trim());if(!t)throw g(`--max-size ${JSON.stringify(e)} is not a size: 10MB, 512KiB, 1GB, a number of bytes, or platform`);let n=(t[2]??"b").toLowerCase(),s={b:1,kb:1e3,kib:2**10,mb:1e6,mib:2**20,gb:1e9,gib:2**30};return Math.round(Number(t[1])*s[n])}function ar(e){return{public:e.public,file_size_limit:e.file_size_limit??null,allowed_mime_types:e.allowed_mime_types??[],variants:e.variants??[]}}async function $c(e,t,n){try{return await c(e,t,"GET",jt(n))}catch(s){if(s instanceof Y&&s.status===404)return;throw s}}var Ni={name:"storage:buckets:list",summary:"the environment's buckets: who may read each, the largest file and the types it takes",scope:"environment",async run(e){let t=await h(e),{buckets:n}=await c(e,t,"GET","/storage/buckets");e.out.result({buckets:n},()=>{n.length===0&&e.out.line("No bucket yet: palbase storage:buckets:set <name> makes one."),e.out.table(n,[{header:"bucket",value:s=>s.name},{header:"read by",value:s=>s.public?"anyone":"signed URLs, rules"},{header:"largest file",value:s=>ir(s.file_size_limit)},{header:"types",value:s=>s.allowed_mime_types.length===0?"any":s.allowed_mime_types.join(", ")},{header:"renditions",value:s=>s.variants.map(r=>r.name).join(", ")}])})}},qi={name:"storage:buckets:get",summary:"a bucket's declaration in full, with its renditions",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",jt(e.args[0]));e.out.result(n,()=>{e.out.line(n.name),e.out.line(` read by ${n.public?"anyone, at /storage/public/":"signed URLs and the rules"}`),e.out.line(` largest file ${ir(n.file_size_limit)}`),e.out.line(` types ${n.allowed_mime_types.length===0?"any":n.allowed_mime_types.join(", ")}`);for(let s of n.variants)e.out.line(` rendition ${s.name}: ${s.width}\xD7${s.height} ${s.fit}, ${s.format}${s.quality?` q${s.quality}`:""}`)})}},xi={name:"storage:buckets:set",summary:"make a bucket, or change one: public or private, the largest file, the content types, its renditions",usage:"<bucket> [--public|--private] [--max-size <size>] [--types <a/b,c/*>] [--variants-file <path>]",details:["What is not given is kept as it is (a new bucket: private, the platform's size, any type, no rendition)."],args:{min:1,max:1},flags:{public:{type:"boolean",description:"anyone may read its files at /storage/public/"},private:{type:"boolean",description:"its files are read with signed URLs and the rules"},"max-size":{type:"string",value:"size",description:"the largest file: 10MB, 512KiB, 1GB, bytes, or platform"},types:{type:"string",value:"list",description:'the content types it takes, comma-separated (image/* a family); "" for any'},"variants-file":{type:"string",value:"path",description:"its renditions, a JSON list of {name, width, height, fit, format, quality}"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(e.flags.public===!0&&e.flags.private===!0)throw g("--public and --private are one or the other");let s=await $c(e,t,n),r=ar(s??{name:n,public:!1,allowed_mime_types:[],variants:[]});if(e.flags.public===!0&&(r.public=!0),e.flags.private===!0&&(r.public=!1),e.flags["max-size"]!==void 0&&(r.file_size_limit=kc(e.flags["max-size"])),e.flags.types!==void 0&&(r.allowed_mime_types=me(e.flags.types)??[]),e.flags["variants-file"]!==void 0){let i=he(await ve(e,e.flags["variants-file"],"the renditions"),"the renditions file"),o=Array.isArray(i)?i:i.variants;if(!Array.isArray(o))throw g("the renditions file is a JSON list of {name, width, height, fit, format, quality}");r.variants=o}if(s&&Re(ar(s),r)){e.out.result(s,()=>e.out.line(`Nothing to change: ${n} already is so.`));return}await b(e,t,`${s?"change":"make"} the bucket ${n} (${r.public?"public":"private"})`);let a=await c(e,t,"PUT",jt(n),r);e.out.result(a,()=>e.out.line(`${s?"Changed":"Made"} the bucket ${a.name}: ${a.public?"public":"private"}, files up to ${ir(a.file_size_limit)}, ${a.allowed_mime_types.length===0?"any type":a.allowed_mime_types.join(", ")}.`))}},Mi={name:"storage:buckets:delete",summary:"delete an empty bucket (one that still holds files is refused)",usage:"<bucket>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=jt(e.args[0]);await b(e,t,`delete the bucket ${e.args[0]}`),await c(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},Ui={name:"storage",async read(e,t){let{buckets:n}=await c(e,t,"GET","/storage/buckets"),s={};for(let r of n){let a=ar(r);s[`buckets.${r.name}`]={...a,allowed_mime_types:[...a.allowed_mime_types].sort()}}return s},async copy(e,t,n,s){for(let r of Q(n,"buckets."))r.from===void 0?await U(s,`delete the bucket ${r.name}`,async()=>{await c(e,t,"DELETE",jt(r.name))}):await U(s,`declare the bucket ${r.name}`,async()=>{await c(e,t,"PUT",jt(r.name),r.from)})}};var Ot=[ei,Ui,ui,Ai,Ci,Di],Kn=[Li,Ii],Ki={from:{type:"string",value:"env",description:"the environment compared from (slug or ref)"},to:{type:"string",value:"env",description:"the environment compared to (slug or ref)"},only:{type:"string",value:"modules",description:`only these, comma-separated: ${Ot.map(e=>e.name).join(",")} (the default), and ${Kn.map(e=>e.name).join(",")} when named`}};async function Gi(e){if(e.global.env!==void 0)throw g("the two environments are named with --from and --to, not -E");let t=e.flags.from,n=e.flags.to;if(t===void 0||n===void 0)throw g("name both environments: --from <env> --to <env>");let s=await xe(e.studio),r;if(e.global.project!==void 0)r=mt(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new m("no_target","which project? none is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});r=s.filter(p=>p.project_id===l.target.project_id)}let a=l=>{let p=r.find(u=>u.slug===l||u.ref===l);if(!p)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(l)}`,{hint:`its environments: ${r.map(u=>u.slug).join(", ")}`});return ue(p)},i=a(t),o=a(n);if(i.env_ref===o.env_ref)throw g("--from and --to name the same environment");return{from:i,to:o}}function Bi(e){let t=me(e.flags.only);if(t===void 0)return Ot;let n=[...Ot,...Kn];for(let s of t)if(!n.some(r=>r.name===s))throw g(`no module ${JSON.stringify(s)}`,`the modules: ${n.map(r=>r.name).join(", ")}`);return n.filter(s=>t.includes(s.name))}function Ac(e,t){let n=[...new Set([...Object.keys(e),...Object.keys(t)])].sort(),s=[];for(let r of n){let a=r in e,i=r in t;a&&!i?s.push({key:r,change:"only_in_from",from:e[r]}):!a&&i?s.push({key:r,change:"only_in_to",to:t[r]}):Re(e[r],t[r])||s.push({key:r,change:"differs",from:e[r],to:t[r]})}return s}async function or(e,t,n,s){let r=l=>Promise.all(t.map(p=>p.read(e,l))),[a,i]=await Promise.all([r(n),r(s)]),o=Object.fromEntries(t.map((l,p)=>[l.name,Ac(a[p],i[p])]));return{from:n,to:s,modules:o,count:Object.values(o).reduce((l,p)=>l+p.length,0)}}function lr(e){return{from:{env:e.from.env_slug,ref:e.from.env_ref},to:{env:e.to.env_slug,ref:e.to.env_ref},equal:e.count===0,differences:e.count,modules:e.modules}}function Un(e){let t=JSON.stringify(e)??"undefined";return t.length>140?`${t.slice(0,139)}\u2026`:t}function pr(e,t){let n=t.from.env_slug,s=t.to.env_slug;for(let[a,i]of Object.entries(t.modules))if(i.length!==0){e.out.line(`${a} (${i.length})`);for(let o of i)o.change==="differs"?e.out.line(` ~ ${o.key}: ${n} ${Un(o.from)} \xB7 ${s} ${Un(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${Un(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${Un(o.to)}`)}let r=Object.keys(t.modules).join(", ");e.out.line(t.count===0?`${n} and ${s} have the same settings (${r}).`:`${t.count} difference(s) between ${n} and ${s} (${r}).`)}var Pc={name:"config:diff",summary:"how two environments' module settings differ (auth, storage, flags, notify, realtime, analytics; machines, databases when named)",usage:"--from <env> --to <env> [--only <modules>]",details:["- a key only in --from, + only in --to, ~ in both with other values. Ids, times and versions are not compared; a secret is compared as set or not, never by its value.","Exit code 0 when they are the same, 1 when they differ. --json gives each difference with both values."],flags:Ki,scope:"environment",async run(e){let{from:t,to:n}=await Gi(e),s=await or(e,Bi(e),t,n);return e.out.result(lr(s),()=>pr(e,s)),s.count===0?0:1}},Rc={name:"config:copy",summary:"make one environment's module settings equal to another's (secrets excepted: their names are said)",usage:"--from <env> --to <env> [--only <modules>]",details:["It shows the differences, asks before changing a production environment (-y confirms), changes the target, and compares again.","A secret is never copied: a provider's credentials and a sign-in client's secret are listed by name for you to set. A new hook or OAuth client gets its own secret, printed this once.","What the target holds that its users depend on is not taken by a copy: a bucket with files, a role users hold, a topic users follow are left and said.","--only machines,databases copies what they are declared as \u2014 a machine's source, size, copies, settings, secrets' name, database; a database's size, who may connect, backups \u2014 never a database's data (palbase db:copy) nor a secret's value. A machine or database only the target has is left serving.","Exit code 0 when the two are the same afterwards, 1 when a difference remains."],flags:Ki,scope:"environment",async run(e){let{from:t,to:n}=await Gi(e);return dr(e,Bi(e),t,n)}};async function dr(e,t,n,s,r={}){let a=await or(e,t,n,s);if(a.count===0)return e.out.result({...r,copied:[],left:[],shown_once:[],after:lr(a)},()=>e.out.line(`Nothing to copy: ${n.env_slug} and ${s.env_slug} have the same settings.`)),0;e.out.json||pr(e,a);let i=t.filter(d=>a.modules[d.name].length>0).map(d=>d.name);await b(e,s,`make its ${i.join(", ")} settings equal to ${n.env_slug}'s (${a.count} difference(s))`);let o=[],l=[],p=[];e.out.json||e.out.line();for(let d of t){let f=a.modules[d.name];if(f.length===0)continue;let y={done(v){o.push(`${d.name}: ${v}`),e.out.json||e.out.line(`copied ${d.name}: ${v}`)},left(v){l.push(`${d.name}: ${v}`),e.out.json||e.out.line(`left ${d.name}: ${v}`)},shown(v,T){p.push({what:`${d.name}: ${v}`,value:T}),e.out.json||e.out.line(` ${d.name}: ${v} ${T} (shown this once)`)}};await d.copy(e,s,f,y)}let u=await or(e,t,n,s);return e.out.result({...r,copied:o,left:l,shown_once:p,after:lr(u)},()=>{e.out.line(),e.out.line("Compared again:"),pr(e,u)}),u.count===0?0:1}var Ji=[qa,xa,Ma,Ua,Ga,Ka,Ba,Ja,za,Fa,Va,Ha,Xa,Ya,Za,Qa,Ni,qi,xi,Mi,ri,ai,ii,oi,pi,di,li,mi,gi,hi,fi,yi,bi,wi,vi,_i,Si,ki,$i,Pi,Ri,Ti,Ei,ji,Oi,Pc,Rc];async function ur(e){return(await e.studio.get("/orgs")).organizations}async function at(e,t){let n=await ur(e);if(t===void 0){if(n.length===1)return n[0];throw g(n.length===0?"you are in no organization yet: make one with palbase orgs:create <name>":"which organization? pass --org <name>",`yours: ${n.map(r=>r.name).join(", ")}`)}let s=n.filter(r=>r.id===t||r.name.localeCompare(t,void 0,{sensitivity:"accent"})===0);if(s.length===0)throw new m("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new m("ambiguous_org",`${JSON.stringify(t)} names more than one of your organizations`,{hint:"pass its id"});return s[0]}var zi={name:"orgs:list",summary:"your organizations and your role in each",scope:"account",async run(e){let t=await ur(e);e.out.result({organizations:t},()=>{t.length===0&&e.out.line("You are in no organization yet: palbase orgs:create <name> makes one."),e.out.table(t,[{header:"name",value:n=>n.name},{header:"role",value:n=>n.role},{header:"id",value:n=>n.id}])})}},Wi={name:"orgs:create",summary:"make an organization; you are its owner",usage:"<name>",args:{min:1,max:1},scope:"account",async run(e){let t=await e.studio.post("/orgs",{name:e.args[0]});e.out.result(t,()=>e.out.line(`Made the organization ${G(t.name)} (${t.id}).`))}},Fi={name:"orgs:members:list",summary:"an organization's members, and the invitations still open (for its admins)",flags:{org:{type:"string",value:"org",description:"the organization (name or id); needed when you are in more than one"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=await e.studio.get(`/orgs/${t.id}/members`);e.out.result(n,()=>{e.out.table(n.members,[{header:"email",value:s=>s.email??""},{header:"role",value:s=>s.role},{header:"user",value:s=>s.user_id}]),n.invitations.length>0&&(e.out.line(),e.out.line("Invited:"),e.out.table(n.invitations,[{header:"email",value:s=>s.email},{header:"role",value:s=>s.role},{header:"invitation",value:s=>s.id}]))})}},Vi={name:"orgs:members:invite",summary:"invite someone to an organization by email; they accept in Studio",usage:"<email> [--role admin|member]",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"admin or member (default member)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw g("--role is admin or member");let s=await e.studio.post(`/orgs/${t.id}/invitations`,{email:e.args[0],role:n});e.out.result(s,()=>e.out.line(`Invited ${G(s.email)} to ${G(t.name)} as ${s.role}.`))}},Hi={name:"orgs:members:remove",summary:"take a member (by email or user id) or an open invitation out of an organization",usage:"<email|user-id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.args[0],s=await e.studio.get(`/orgs/${t.id}/members`),r=s.members.find(i=>i.user_id===n||i.email!==null&&i.email.toLowerCase()===n.toLowerCase());if(r){await e.studio.request("DELETE",`/orgs/${t.id}/members/${encodeURIComponent(r.user_id)}`),e.out.result({removed:r},()=>e.out.line(`${G(r.email??r.user_id)} is no longer in ${G(t.name)}.`));return}let a=s.invitations.find(i=>i.id===n||i.email.toLowerCase()===n.toLowerCase());if(!a)throw new m("no_such_member",`${JSON.stringify(n)} is neither a member of ${t.name} nor invited to it`);await e.studio.request("DELETE",`/orgs/${t.id}/invitations/${encodeURIComponent(a.id)}`),e.out.result({withdrawn:a},()=>e.out.line(`The invitation of ${G(a.email)} is withdrawn.`))}},Xi={name:"tokens:list",summary:"an organization's access tokens (PALBASE_TOKEN for CI and agents): name, role, when they end and were last used \u2014 never their values",flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`);e.out.result({tokens:n},()=>{n.length===0?e.out.line(`${G(t.name)} has no access token yet: palbase tokens:create <name> makes one.`):e.out.table(n,[{header:"name",value:s=>G(s.name)},{header:"role",value:s=>s.role},{header:"token",value:s=>`pbo_\u2026${s.hint}`},{header:"ends",value:s=>s.expires_at.slice(0,10)},{header:"last used",value:s=>s.last_used_at?.slice(0,16).replace("T"," ")??"never"},{header:"id",value:s=>s.id}])})}},Yi={name:"orgs:audit",summary:"who changed what at an organization's environments, through Studio or the CLI, newest first \u2014 its admins'",usage:"[--org <org>] [--limit <n>]",details:["A module keeps a change made through Studio as Studio's key's: this says whose it was. What was sent is not kept."],flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.limit===void 0?50:Number(e.flags.limit);if(!Number.isInteger(n)||n<1||n>1e4)throw g("--limit is a whole number from 1 to 10000");let s=[],r=null;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{before:r}:{}}),i=await e.studio.get(`/orgs/${t.id}/audit?${a}`);s.push(...i.entries),r=i.next_before}while(r&&s.length<n);e.out.result({entries:s},()=>{s.length===0?e.out.line(`Nothing changed at ${G(t.name)}'s environments yet.`):e.out.table(s,[{header:"when",value:a=>a.at.slice(0,16).replace("T"," ")},{header:"who",value:a=>G(a.actor_name??a.actor)},{header:"environment",value:a=>G(a.environment_ref)},{header:"what",value:a=>G(`${a.method} ${a.path}`)},{header:"answer",value:a=>String(a.status)}])})}},Zi={name:"tokens:create",summary:"make an access token of an organization for CI or an agent; its value is shown this once",usage:"<name> [--role member|admin] [--days <n>]",details:["Give it to the CI as the secret PALBASE_TOKEN: the CLI acts with it in place of a sign-in. It reaches its organization's projects and environments at its role, and nothing of its people or tokens.","It ends after --days (default 90, at most 365), and goes when it is revoked or when the one who made it leaves the organization."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},role:{type:"string",value:"role",description:"member (default) or admin: what it may change"},days:{type:"string",value:"n",description:"how many days it is good for (default 90)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw g("--role is member or admin");let s=e.flags.days===void 0?90:Number(e.flags.days);if(!Number.isInteger(s)||s<1||s>365)throw g("--days is a whole number from 1 to 365");let r=await e.studio.post(`/orgs/${t.id}/tokens`,{name:e.args[0],role:n,expires_in_days:s});e.out.result(r,()=>{e.out.line(`Made the access token ${G(r.name)} of ${G(t.name)} (${r.role}, until ${r.expires_at.slice(0,10)}).`),e.out.line(`token (shown this once \u2014 keep it as the CI's secret PALBASE_TOKEN): ${r.token}`)})}},Qi={name:"tokens:revoke",summary:"revoke an organization's access token (by its name or id): refused from its next request on",usage:"<name|id>",args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`),s=n.filter(r=>r.id===e.args[0]||r.name===e.args[0]);if(s.length===0)throw new m("no_such_token",`${G(t.name)} has no access token ${JSON.stringify(e.args[0])}`,{hint:"palbase tokens:list shows them"});if(s.length>1)throw new m("ambiguous_token",`${JSON.stringify(e.args[0])} names more than one token`,{hint:"pass its id"});await e.studio.request("DELETE",`/orgs/${t.id}/tokens/${encodeURIComponent(s[0].id)}`),e.out.result({revoked:s[0].id},()=>e.out.line(`Revoked the access token ${G(s[0].name)} of ${G(t.name)}.`))}},eo={name:"projects:list",summary:"every project of your organizations, with its environments",scope:"account",async run(e){let t=await xe(e.studio),n=await e.state.targetFor(e.io.cwd),s=(await e.studio.get("/projects")).projects,r=new Map(t.map(a=>[a.organization_id,a.org_name]));for(let a of await ur(e))r.set(a.id,a.name);e.out.result({projects:s.map(a=>({...a,organization_name:r.get(a.organization_id)??null}))},()=>{s.length===0&&e.out.line("No project yet: palbase projects:create <name> makes one.");let a=s.flatMap(i=>i.environments.length===0?[{org:r.get(i.organization_id)??"",project:i.name,env:"(none)",ref:"",here:""}]:i.environments.map(o=>({org:r.get(i.organization_id)??"",project:i.name,env:o.slug,ref:o.ref,here:n?.target.env_ref===o.ref?"*":""})));e.out.table(a,[{header:"",value:i=>i.here},{header:"organization",value:i=>i.org},{header:"project",value:i=>i.project},{header:"env",value:i=>i.env},{header:"ref",value:i=>i.ref}])})}},to={name:"projects:create",summary:"make a project with its first environment (main), and print its keys",usage:"<name> [--org <org>] [--env-name <name>]",details:["The first environment is the project's production one: its slug is always main.","Its secret key is printed this once; keys:create makes more."],args:{min:1,max:1},flags:{org:{type:"string",value:"org",description:"the organization (name or id)"},"env-name":{type:"string",value:"name",description:"what the first environment is called on screen (default Production)"}},scope:"account",async run(e){let t=await at(e,e.flags.org),{project:n}=await e.studio.post(`/orgs/${t.id}/projects`,{name:e.args[0]}),s=await e.studio.post(`/projects/${n.id}/environments`,{name:e.flags["env-name"]??"Production"});e.out.result({project:n,environment:s.environment,keys:s.keys},()=>{e.out.line(`Made ${G(t.name)} \u203A ${G(n.name)} with its environment ${s.environment.slug} (ref ${s.environment.ref}).`),s.keys&&(e.out.line(`publishable key: ${s.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${s.keys.secret.key}`)),e.out.line(`palbase use ${n.name}/${s.environment.slug} chooses it for this directory.`)})}},no={name:"projects:delete",summary:"delete a project and every environment of it",usage:"<project> [-y]",details:["Its environments' data goes with them (the platform keeps it a week to bring back)."],args:{min:1,max:1},scope:"account",async run(e){let t=mt(await xe(e.studio),e.args[0]),n=t[0];if(e.out.note(`${n.org_name} \u203A ${n.project_name}: delete the project and its environments ${t.map(s=>s.slug).join(", ")}`),!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting a project is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the project's name to delete it: ")).trim()!==n.project_name)throw new m("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/projects/${n.project_id}`),e.out.result({deleted:{project_id:n.project_id,environments:t.map(s=>s.ref)}},()=>e.out.line(`Deleted ${G(n.project_name)}.`))}},so={name:"envs:list",summary:"a project's environments (the project chosen here, or -P)",scope:"environment",async run(e){let t=await Fe(e);e.out.result({environments:t},()=>e.out.table(t,[{header:"slug",value:n=>n.slug},{header:"name",value:n=>n.name},{header:"ref",value:n=>n.ref},{header:"your role",value:n=>n.role}]))}},ro={name:"envs:create",summary:"add an environment to a project (staging, a developer's own)",usage:"<slug> [--name <name>] [--from <env> [--with-cloud]]",details:["Each environment is a project of its own on the platform: its users, data, machines and keys are its own.","--from opens it with another environment's module settings (palbase config:copy: secrets are named, not copied); --with-cloud its machines and databases too \u2014 declared alike, built from the same branches, with no data (palbase db:copy brings it)."],args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called on screen (default: the slug)"},from:{type:"string",value:"env",description:"open it with this environment's settings (slug or ref)"},"with-cloud":{type:"boolean",description:"with --from: its machines and databases too (the new environment's GitHub link builds the machines)"}},scope:"environment",async run(e){let t=await Tc(e),n=e.args[0],s=e.flags.from;if(e.flags["with-cloud"]===!0&&s===void 0)throw g("--with-cloud goes with --from <env>");let r=s===void 0?[]:(await xe(e.studio)).filter(p=>p.project_id===t.project_id),a=s===void 0?void 0:r.find(p=>p.slug===s||p.ref===s);if(s!==void 0&&!a)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(s)}`,{hint:`its environments: ${r.map(p=>p.slug).join(", ")}`});let i=await e.studio.post(`/projects/${t.project_id}/environments`,{name:e.flags.name??n,slug:n}),o=()=>{e.out.line(`Made ${G(t.project_name)} \u203A ${i.environment.slug} (ref ${i.environment.ref}).`),i.keys&&(e.out.line(`publishable key: ${i.keys.publishable.key}`),e.out.line(`secret key (shown this once): ${i.keys.secret.key}`))};if(!a){e.out.result(i,o);return}e.out.json||o();let l=ue({...a,ref:i.environment.ref,slug:i.environment.slug,name:i.environment.name});return e.out.json||e.out.line(),dr(e,e.flags["with-cloud"]===!0?[...Ot,...Kn]:Ot,ue(a),l,{made:i})}},ao={name:"envs:delete",summary:"delete an environment of a project, with its data",usage:"<slug>",args:{min:1,max:1},scope:"environment",async run(e){let n=(await Fe(e)).find(a=>a.slug===e.args[0]||a.ref===e.args[0]);if(!n)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=ue(n);if(e.out.note(`${x(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting an environment is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the environment's slug to delete it: ")).trim()!==n.slug)throw new m("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/environments/${n.ref}`);let r=await e.state.targetFor(e.io.cwd);r?.target.env_ref===n.ref&&await e.state.clearTarget(r.dir),e.out.result({deleted:n.ref},()=>e.out.line(`Deleted ${x(s)}.`))}},io={name:"use",summary:"choose the environment this directory acts on (kept on this computer)",usage:"[<project>[/<env>] | <org>/<project>/<env>] [--clear]",details:["Commands run in this directory, or a directory under it, then act on that environment unless -P/-E say otherwise.","Nothing is written into the project: two checkouts of it can each act on their own environment.","With no argument and a person at the terminal, it lists your environments to pick from."],args:{min:0,max:1},flags:{clear:{type:"boolean",description:"forget the choice made for this directory"}},scope:"account",async run(e){if(e.flags.clear===!0){let n=await e.state.clearTarget(e.io.cwd);e.out.result({cleared:n},()=>e.out.line(n?"This directory acts on no environment now.":"No environment was chosen for this directory."));return}let t;if(e.args.length===0)t=await h(e);else{let n=await xe(e.studio);t=ue(Ec(n,e.args[0]))}await e.state.setTarget(e.io.cwd,t),e.out.result({target:t,dir:e.io.cwd},()=>e.out.line(`This directory acts on ${x(t)}.`))}},oo={name:"status",summary:"the environment this directory acts on: who you are there, its machines and databases",scope:"environment",async run(e){let t=await h(e),n=await e.studio.get(`/environments/${t.env_ref}`),[s,r]=await Promise.all([e.studio.call(`/environments/${t.env_ref}/call`,{method:"GET",path:"/machines?limit=100"}),e.studio.call(`/environments/${t.env_ref}/call`,{method:"GET",path:"/databases?limit=100"})]),a=s.status===200?s.body.machines??[]:[],i=r.status===200?r.body.databases??[]:[],o=await e.state.targetFor(e.io.cwd);e.out.result({target:t,role:n.role,address:`https://${t.env_ref}.${e.cloud.domain}`,machines:a,databases:i,chosen_for:o?.dir??null},()=>{e.out.line(x(t)),e.out.line(` ref ${t.env_ref} \xB7 your role ${n.role} \xB7 https://${t.env_ref}.${e.cloud.domain}`),o&&o.target.env_ref===t.env_ref&&e.out.line(` chosen for ${o.dir}`),e.out.line(` machines: ${a.length===0?"none":a.map(l=>`${l.name} (${l.size}, ${l.state})`).join(", ")}`),e.out.line(` databases: ${i.length===0?"none":i.map(l=>`${l.name} (${l.size}, ${l.state})`).join(", ")}`)})}},lo={name:"open",summary:"open the environment in Studio (its overview, or a section: auth, storage, flags, \u2026)",usage:"[section]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0]?`/${e.args[0].replace(/^\/+/,"")}`:"",s=`${e.cloud.studioWeb}/projects/${t.project_id}/environments/${t.env_ref}${n}`,r=e.interactive&&await e.io.openBrowser(s);e.out.result({url:s,opened:r},()=>e.out.line(r?`Opened ${s}`:s))}},po={name:"keys:list",summary:"the environment's API keys (never their values)",scope:"environment",async run(e){let t=await h(e),{keys:n}=await e.studio.get(`/environments/${t.env_ref}/keys`);e.out.result({keys:n},()=>e.out.table(n,[{header:"id",value:s=>s.id},{header:"kind",value:s=>s.kind},{header:"label",value:s=>s.label},{header:"ends",value:s=>s.hint?`\u2026${s.hint}`:""},{header:"state",value:s=>s.revoked_at?`revoked ${s.revoked_at}`:s.expires_at?`until ${s.expires_at}`:"active"},{header:"created",value:s=>s.created_at}]))}},uo={name:"keys:create",summary:"make an API key of the environment; its value is shown this once",usage:"--kind publishable|secret [--label <label>]",flags:{kind:{type:"string",value:"kind",description:"publishable (ships in apps) or secret (servers only)"},label:{type:"string",value:"label",description:"what it is for, as Studio lists it"}},scope:"environment",async run(e){let t=e.flags.kind;if(t!=="publishable"&&t!=="secret")throw g("--kind is publishable or secret");let n=await h(e);await b(e,n,`make a ${t} key`);let s=await e.studio.post(`/environments/${n.env_ref}/keys`,{kind:t,label:e.flags.label??""});e.out.result(s,()=>{e.out.line(`${s.kind} key ${s.id}${s.kind==="secret"?" (shown this once)":""}:`),e.out.line(s.key)})}},co={name:"keys:revoke",summary:"revoke an API key of the environment; what holds it is refused from then on",usage:"<key-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`revoke the key ${e.args[0]}`),await e.studio.request("DELETE",`/environments/${t.env_ref}/keys/${encodeURIComponent(e.args[0])}`),e.out.result({revoked:e.args[0]},()=>e.out.line(`Revoked ${e.args[0]}.`))}};async function Tc(e){return(await Fe(e))[0]}async function Fe(e){let t=await xe(e.studio);if(e.global.project!==void 0)return mt(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new m("no_target","no project is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});let s=t.filter(r=>r.project_id===n.target.project_id);if(s.length===0)throw new m("no_such_project",`the project chosen here (${n.target.project_name}) is not among yours any more`,{hint:"palbase use another"});return s}function Ec(e,t){let n=t.split("/");if(n.length>3||n.some(a=>a===""))throw g(`${JSON.stringify(t)} is not project, project/env or org/project/env`);let s=n.length===3?[{project:`${n[0]}/${n[1]}`,env:n[2]}]:n.length===2?[{project:n[0],env:n[1]},{project:t}]:[{project:t}],r;for(let a of s){let i;try{i=mt(e,a.project)}catch(o){r=o;continue}if(a.env!==void 0){let o=i.find(l=>l.slug===a.env||l.ref===a.env);if(o)return o;r=new m("no_such_environment",`${i[0].project_name} has no environment ${JSON.stringify(a.env)}`,{hint:`its environments: ${i.map(l=>l.slug).join(", ")}`});continue}if(i.length===1)return i[0];throw new m("which_environment",`${i[0].project_name} has more than one environment`,{hint:`name one: ${i.map(o=>`${o.project_name}/${o.slug}`).join(", ")}`})}throw r}var Cc=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/;async function D(e,t,n,s,r,a){return(await Ae(e.studio,t.env_ref,n,s,r,a)).body}function H(e){if(!Cc.test(e))throw g(`${JSON.stringify(e)} is not a name: lowercase letters, digits and dashes, starting with a letter`);return encodeURIComponent(e)}async function Te(e,t,n){if(n!==void 0)return n;let{machines:s}=await D(e,t,"GET","/machines?limit=100");if(s.length===1)return s[0].name;let r=await Bs(e.io,e.io.cwd),a=r?.repository?s.filter(i=>i.source.repository.toLowerCase()===r.repository?.toLowerCase()&&(i.source.path??"")===r.path):[];if(a.length===1)return a[0].name;throw s.length===0?new m("no_machine","the environment has no machine yet",{hint:"make one with palbase machines:create"}):g("which machine? name it",`the environment's machines: ${s.map(i=>i.name).join(", ")}`)}var mo={name:"machines:sizes",summary:"the machine sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await h(e),{sizes:n}=await D(e,t,"GET","/machines/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"sleeps",value:s=>s.sleeps?"when idle":"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},go={name:"databases:sizes",summary:"the database sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await h(e),{sizes:n}=await D(e,t,"GET","/databases/sizes");e.out.result({sizes:n},()=>e.out.table(n,[{header:"size",value:s=>s.name},{header:"cpu",value:s=>`${s.vcpu} vCPU`},{header:"memory",value:s=>`${s.memory_mib} MiB`},{header:"storage",value:s=>`${(s.storage_bytes/1e9).toFixed(0)} GB`},{header:"sleeps",value:s=>s.sleeps?`when idle${s.awake_hours_per_month?`, ${s.awake_hours_per_month} h awake a month`:""}`:"never"},{header:"open",value:s=>s.available?"yes":`no \u2014 ${s.reason??""}`}]))}},ho={name:"machines:list",summary:"the environment's machines: size, state, the version serving, address",scope:"environment",async run(e){let t=await h(e),{machines:n}=await D(e,t,"GET","/machines?limit=100");e.out.result({machines:n},()=>{n.length===0&&e.out.line("No machine yet: palbase machines:create makes one from the code in this directory."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"size",value:s=>`${s.size}${s.replicas>1?` \xD7${s.replicas}`:""}`},{header:"state",value:s=>s.state},{header:"version",value:s=>s.running_version===s.version?`${s.version}`:`${s.running_version} \u2192 ${s.version}`},{header:"source",value:s=>`${s.source.repository}@${s.source.branch}${s.source.path?`/${s.source.path}`:""}`},{header:"url",value:s=>s.url}])})}},fo={name:"machines:get",summary:"a machine in full: its source, size, environment, database and the build serving",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}`);e.out.result(s,()=>{e.out.line(`${s.name} \u2014 ${s.state}${s.state_reason?` (${s.state_reason})`:""}`),e.out.line(` url ${s.url}`),e.out.line(` source ${s.source.repository} @ ${s.source.branch}${s.source.path?`, ${s.source.path}`:""}${s.source.context?` (context ${s.source.context})`:""}`),e.out.line(` size ${s.size} \xD7 ${s.replicas}, port ${s.port}${s.health_path?`, health ${s.health_path}`:""}`),e.out.line(` version ${s.running_version} serving${s.version!==s.running_version?`, ${s.version} ${s.state_reason?"not up":"rolling out"}`:""}${s.build?` (build ${s.build.id}, ${s.build.commit.slice(0,7)})`:""}`),e.out.line(` contract ${s.contract_digest?`sha256:${s.contract_digest.slice(0,12)}\u2026`:"none read"}${s.openapi_path?`, from ${s.openapi_path}`:""}`),e.out.line(` database ${s.database?.name?`${s.database.name}${s.database.migrations?", its migrations run before each release":""}`:"none"}`),e.out.line(` secrets ${s.secrets?`the ${s.secrets} secrets`:"none"}`),e.out.line(` tests ${s.test_command?`${s.test_command}${s.test_users?.length?`, as ${s.test_users.join(", ")}`:""} \u2014 run on each build before it rolls out`:"none"}`),e.out.line(` env ${Object.keys(s.env).length===0?"none":Object.keys(s.env).sort().join(", ")}`)})}},yo={repo:{type:"string",value:"owner/name",description:"the GitHub repository (default: this checkout's origin)"},branch:{type:"string",value:"branch",description:"the branch whose pushes are built and rolled out (default: the one checked out)"},path:{type:"string",value:"dir",description:"the directory built, from the repository's root (default: this directory's place in it)"},context:{type:"string",value:"dir",description:'what the build is given above --path (a monorepo: ".")'},size:{type:"string",value:"size",description:"its size (palbase machines:sizes lists them)"},replicas:{type:"string",value:"n",description:"how many copies run, each on a node of its own"},port:{type:"string",value:"port",description:"the port it listens on (it also finds it in PORT)"},health:{type:"string",value:"path",description:"where it answers 2xx while well; a release that does not is not rolled out"},"openapi-path":{type:"string",value:"path",description:`where its app serves its OpenAPI contract, when it is not a Palbase backend (apps:sync makes its client from it); "" for a Palbase backend's own`},db:{type:"string",value:"database",description:'a database of the environment it is given (PALBASE_DB_URL); "" for none'},migrations:{type:"boolean",description:"run the database's migrations before each release (a Palbase backend's)"},secrets:{type:"string",value:"production|development",description:'the secrets it is started with, by name; "" for none'},var:{type:"string",multiple:!0,value:"K=V",description:"a setting it is started with (repeat it); secrets go in secrets:set"},"test-command":{type:"string",value:"command",description:'what each new build runs before it takes traffic, in its image, against the new version itself once its migration has run (bun test, npm test); a failure fails the build and the machine runs what it ran; "" for none'},"test-users":{type:"string",value:"names",description:'the test users each test run makes for its tests (api.signInAs), comma-separated; "" for none'}};function bo(e,t){e.flags["test-command"]!==void 0&&(t.test_command=e.flags["test-command"]),e.flags["test-users"]!==void 0&&(t.test_users=Ys(e.flags["test-users"]))}function wo(e){if(!e)return;let t={};for(let n of e){let s=n.indexOf("=");if(s<=0)throw g(`a setting is K=V, not ${JSON.stringify(n)}`);t[n.slice(0,s)]=n.slice(s+1)}return t}function gt(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw g(`--${t} is a whole number`);return n}var vo={name:"machines:create",summary:"make a machine from the code in this directory, built on every push to its branch",usage:"[name] [--size <size>] [--db <database> --migrations] [--repo --branch --path --context] \u2026",details:["Run in a checkout, it reads the repository from origin, the branch from HEAD and the directory from where you are; flags say otherwise.","The first build starts at once; --wait follows it until the machine serves, and prints its address.","The environment needs its GitHub link first: palbase github:connect."],args:{min:0,max:1},flags:{...yo,wait:{type:"boolean",description:"follow the first build until the machine serves"}},scope:"environment",async run(e){let t=await h(e),n=await Bs(e.io,e.io.cwd),s=e.flags.repo??n?.repository,r=e.flags.branch??n?.branch;if(!s)throw g("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!r)throw g("which branch? none is checked out here","pass --branch <branch>");let a=e.flags.size;if(a===void 0){let{sizes:f}=await D(e,t,"GET","/machines/sizes");if(a=f.find(y=>y.available)?.name,!a)throw new m("no_size_open","no machine size is open to this environment",{hint:"palbase machines:sizes says why"})}let i=e.flags.path??n?.path??"",o={...e.args[0]?{name:e.args[0]}:{},source:{repository:s,branch:r,...i!==""?{path:i}:{},...e.flags.context?{context:e.flags.context}:{}},size:a},l=gt(e.flags.replicas,"replicas"),p=gt(e.flags.port,"port");l!==void 0&&(o.replicas=l),p!==void 0&&(o.port=p),e.flags.health!==void 0&&(o.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(o.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(o.secrets=e.flags.secrets);let u=wo(e.flags.var);if(u&&(o.env=u),e.flags.db!==void 0)o.database={name:e.flags.db,migrations:e.flags.migrations===!0};else if(e.flags.migrations===!0)throw g("--migrations goes with --db <database>");bo(e,o),await b(e,t,`make a machine from ${s}@${r}${i?`/${i}`:""} (${a})`);let d=await D(e,t,"POST","/machines",o);e.flags.wait===!0&&(d=await Oc(e,t,d.name)),e.out.result(d,()=>e.out.line(`Made the machine ${d.name} (${d.size}) \u2014 ${d.state}; it answers at ${d.url}`))}},_o={name:"machines:update",summary:"change a machine: the change rolls out as a new version beside the one serving",usage:"[name] [--size \u2026] [--replicas \u2026] [--db \u2026] [--repo --branch --path --context] \u2026",args:{min:0,max:1},flags:yo,scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s={};if(e.flags.repo!==void 0||e.flags.branch!==void 0||e.flags.path!==void 0||e.flags.context!==void 0){let o=await D(e,t,"GET",`/machines/${H(n)}`);s.source={repository:e.flags.repo??o.source.repository,branch:e.flags.branch??o.source.branch,path:e.flags.path??o.source.path??"",context:e.flags.context??o.source.context??""}}e.flags.size!==void 0&&(s.size=e.flags.size);let r=gt(e.flags.replicas,"replicas"),a=gt(e.flags.port,"port");if(r!==void 0&&(s.replicas=r),a!==void 0&&(s.port=a),e.flags.health!==void 0&&(s.health_path=e.flags.health),e.flags["openapi-path"]!==void 0&&(s.openapi_path=e.flags["openapi-path"]),e.flags.secrets!==void 0&&(s.secrets=e.flags.secrets),e.flags.var!==void 0)throw g("machines:update leaves the settings as they are: machines:env:set and machines:env:unset change them");if(e.flags.db!==void 0&&(s.database={name:e.flags.db,migrations:e.flags.migrations===!0}),bo(e,s),Object.keys(s).length===0)throw g("nothing to change: give a flag","palbase help machines:update");await b(e,t,`change the machine ${n}: ${Object.keys(s).join(", ")}`);let i=await D(e,t,"PATCH",`/machines/${H(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: version ${i.version} rolls out beside ${i.running_version}.`))}},So={name:"machines:env:list",summary:"what a machine is started with, and where each comes from (the platform, the machine, its secrets)",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}`),r=s.secrets?await D(e,t,"GET",`/secrets/${encodeURIComponent(s.secrets)}`).then(o=>(o.items??o.secrets??[]).map(l=>l.name),()=>[]):[],a=["PORT","PALBASE_URL","PALBASE_PROJECT_ID","PALBASE_REPLICAS","PALBASE_PLATFORM_SECRET","PALBASE_SECRET_KEY","PALBASE_WEBHOOK_SECRET",...s.database?.name?["PALBASE_DB_URL","PALBASE_DB_CONNECTIONS"]:[]],i=a.map(o=>({name:o,from:"platform",note:r.includes(o)||o in s.env?`in place of the ${o} set by hand`:""}));for(let o of Object.keys(s.env).sort())a.includes(o)||i.push({name:o,from:"machine",note:r.includes(o)?`shadows the secret ${o}`:""});for(let o of r.sort())a.includes(o)||o in s.env||i.push({name:o,from:`secrets (${s.secrets})`,note:""});e.out.result({machine:s.name,env:i},()=>e.out.table(i,[{header:"name",value:o=>o.name},{header:"from",value:o=>o.from},{header:"",value:o=>o.note}]))}};async function ko(e,t){let n=await h(e),s=await Te(e,n,e.flags.machine);for(let r=1;;r++){let a=await Ae(e.studio,n.env_ref,"GET",`/machines/${H(s)}`),i={...a.body.env},o=t(i);if(r===1){if(a.body.secrets){let l=await D(e,n,"GET",`/secrets/${encodeURIComponent(a.body.secrets)}`).catch(()=>({items:[]}));for(let p of Object.keys(i))(l.items??[]).some(u=>u.name===p)&&e.out.warn(`${p} in the machine's env shadows the secret of that name`)}await b(e,n,`${o.join(", ")} on the machine ${s}`)}try{let l=await D(e,n,"PATCH",`/machines/${H(s)}`,{env:i},a.etag?{"if-match":a.etag}:void 0);e.out.result({machine:l.name,env:Object.keys(l.env).sort()},()=>e.out.line(`${l.name}: version ${l.version} rolls out with it.`));return}catch(l){if(!(l instanceof Y&&l.status===412)||r===3)throw l;e.out.note(`${s} changed meanwhile: making the change again on what it is now\u2026`)}}}var $o={name:"machines:env:set",summary:"set settings a machine is started with (not secrets: secrets:set keeps those)",usage:"K=V\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=wo(e.args);await ko(e,n=>(Object.assign(n,t),Object.keys(t).map(s=>`set ${s}`)))}},Ao={name:"machines:env:unset",summary:"take settings away from what a machine is started with",usage:"K\u2026 [--machine <name>]",args:{min:1,max:50},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){await ko(e,t=>{for(let n of e.args){if(!(n in t))throw new m("no_such_setting",`the machine is not started with ${n}`);delete t[n]}return e.args.map(n=>`unset ${n}`)})}},Po={name:"machines:builds",summary:"a machine's builds, newest first",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),{builds:s}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=20`);e.out.result({builds:s},()=>e.out.table(s,[{header:"build",value:r=>r.id},{header:"commit",value:r=>r.commit.slice(0,7)},{header:"state",value:r=>`${r.state}${r.running?" (serving)":""}${r.reason?` \u2014 ${r.reason}`:""}`},{header:"message",value:r=>r.message.split(`
30
30
  `)[0].slice(0,60)},{header:"created",value:r=>r.created_at}]))}},Ro={name:"machines:builds:log",summary:"a build's log (the newest build's when none is named)",usage:"[build] [--machine <name>]",args:{min:0,max:1},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.flags.machine),s=e.args[0];if(!s){let{builds:i}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=1`);if(i.length===0)throw new m("no_build",`${n} has no build yet`);s=i[0].id}let r=await D(e,t,"GET",`/machines/${H(n)}/builds/${encodeURIComponent(s)}/log`),a=typeof r=="string"?r:JSON.stringify(r);e.out.result({build:s,log:a},()=>{for(let i of a.split(`
31
- `))e.out.line(i)})}},To={name:"machines:logs",summary:"a machine's log: the last hour, or since a time; -f follows what it writes next",usage:"[name] [-f] [--since <time>] [--lines <n>] [--search <text>]",args:{min:0,max:1},flags:{follow:{type:"boolean",short:"f",description:"keep printing what the machine writes"},since:{type:"string",value:"time",description:"from when: an RFC 3339 time, or 10m, 2h, 1d ago"},lines:{type:"string",value:"n",description:"how many lines at most (default 200)"},search:{type:"string",value:"text",description:"only the lines that hold this text"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=gt(e.flags.lines,"lines")??200,r=e.flags.since?Dc(e.flags.since,e.io.now()):void 0,a=e.flags.search,i=new Set,o=async d=>{let u=new URLSearchParams({lines:String(s),order:d,...r?{since:r}:{},...a?{q:a}:{}}),p=await D(e,t,"GET",`/machines/${H(n)}/logs?${u}`),f=p!==null&&typeof p=="object"?[p]:String(p??"").split(`
32
- `).filter(y=>y.trim()!=="").map(y=>JSON.parse(y));return d==="desc"?f.reverse():f},l=d=>{for(let u of d)i.has(u.id)||(i.add(u.id),e.global.json?e.io.stdout(`${JSON.stringify(u)}
33
- `):e.out.line(`${u.ts} ${u.pod?`[${u.pod.slice(-5)}] `:""}${u.line}`));d.length>0&&(r=d[d.length-1].ts)};for(l(await o("desc"));e.flags.follow===!0;)await e.io.sleep(2e3),l(await o("asc"))}},Eo={name:"machines:deploy",summary:"build the head of a machine's branch now and roll it out, as a push does",usage:"[name] [--wait]",details:["A push to the branch does this by itself; this is for the one GitHub did not deliver, or a build again.","The commit is the branch's head on GitHub: push first."],args:{min:0,max:1},flags:{wait:{type:"boolean",description:"follow the build until the machine serves it"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]);await b(e,t,`build and roll out ${n} from the head of its branch`);let s=await D(e,t,"POST",`/machines/${H(n)}/builds`);e.flags.wait===!0&&(s=await jc(e,t,n)),e.out.result(s,()=>e.out.line(e.flags.wait===!0?`${s.name} serves version ${s.running_version}${s.build?` (${s.build.commit.slice(0,7)})`:""}.`:`${s.name}: a build of ${s.source.branch}'s head is on its way (palbase machines:builds).`))}};async function jc(e,t,n){let s="";for(let r=0;r<900;r++){let a=await D(e,t,"GET",`/machines/${H(n)}`);if(!a.build_requested){let{builds:i}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=1`),o=i[0];if(!o)throw new m("build_failed",`${n} has no build${a.state_reason?`: ${a.state_reason}`:""}`,{hint:`palbase machines:get ${n}`});let l=`build ${o.id.slice(0,8)} (${o.commit.slice(0,7)}): ${o.state}${o.running?", serving":""}`;if(l!==s&&e.out.note(` ${l}`),s=l,o.state==="failed"||o.state==="cancelled"||o.state==="skipped")throw new m("build_failed",`the build ${o.state}${o.reason?`: ${o.reason}`:""}`,{hint:`palbase machines:builds:log ${o.id} --machine ${n}`});if(o.state==="succeeded"&&a.state==="failed")throw new m("build_failed",`${n}: ${a.state_reason??"failed"}`,{hint:`palbase machines:get ${n}`});if(o.running&&a.running_version===a.version)return a}await e.io.sleep(2e3)}throw new m("timeout",`${n} was not serving the build after 30 minutes`,{hint:`palbase machines:builds ${n}`})}var Co={name:"machines:rollback",summary:"serve an earlier build again (the one before the serving one, or a named one)",usage:"[name] [--to <build>]",args:{min:0,max:1},flags:{to:{type:"string",value:"build",description:"the build to go back to (default: the last one that served before)"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=e.flags.to;if(!s){let{builds:a}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=50`),i=a.findIndex(l=>l.running),o=a.slice(i+1).find(l=>l.state==="succeeded");if(i<0||!o)throw new m("no_earlier_build",`${n} has no earlier build that succeeded`,{hint:"name one with --to (palbase machines:builds lists them)"});s=o.id}await b(e,t,`roll ${n} back to build ${s}`);let r=await D(e,t,"POST",`/machines/${H(n)}/rollback`,{build:s});e.out.result(r,()=>e.out.line(`${r.name} rolls back to build ${s} (version ${r.version}).`))}},jo={name:"machines:jobs",summary:"the jobs a machine runs on a schedule, when each is next due and how its last run went",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}/jobs`),r=s.jobs??[];e.out.result(s,()=>e.out.table(r,[{header:"job",value:a=>a.name},{header:"schedule",value:a=>a.schedule},{header:"next",value:a=>a.next_at??""},{header:"last run",value:a=>a.last_run?`${a.last_run.state} ${a.last_run.started_at??""}`:""}]))}},Oo={name:"machines:jobs:runs",summary:"a job's runs, newest first",usage:"<job> [--machine <name>]",args:{min:1,max:1},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.flags.machine),s=await D(e,t,"GET",`/machines/${H(n)}/jobs/${encodeURIComponent(e.args[0])}/runs`),r=s.runs??[];e.out.result(s,()=>e.out.table(r,[{header:"run",value:a=>a.id},{header:"state",value:a=>a.state},{header:"started",value:a=>a.started_at??""},{header:"finished",value:a=>a.finished_at??""}]))}},Do={name:"machines:contract",summary:"the API a Palbase backend serves (its OpenAPI document), as the platform reads it from the machine",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}/openapi`);e.io.stdout(`${JSON.stringify(s,null,2)}
34
- `)}},Io={name:"machines:delete",summary:"delete a machine: it stops serving, its address answers nothing",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting a machine is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the machine's name to delete it: ")).trim()!==n)throw new m("cancelled","nothing was deleted")}await D(e,t,"DELETE",`/machines/${H(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the machine ${n}.`))}};async function Oc(e,t,n){e.out.note(`Following ${n}'s first build\u2026`);let s="";for(let r=0;r<900;r++){let a=await D(e,t,"GET",`/machines/${H(n)}`),i=`${a.state}${a.build?` (${a.build.commit.slice(0,7)})`:""}`;if(i!==s&&e.out.note(` ${i}${a.state_reason?` \u2014 ${a.state_reason}`:""}`),s=i,a.running_version>=1&&a.running_version===a.version&&(a.state==="awake"||a.state==="asleep"))return a;if(a.state==="failed")throw new m("build_failed",`${n}'s build failed${a.state_reason?`: ${a.state_reason}`:""}`,{hint:`palbase machines:builds:log --machine ${n}`});await e.io.sleep(2e3)}throw new m("timeout",`${n} was not serving after 30 minutes`,{hint:`palbase machines:get ${n}`})}function Dc(e,t){let n=/^(\d+)([smhd])$/.exec(e);if(n)return new Date(t-Number(n[1])*{s:1e3,m:6e4,h:36e5,d:864e5}[n[2]]).toISOString();if(Number.isNaN(Date.parse(e)))throw g(`--since is a time (2026-10-08T12:00:00Z) or a duration ago (10m, 2h, 1d), not ${JSON.stringify(e)}`);return new Date(e).toISOString()}var Lo={name:"databases:list",summary:"the environment's databases: size, state, how full",scope:"environment",async run(e){let t=await h(e),{databases:n}=await D(e,t,"GET","/databases?limit=100");e.out.result({databases:n},()=>{n.length===0&&e.out.line("No database yet: palbase databases:create makes one."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"size",value:s=>s.size},{header:"state",value:s=>`${s.state}${s.deleted_at?" (deleted)":""}`},{header:"used",value:s=>s.used_bytes!==void 0?`${(s.used_bytes/1e6).toFixed(1)} / ${(s.quota_bytes/1e6).toFixed(0)} MB`:""},{header:"host",value:s=>s.host}])})}},No={name:"databases:get",summary:"a database in full, with how to connect to it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await D(e,t,"GET",`/databases/${H(e.args[0])}`),s=await e.state.databasePasswords(t.env_ref,n.name);e.out.result({...n,passwords_on_this_computer:{owner:s.owner!==void 0,app:s.app!==void 0}},()=>{e.out.line(`${n.name} \u2014 ${n.size}, ${n.state}${n.state_reason?` (${n.state_reason})`:""}`),e.out.line(` host ${n.host}:${n.port} (pooled ${n.pooled_port})`),e.out.line(` allows ${n.allow.length===0?"anyone with the password":n.allow.join(", ")}`),e.out.line(` backups ${n.backup_days} days, any moment of them (databases:restore --to)`),n.used_bytes!==void 0&&e.out.line(` used ${(n.used_bytes/1e6).toFixed(1)} MB of ${(n.quota_bytes/1e6).toFixed(0)} MB`),n.awake_hours_limit&&e.out.line(` awake ${n.awake_hours??0} of ${n.awake_hours_limit} hours this month`),e.out.line(` passwords ${s.owner||s.app?`this computer holds ${[s.owner&&"owner",s.app&&"app"].filter(Boolean).join(" and ")}`:"none on this computer (databases:password:reset makes new ones)"}`)})}};function qo(e){if(e!==void 0)return String(e).split(",").map(t=>t.trim()).filter(t=>t!=="")}var Mo={name:"databases:create",summary:"open a database; its passwords are kept on this computer (the platform shows them once)",usage:"[name] [--size <size>] [--allow <ip/cidr,\u2026>] [--backup-days <n>] [--show-passwords]",args:{min:0,max:1},flags:{size:{type:"string",value:"size",description:"its size (palbase databases:sizes lists what is open)"},allow:{type:"string",value:"list",description:"who may connect, as addresses or CIDRs, comma-separated (default: anyone with the password)"},"backup-days":{type:"string",value:"n",description:"how many days of backups to keep, 1 to 35"},"show-passwords":{type:"boolean",description:"print the passwords too (they are kept on this computer either way)"}},scope:"environment",async run(e){let t=await h(e),n={};e.args[0]&&(n.name=e.args[0]),e.flags.size!==void 0&&(n.size=e.flags.size);let s=qo(e.flags.allow);s&&(n.allow=s);let r=gt(e.flags["backup-days"],"backup-days");r!==void 0&&(n.backup_days=r),await b(e,t,`open a database ${e.args[0]??"main"}${n.size?` (${n.size})`:""}`);let a=await D(e,t,"POST","/databases",n);await e.state.keepDatabasePasswords(t.env_ref,a.name,{owner:a.credentials.owner.password,app:a.credentials.app.password});let i=e.flags["show-passwords"]===!0,{credentials:o,...l}=a;e.out.result(i?a:{...l,credentials:{owner:mr(o.owner),app:mr(o.app)}},()=>{e.out.line(`Opened the database ${a.name} (${a.size}) at ${a.host}.`),e.out.line("Its passwords are kept on this computer: palbase db:url and db:shell use them."),i&&(e.out.line(`owner: ${a.credentials.owner.password??""}`),e.out.line(`app: ${a.credentials.app.password??""}`))})}},cr="[YOUR-PASSWORD]";function mr(e){let{password:t,...n}=e,s={};for(let[r,a]of Object.entries(n))s[r]=typeof a=="string"?Ic(a,t):a;return s}function Ic(e,t){let n=e.replace(/(postgres(?:ql)?:\/\/[^:/@\s'"]+:)[^@\s'"]*(@)/gi,`$1${cr}$2`).replace(/([?&;]password=)[^&;\s'"]*/gi,`$1${cr}`);if(t)for(let s of new Set([t,encodeURIComponent(t),new URLSearchParams({p:t}).toString().slice(2)]))n=n.split(s).join(cr);return n}var Uo={name:"databases:update",summary:"change a database's size, who may connect to it, or how long its backups are kept",usage:'<name> [--size <size>] [--allow <list>|""] [--backup-days <n>] [--wait]',details:["A new size is taken at once and the database moves to it serving on (palbase databases:sizes lists them): its sessions on the direct port end once, when its primary moves, and new logins wait a few seconds then."],args:{min:1,max:1},flags:{size:{type:"string",value:"size",description:"the size to move it to"},allow:{type:"string",value:"list",description:'who may connect (comma-separated addresses or CIDRs); "" for anyone with the password'},"backup-days":{type:"string",value:"n",description:"how many days of backups to keep, 1 to 35"},wait:{type:"boolean",description:"with --size: follow the move until it serves at the new size"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s={},r=e.flags.size;if(r!==void 0){if(r==="")throw g("--size is one of palbase databases:sizes");s.size=r}let a=qo(e.flags.allow);a&&(s.allow=a);let i=gt(e.flags["backup-days"],"backup-days");if(i!==void 0&&(s.backup_days=i),Object.keys(s).length===0)throw g("nothing to change: --size, --allow or --backup-days");await b(e,t,`change the database ${n}: ${Object.keys(s).join(", ")}`);let o=await D(e,t,"PATCH",`/databases/${H(n)}`,s);r!==void 0&&e.flags.wait===!0&&(o=await Lc(e,t,n,r)),e.out.result(o,()=>{r!==void 0&&e.out.line(o.state==="resizing"?`${o.name} is moving to ${r}, serving on: its sessions on the direct port end once, when its primary moves (palbase databases:get ${o.name} follows it).`:`${o.name} serves at ${o.size} (${o.state}).`),(a||i!==void 0)&&e.out.line(`${o.name}: allows ${o.allow.length===0?"anyone with the password":o.allow.join(", ")}; backups ${o.backup_days} days.`)})}};async function Lc(e,t,n,s){e.out.note(`Following ${n}'s move to ${s}\u2026`);let r="";for(let a=0;a<900;a++){let i=await D(e,t,"GET",`/databases/${H(n)}`),o=`${i.state}${i.state_reason?` \u2014 ${i.state_reason}`:""}`;if(o!==r&&e.out.note(` ${o}`),r=o,i.size===s&&i.state!=="resizing"&&i.state!=="starting"){if(i.state==="failed")throw new m("resize_failed",`${n} did not come up at ${s}${i.state_reason?`: ${i.state_reason}`:""}`,{hint:`palbase databases:get ${n}`});return i}if(i.size!==s)throw new m("resize_changed",`${n} is asked for ${i.size} now, not ${s}: another change came`,{hint:`palbase databases:get ${n}`});await e.io.sleep(2e3)}throw new m("timeout",`${n} was still moving to ${s} after 30 minutes; it serves on meanwhile`,{hint:`palbase databases:get ${n}`})}var xo={name:"databases:password:reset",summary:"make a database role's password new; it is kept on this computer, the old one stops working",usage:"<name> --role owner|app [--show-password]",details:["The app role's new password reaches the machines given the database as they roll out with it; they keep serving."],args:{min:1,max:1},flags:{role:{type:"string",value:"role",description:"owner (migrations, the console) or app (what the machines use)"},"show-password":{type:"boolean",description:"print it too"}},scope:"environment",async run(e){let t=e.flags.role;if(t!=="owner"&&t!=="app")throw g("--role is owner or app");let n=await h(e);await b(e,n,`make the ${t} password of ${e.args[0]} new`);let r=(await D(e,n,"POST",`/databases/${H(e.args[0])}/password`,{role:t}))[t];if(!r?.password)throw new m("no_password","the platform answered no password");await e.state.keepDatabasePasswords(n.env_ref,e.args[0],{[t]:r.password}),await Nc(e,n,e.args[0]),e.out.result(e.flags["show-password"]===!0?r:mr(r),()=>{e.out.line(`The ${t} password of ${e.args[0]} is new and kept on this computer.`),e.flags["show-password"]===!0&&e.out.line(r.password??"")})}};async function Nc(e,t,n){for(let s=0;s<60;s++){if((await D(e,t,"GET",`/databases/${H(n)}`)).password_changing!==!0)return;s===0&&e.out.note(`Waiting for ${n} to take the new password\u2026`),await e.io.sleep(1e3)}throw new m("timeout",`${n} had not taken the new password after a minute; it is kept here and works once it has`,{hint:`palbase databases:get ${n}`})}var Ko={name:"databases:restore",summary:"bring a database back to a moment of its backups",usage:"<name> --to <time>",args:{min:1,max:1},flags:{to:{type:"string",value:"time",description:"the moment, as an RFC 3339 time (2026-10-08T12:00:00Z)"}},scope:"environment",async run(e){let t=e.flags.to;if(!t||Number.isNaN(Date.parse(t)))throw g("--to is the moment to go back to, as an RFC 3339 time");let n=await h(e);await b(e,n,`bring ${e.args[0]} back to ${t} (what was written since goes)`);let s=await D(e,n,"POST",`/databases/${H(e.args[0])}/restore`,{to:new Date(t).toISOString()});e.out.result(s,()=>e.out.line(`${s.name} is being brought back to ${t} \u2014 ${s.state}.`))}};function gr(e,t,n,s,r,a,i=!1){return{name:e,summary:t,usage:"<name>",args:{min:1,max:1},scope:"environment",async run(o){let l=await h(o),d=o.args[0];if(n==="DELETE"||s==="/purge"){if(!o.global.yes){if(!o.interactive)throw new m("confirmation_needed",`${r(d)} is confirmed`,{hint:"confirm with -y"});if((await o.io.prompt("Type the database's name to go ahead: ")).trim()!==d)throw new m("cancelled","nothing was changed")}}else await b(o,l,r(d));await D(o,l,n,`/databases/${H(d)}${s}`),i&&await o.state.forgetDatabase(l.env_ref,d),o.out.result({database:d,done:e},()=>o.out.line(a(d)))}}}var Go=gr("databases:delete","delete a database (the platform keeps it 7 days to take back)","DELETE","",e=>`deleting the database ${e}`,e=>`Deleted ${e}; databases:undelete takes it back within 7 days.`),Bo=gr("databases:undelete","take back a database deleted in the last 7 days","POST","/undelete",e=>`take back the database ${e}`,e=>`${e} is back.`),Jo=gr("databases:purge","delete a deleted database for good, now","POST","/purge",e=>`purging the database ${e} for good`,e=>`${e} is purged.`,!0),zo={name:"github:connect",summary:"link the environment to GitHub: Palbase Cloud's GitHub app on the account whose repositories machines are built from",usage:"[--device | --github-token-stdin] [--installation <account|id>] [--all-envs]",details:["Opens GitHub; back from it, the page shows the account and you link it by writing the environment's ref.","--device: no browser here \u2014 GitHub's device flow (a code you enter at github.com/login/device on any device); the GitHub token stays in memory.","--all-envs links every environment of the project the same way."],flags:{device:{type:"boolean",description:"GitHub's device flow, for a computer with no browser (a server, an agent)"},"github-token-stdin":{type:"boolean",description:"a GitHub user's token from stdin (CI): the installations it reaches, no browser and no code"},installation:{type:"string",value:"account|id",description:"which installation to link, when the GitHub user reaches more than one (--device)"},"all-envs":{type:"boolean",description:"link every environment of the project"}},scope:"environment",async run(e){let t=await h(e),n=e.flags["all-envs"]===!0?(await Fe(e)).map(ue):[t],s=await D(e,t,"POST","/machines/github/connect"),r=e.flags["github-token-stdin"]===!0;if(!r&&e.flags.device!==!0&&e.interactive&&n.length===1){let l=await e.io.openBrowser(s.url);e.out.result({url:s.url,expires_at:s.expires_at,ref:t.env_ref},()=>e.out.line(l?`Opened GitHub. When it asks, link the environment ${t.env_ref}.`:`Open ${s.url} (good until ${s.expires_at}) and link the environment ${t.env_ref}.`));return}let a=r?await fe(e,"a GitHub user's token"):await qc(e,s.client_id),i=await Mc(e,a),o=[];for(let l of n){await b(e,l,`link the GitHub account ${i.account}`);let d=await D(e,l,"POST","/machines/github/installations",{installation_id:i.id,user_token:a});o.push({ref:l.env_ref,account:d.account})}e.out.result({linked:o},()=>{for(let l of o)e.out.line(`Linked ${l.account} to ${l.ref}.`)})}};async function qc(e,t){let n=o=>({method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams(o)}),s=await e.io.fetch("https://github.com/login/device/code",n({client_id:t}));if(!s.ok)throw new m("github_unavailable",`GitHub did not start the sign-in: ${s.status}`);let r=await s.json();e.out.note(""),e.out.note(` Enter this code at ${r.verification_uri}: ${r.user_code}`),e.out.note("");let a=Math.max(1,r.interval??5),i=e.io.now()+(r.expires_in??900)*1e3;for(;e.io.now()<i;){await e.io.sleep(a*1e3);let o;try{o=await e.io.fetch("https://github.com/login/oauth/access_token",n({client_id:t,device_code:r.device_code,grant_type:"urn:ietf:params:oauth:grant-type:device_code"}))}catch{continue}if(o.status>=500||o.status===429)continue;let l=await o.json().catch(()=>({}));if(l.access_token)return l.access_token;if(l.error!=="authorization_pending"){if(l.error==="slow_down"){a+=5;continue}if(l.error==="expired_token")break;throw l.error==="access_denied"?new m("access_denied","the sign-in was denied on GitHub"):new m("github_denied",`GitHub's sign-in ended: ${l.error??o.status}`)}}throw new m("expired_code","GitHub's code expired before it was entered",{hint:"run the command again"})}async function Mc(e,t){let n=await e.io.fetch("https://api.github.com/user/installations?per_page=100",{headers:{authorization:`Bearer ${t}`,accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!n.ok)throw new m("github_unavailable",`GitHub did not list your installations: ${n.status}`);let s=(await n.json()).installations.map(a=>({id:a.id,account:a.account.login}));if(s.length===0)throw new m("no_installation","Palbase Cloud is installed on no GitHub account you reach",{hint:"install it (palbase github:connect, in a browser) or have the account's owner install it"});let r=e.flags.installation;if(r!==void 0){let a=s.find(i=>String(i.id)===r||i.account.toLowerCase()===r.toLowerCase());if(!a)throw new m("no_such_installation",`you reach no installation ${JSON.stringify(r)}: ${s.map(i=>i.account).join(", ")}`);return a}if(s.length===1)return s[0];if(!e.interactive)throw g("which installation? --installation <account>",`yours: ${s.map(a=>a.account).join(", ")}`);for(s.forEach((a,i)=>e.out.note(` ${i+1}) ${a.account}`));;){let a=Number((await e.io.prompt(`Which account? [1-${s.length}] `)).trim());if(Number.isInteger(a)&&a>=1&&a<=s.length)return s[a-1]}}var Wo={name:"github:list",summary:"the GitHub accounts linked to the environment, and who linked each",scope:"environment",async run(e){let t=await h(e),{installations:n}=await D(e,t,"GET","/machines/github/installations");e.out.result({installations:n},()=>{n.length===0&&e.out.line("No GitHub account is linked: palbase github:connect links one."),e.out.table(n,[{header:"account",value:s=>s.account},{header:"installation",value:s=>String(s.installation_id)},{header:"linked by",value:s=>s.linked_by}])})}},Fo={name:"github:repos",summary:"the repositories machines of the environment may be built from",scope:"environment",async run(e){let t=await h(e),{repositories:n}=await D(e,t,"GET","/machines/github/repositories");e.out.result({repositories:n},()=>e.out.table(n,[{header:"repository",value:s=>s.repository},{header:"default branch",value:s=>s.default_branch},{header:"",value:s=>s.private?"private":""}]))}},Vo={name:"github:disconnect",summary:"unlink a GitHub account from the environment; its repositories are no longer built",usage:"<account|installation-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),{installations:n}=await D(e,t,"GET","/machines/github/installations"),s=n.find(r=>String(r.installation_id)===e.args[0]||r.account.toLowerCase()===e.args[0].toLowerCase());if(!s)throw new m("no_such_installation",`no linked GitHub account ${JSON.stringify(e.args[0])}`);await b(e,t,`unlink the GitHub account ${s.account}`),await D(e,t,"DELETE",`/machines/github/installations/${s.installation_id}`),e.out.result({unlinked:s},()=>e.out.line(`Unlinked ${s.account}.`))}};import{createHash as Yg}from"node:crypto";import{lstat as Zg,mkdir as Qg,mkdtemp as eh,readdir as Tr,readFile as Er,rm as pd,rmdir as th,writeFile as nh}from"node:fs/promises";import{tmpdir as sh}from"node:os";import{dirname as rh,join as Oe,relative as ps,resolve as od,sep as ds}from"node:path";function Kc(e){return e.replace(/\s+/g," ").trim()}var cS=String.raw`
31
+ `))e.out.line(i)})}},To={name:"machines:logs",summary:"a machine's log: the last hour, or since a time; -f follows what it writes next",usage:"[name] [-f] [--since <time>] [--lines <n>] [--search <text>]",args:{min:0,max:1},flags:{follow:{type:"boolean",short:"f",description:"keep printing what the machine writes"},since:{type:"string",value:"time",description:"from when: an RFC 3339 time, or 10m, 2h, 1d ago"},lines:{type:"string",value:"n",description:"how many lines at most (default 200)"},search:{type:"string",value:"text",description:"only the lines that hold this text"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=gt(e.flags.lines,"lines")??200,r=e.flags.since?Dc(e.flags.since,e.io.now()):void 0,a=e.flags.search,i=new Set,o=async p=>{let u=new URLSearchParams({lines:String(s),order:p,...r?{since:r}:{},...a?{q:a}:{}}),d=await D(e,t,"GET",`/machines/${H(n)}/logs?${u}`),f=d!==null&&typeof d=="object"?[d]:String(d??"").split(`
32
+ `).filter(y=>y.trim()!=="").map(y=>JSON.parse(y));return p==="desc"?f.reverse():f},l=p=>{for(let u of p)i.has(u.id)||(i.add(u.id),e.global.json?e.io.stdout(`${JSON.stringify(u)}
33
+ `):e.out.line(`${u.ts} ${u.pod?`[${u.pod.slice(-5)}] `:""}${u.line}`));p.length>0&&(r=p[p.length-1].ts)};for(l(await o("desc"));e.flags.follow===!0;)await e.io.sleep(2e3),l(await o("asc"))}},Eo={name:"machines:deploy",summary:"build the head of a machine's branch now and roll it out, as a push does",usage:"[name] [--wait]",details:["A push to the branch does this by itself; this is for the one GitHub did not deliver, or a build again.","The commit is the branch's head on GitHub: push first."],args:{min:0,max:1},flags:{wait:{type:"boolean",description:"follow the build until the machine serves it"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]);await b(e,t,`build and roll out ${n} from the head of its branch`);let s=await D(e,t,"POST",`/machines/${H(n)}/builds`);e.flags.wait===!0&&(s=await jc(e,t,n)),e.out.result(s,()=>e.out.line(e.flags.wait===!0?`${s.name} serves version ${s.running_version}${s.build?` (${s.build.commit.slice(0,7)})`:""}.`:`${s.name}: a build of ${s.source.branch}'s head is on its way (palbase machines:builds).`))}};async function jc(e,t,n){let s="";for(let r=0;r<900;r++){let a=await D(e,t,"GET",`/machines/${H(n)}`);if(!a.build_requested){let{builds:i}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=1`),o=i[0];if(!o)throw new m("build_failed",`${n} has no build${a.state_reason?`: ${a.state_reason}`:""}`,{hint:`palbase machines:get ${n}`});let l=`build ${o.id.slice(0,8)} (${o.commit.slice(0,7)}): ${o.state}${o.running?", serving":""}`;if(l!==s&&e.out.note(` ${l}`),s=l,o.state==="failed"||o.state==="cancelled"||o.state==="skipped")throw new m("build_failed",`the build ${o.state}${o.reason?`: ${o.reason}`:""}`,{hint:`palbase machines:builds:log ${o.id} --machine ${n}`});if(o.state==="succeeded"&&a.state==="failed")throw new m("build_failed",`${n}: ${a.state_reason??"failed"}`,{hint:`palbase machines:get ${n}`});if(o.running&&a.running_version===a.version)return a}await e.io.sleep(2e3)}throw new m("timeout",`${n} was not serving the build after 30 minutes`,{hint:`palbase machines:builds ${n}`})}var Co={name:"machines:rollback",summary:"serve an earlier build again (the one before the serving one, or a named one)",usage:"[name] [--to <build>]",args:{min:0,max:1},flags:{to:{type:"string",value:"build",description:"the build to go back to (default: the last one that served before)"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=e.flags.to;if(!s){let{builds:a}=await D(e,t,"GET",`/machines/${H(n)}/builds?limit=50`),i=a.findIndex(l=>l.running),o=a.slice(i+1).find(l=>l.state==="succeeded");if(i<0||!o)throw new m("no_earlier_build",`${n} has no earlier build that succeeded`,{hint:"name one with --to (palbase machines:builds lists them)"});s=o.id}await b(e,t,`roll ${n} back to build ${s}`);let r=await D(e,t,"POST",`/machines/${H(n)}/rollback`,{build:s});e.out.result(r,()=>e.out.line(`${r.name} rolls back to build ${s} (version ${r.version}).`))}},jo={name:"machines:jobs",summary:"the jobs a machine runs on a schedule, when each is next due and how its last run went",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}/jobs`),r=s.jobs??[];e.out.result(s,()=>e.out.table(r,[{header:"job",value:a=>a.name},{header:"schedule",value:a=>a.schedule},{header:"next",value:a=>a.next_at??""},{header:"last run",value:a=>a.last_run?`${a.last_run.state} ${a.last_run.started_at??""}`:""}]))}},Oo={name:"machines:jobs:runs",summary:"a job's runs, newest first",usage:"<job> [--machine <name>]",args:{min:1,max:1},flags:{machine:{type:"string",value:"name",description:"the machine (default: the only one, or this directory's)"}},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.flags.machine),s=await D(e,t,"GET",`/machines/${H(n)}/jobs/${encodeURIComponent(e.args[0])}/runs`),r=s.runs??[];e.out.result(s,()=>e.out.table(r,[{header:"run",value:a=>a.id},{header:"state",value:a=>a.state},{header:"started",value:a=>a.started_at??""},{header:"finished",value:a=>a.finished_at??""}]))}},Do={name:"machines:contract",summary:"the API a Palbase backend serves (its OpenAPI document), as the platform reads it from the machine",usage:"[name]",args:{min:0,max:1},scope:"environment",async run(e){let t=await h(e),n=await Te(e,t,e.args[0]),s=await D(e,t,"GET",`/machines/${H(n)}/openapi`);e.io.stdout(`${JSON.stringify(s,null,2)}
34
+ `)}},Io={name:"machines:delete",summary:"delete a machine: it stops serving, its address answers nothing",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=e.args[0];if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed","deleting a machine is confirmed",{hint:"confirm with -y"});if((await e.io.prompt("Type the machine's name to delete it: ")).trim()!==n)throw new m("cancelled","nothing was deleted")}await D(e,t,"DELETE",`/machines/${H(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the machine ${n}.`))}};async function Oc(e,t,n){e.out.note(`Following ${n}'s first build\u2026`);let s="";for(let r=0;r<900;r++){let a=await D(e,t,"GET",`/machines/${H(n)}`),i=`${a.state}${a.build?` (${a.build.commit.slice(0,7)})`:""}`;if(i!==s&&e.out.note(` ${i}${a.state_reason?` \u2014 ${a.state_reason}`:""}`),s=i,a.running_version>=1&&a.running_version===a.version&&(a.state==="awake"||a.state==="asleep"))return a;if(a.state==="failed")throw new m("build_failed",`${n}'s build failed${a.state_reason?`: ${a.state_reason}`:""}`,{hint:`palbase machines:builds:log --machine ${n}`});await e.io.sleep(2e3)}throw new m("timeout",`${n} was not serving after 30 minutes`,{hint:`palbase machines:get ${n}`})}function Dc(e,t){let n=/^(\d+)([smhd])$/.exec(e);if(n)return new Date(t-Number(n[1])*{s:1e3,m:6e4,h:36e5,d:864e5}[n[2]]).toISOString();if(Number.isNaN(Date.parse(e)))throw g(`--since is a time (2026-10-08T12:00:00Z) or a duration ago (10m, 2h, 1d), not ${JSON.stringify(e)}`);return new Date(e).toISOString()}var Lo={name:"databases:list",summary:"the environment's databases: size, state, how full",scope:"environment",async run(e){let t=await h(e),{databases:n}=await D(e,t,"GET","/databases?limit=100");e.out.result({databases:n},()=>{n.length===0&&e.out.line("No database yet: palbase databases:create makes one."),e.out.table(n,[{header:"name",value:s=>s.name},{header:"size",value:s=>s.size},{header:"state",value:s=>`${s.state}${s.deleted_at?" (deleted)":""}`},{header:"used",value:s=>s.used_bytes!==void 0?`${(s.used_bytes/1e6).toFixed(1)} / ${(s.quota_bytes/1e6).toFixed(0)} MB`:""},{header:"host",value:s=>s.host}])})}},No={name:"databases:get",summary:"a database in full, with how to connect to it",usage:"<name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await D(e,t,"GET",`/databases/${H(e.args[0])}`),s=await e.state.databasePasswords(t.env_ref,n.name);e.out.result({...n,passwords_on_this_computer:{owner:s.owner!==void 0,app:s.app!==void 0}},()=>{e.out.line(`${n.name} \u2014 ${n.size}, ${n.state}${n.state_reason?` (${n.state_reason})`:""}`),e.out.line(` host ${n.host}:${n.port} (pooled ${n.pooled_port})`),e.out.line(` allows ${n.allow.length===0?"anyone with the password":n.allow.join(", ")}`),e.out.line(` backups ${n.backup_days} days, any moment of them (databases:restore --to)`),n.used_bytes!==void 0&&e.out.line(` used ${(n.used_bytes/1e6).toFixed(1)} MB of ${(n.quota_bytes/1e6).toFixed(0)} MB`),n.awake_hours_limit&&e.out.line(` awake ${n.awake_hours??0} of ${n.awake_hours_limit} hours this month`),e.out.line(` passwords ${s.owner||s.app?`this computer holds ${[s.owner&&"owner",s.app&&"app"].filter(Boolean).join(" and ")}`:"none on this computer (databases:password:reset makes new ones)"}`)})}};function qo(e){if(e!==void 0)return String(e).split(",").map(t=>t.trim()).filter(t=>t!=="")}var xo={name:"databases:create",summary:"open a database; its passwords are kept on this computer (the platform shows them once)",usage:"[name] [--size <size>] [--allow <ip/cidr,\u2026>] [--backup-days <n>] [--show-passwords]",args:{min:0,max:1},flags:{size:{type:"string",value:"size",description:"its size (palbase databases:sizes lists what is open)"},allow:{type:"string",value:"list",description:"who may connect, as addresses or CIDRs, comma-separated (default: anyone with the password)"},"backup-days":{type:"string",value:"n",description:"how many days of backups to keep, 1 to 35"},"show-passwords":{type:"boolean",description:"print the passwords too (they are kept on this computer either way)"}},scope:"environment",async run(e){let t=await h(e),n={};e.args[0]&&(n.name=e.args[0]),e.flags.size!==void 0&&(n.size=e.flags.size);let s=qo(e.flags.allow);s&&(n.allow=s);let r=gt(e.flags["backup-days"],"backup-days");r!==void 0&&(n.backup_days=r),await b(e,t,`open a database ${e.args[0]??"main"}${n.size?` (${n.size})`:""}`);let a=await D(e,t,"POST","/databases",n);await e.state.keepDatabasePasswords(t.env_ref,a.name,{owner:a.credentials.owner.password,app:a.credentials.app.password});let i=e.flags["show-passwords"]===!0,{credentials:o,...l}=a;e.out.result(i?a:{...l,credentials:{owner:mr(o.owner),app:mr(o.app)}},()=>{e.out.line(`Opened the database ${a.name} (${a.size}) at ${a.host}.`),e.out.line("Its passwords are kept on this computer: palbase db:url and db:shell use them."),i&&(e.out.line(`owner: ${a.credentials.owner.password??""}`),e.out.line(`app: ${a.credentials.app.password??""}`))})}},cr="[YOUR-PASSWORD]";function mr(e){let{password:t,...n}=e,s={};for(let[r,a]of Object.entries(n))s[r]=typeof a=="string"?Ic(a,t):a;return s}function Ic(e,t){let n=e.replace(/(postgres(?:ql)?:\/\/[^:/@\s'"]+:)[^@\s'"]*(@)/gi,`$1${cr}$2`).replace(/([?&;]password=)[^&;\s'"]*/gi,`$1${cr}`);if(t)for(let s of new Set([t,encodeURIComponent(t),new URLSearchParams({p:t}).toString().slice(2)]))n=n.split(s).join(cr);return n}var Mo={name:"databases:update",summary:"change a database's size, who may connect to it, or how long its backups are kept",usage:'<name> [--size <size>] [--allow <list>|""] [--backup-days <n>] [--wait]',details:["A new size is taken at once and the database moves to it serving on (palbase databases:sizes lists them): its sessions on the direct port end once, when its primary moves, and new logins wait a few seconds then."],args:{min:1,max:1},flags:{size:{type:"string",value:"size",description:"the size to move it to"},allow:{type:"string",value:"list",description:'who may connect (comma-separated addresses or CIDRs); "" for anyone with the password'},"backup-days":{type:"string",value:"n",description:"how many days of backups to keep, 1 to 35"},wait:{type:"boolean",description:"with --size: follow the move until it serves at the new size"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s={},r=e.flags.size;if(r!==void 0){if(r==="")throw g("--size is one of palbase databases:sizes");s.size=r}let a=qo(e.flags.allow);a&&(s.allow=a);let i=gt(e.flags["backup-days"],"backup-days");if(i!==void 0&&(s.backup_days=i),Object.keys(s).length===0)throw g("nothing to change: --size, --allow or --backup-days");await b(e,t,`change the database ${n}: ${Object.keys(s).join(", ")}`);let o=await D(e,t,"PATCH",`/databases/${H(n)}`,s);r!==void 0&&e.flags.wait===!0&&(o=await Lc(e,t,n,r)),e.out.result(o,()=>{r!==void 0&&e.out.line(o.state==="resizing"?`${o.name} is moving to ${r}, serving on: its sessions on the direct port end once, when its primary moves (palbase databases:get ${o.name} follows it).`:`${o.name} serves at ${o.size} (${o.state}).`),(a||i!==void 0)&&e.out.line(`${o.name}: allows ${o.allow.length===0?"anyone with the password":o.allow.join(", ")}; backups ${o.backup_days} days.`)})}};async function Lc(e,t,n,s){e.out.note(`Following ${n}'s move to ${s}\u2026`);let r="";for(let a=0;a<900;a++){let i=await D(e,t,"GET",`/databases/${H(n)}`),o=`${i.state}${i.state_reason?` \u2014 ${i.state_reason}`:""}`;if(o!==r&&e.out.note(` ${o}`),r=o,i.size===s&&i.state!=="resizing"&&i.state!=="starting"){if(i.state==="failed")throw new m("resize_failed",`${n} did not come up at ${s}${i.state_reason?`: ${i.state_reason}`:""}`,{hint:`palbase databases:get ${n}`});return i}if(i.size!==s)throw new m("resize_changed",`${n} is asked for ${i.size} now, not ${s}: another change came`,{hint:`palbase databases:get ${n}`});await e.io.sleep(2e3)}throw new m("timeout",`${n} was still moving to ${s} after 30 minutes; it serves on meanwhile`,{hint:`palbase databases:get ${n}`})}var Uo={name:"databases:password:reset",summary:"make a database role's password new; it is kept on this computer, the old one stops working",usage:"<name> --role owner|app [--show-password]",details:["The app role's new password reaches the machines given the database as they roll out with it; they keep serving."],args:{min:1,max:1},flags:{role:{type:"string",value:"role",description:"owner (migrations, the console) or app (what the machines use)"},"show-password":{type:"boolean",description:"print it too"}},scope:"environment",async run(e){let t=e.flags.role;if(t!=="owner"&&t!=="app")throw g("--role is owner or app");let n=await h(e);await b(e,n,`make the ${t} password of ${e.args[0]} new`);let r=(await D(e,n,"POST",`/databases/${H(e.args[0])}/password`,{role:t}))[t];if(!r?.password)throw new m("no_password","the platform answered no password");await e.state.keepDatabasePasswords(n.env_ref,e.args[0],{[t]:r.password}),await Nc(e,n,e.args[0]),e.out.result(e.flags["show-password"]===!0?r:mr(r),()=>{e.out.line(`The ${t} password of ${e.args[0]} is new and kept on this computer.`),e.flags["show-password"]===!0&&e.out.line(r.password??"")})}};async function Nc(e,t,n){for(let s=0;s<60;s++){if((await D(e,t,"GET",`/databases/${H(n)}`)).password_changing!==!0)return;s===0&&e.out.note(`Waiting for ${n} to take the new password\u2026`),await e.io.sleep(1e3)}throw new m("timeout",`${n} had not taken the new password after a minute; it is kept here and works once it has`,{hint:`palbase databases:get ${n}`})}var Ko={name:"databases:restore",summary:"bring a database back to a moment of its backups",usage:"<name> --to <time>",args:{min:1,max:1},flags:{to:{type:"string",value:"time",description:"the moment, as an RFC 3339 time (2026-10-08T12:00:00Z)"}},scope:"environment",async run(e){let t=e.flags.to;if(!t||Number.isNaN(Date.parse(t)))throw g("--to is the moment to go back to, as an RFC 3339 time");let n=await h(e);await b(e,n,`bring ${e.args[0]} back to ${t} (what was written since goes)`);let s=await D(e,n,"POST",`/databases/${H(e.args[0])}/restore`,{to:new Date(t).toISOString()});e.out.result(s,()=>e.out.line(`${s.name} is being brought back to ${t} \u2014 ${s.state}.`))}};function gr(e,t,n,s,r,a,i=!1){return{name:e,summary:t,usage:"<name>",args:{min:1,max:1},scope:"environment",async run(o){let l=await h(o),p=o.args[0];if(n==="DELETE"||s==="/purge"){if(!o.global.yes){if(!o.interactive)throw new m("confirmation_needed",`${r(p)} is confirmed`,{hint:"confirm with -y"});if((await o.io.prompt("Type the database's name to go ahead: ")).trim()!==p)throw new m("cancelled","nothing was changed")}}else await b(o,l,r(p));await D(o,l,n,`/databases/${H(p)}${s}`),i&&await o.state.forgetDatabase(l.env_ref,p),o.out.result({database:p,done:e},()=>o.out.line(a(p)))}}}var Go=gr("databases:delete","delete a database (the platform keeps it 7 days to take back)","DELETE","",e=>`deleting the database ${e}`,e=>`Deleted ${e}; databases:undelete takes it back within 7 days.`),Bo=gr("databases:undelete","take back a database deleted in the last 7 days","POST","/undelete",e=>`take back the database ${e}`,e=>`${e} is back.`),Jo=gr("databases:purge","delete a deleted database for good, now","POST","/purge",e=>`purging the database ${e} for good`,e=>`${e} is purged.`,!0),zo={name:"github:connect",summary:"link the environment to GitHub: Palbase Cloud's GitHub app on the account whose repositories machines are built from",usage:"[--device | --github-token-stdin] [--installation <account|id>] [--all-envs]",details:["Opens GitHub; back from it, the page shows the account and you link it by writing the environment's ref.","--device: no browser here \u2014 GitHub's device flow (a code you enter at github.com/login/device on any device); the GitHub token stays in memory.","--all-envs links every environment of the project the same way."],flags:{device:{type:"boolean",description:"GitHub's device flow, for a computer with no browser (a server, an agent)"},"github-token-stdin":{type:"boolean",description:"a GitHub user's token from stdin (CI): the installations it reaches, no browser and no code"},installation:{type:"string",value:"account|id",description:"which installation to link, when the GitHub user reaches more than one (--device)"},"all-envs":{type:"boolean",description:"link every environment of the project"}},scope:"environment",async run(e){let t=await h(e),n=e.flags["all-envs"]===!0?(await Fe(e)).map(ue):[t],s=await D(e,t,"POST","/machines/github/connect"),r=e.flags["github-token-stdin"]===!0;if(!r&&e.flags.device!==!0&&e.interactive&&n.length===1){let l=await e.io.openBrowser(s.url);e.out.result({url:s.url,expires_at:s.expires_at,ref:t.env_ref},()=>e.out.line(l?`Opened GitHub. When it asks, link the environment ${t.env_ref}.`:`Open ${s.url} (good until ${s.expires_at}) and link the environment ${t.env_ref}.`));return}let a=r?await fe(e,"a GitHub user's token"):await qc(e,s.client_id),i=await xc(e,a),o=[];for(let l of n){await b(e,l,`link the GitHub account ${i.account}`);let p=await D(e,l,"POST","/machines/github/installations",{installation_id:i.id,user_token:a});o.push({ref:l.env_ref,account:p.account})}e.out.result({linked:o},()=>{for(let l of o)e.out.line(`Linked ${l.account} to ${l.ref}.`)})}};async function qc(e,t){let n=o=>({method:"POST",headers:{accept:"application/json","content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams(o)}),s=await e.io.fetch("https://github.com/login/device/code",n({client_id:t}));if(!s.ok)throw new m("github_unavailable",`GitHub did not start the sign-in: ${s.status}`);let r=await s.json();e.out.note(""),e.out.note(` Enter this code at ${r.verification_uri}: ${r.user_code}`),e.out.note("");let a=Math.max(1,r.interval??5),i=e.io.now()+(r.expires_in??900)*1e3;for(;e.io.now()<i;){await e.io.sleep(a*1e3);let o;try{o=await e.io.fetch("https://github.com/login/oauth/access_token",n({client_id:t,device_code:r.device_code,grant_type:"urn:ietf:params:oauth:grant-type:device_code"}))}catch{continue}if(o.status>=500||o.status===429)continue;let l=await o.json().catch(()=>({}));if(l.access_token)return l.access_token;if(l.error!=="authorization_pending"){if(l.error==="slow_down"){a+=5;continue}if(l.error==="expired_token")break;throw l.error==="access_denied"?new m("access_denied","the sign-in was denied on GitHub"):new m("github_denied",`GitHub's sign-in ended: ${l.error??o.status}`)}}throw new m("expired_code","GitHub's code expired before it was entered",{hint:"run the command again"})}async function xc(e,t){let n=await e.io.fetch("https://api.github.com/user/installations?per_page=100",{headers:{authorization:`Bearer ${t}`,accept:"application/vnd.github+json","x-github-api-version":"2022-11-28"}});if(!n.ok)throw new m("github_unavailable",`GitHub did not list your installations: ${n.status}`);let s=(await n.json()).installations.map(a=>({id:a.id,account:a.account.login}));if(s.length===0)throw new m("no_installation","Palbase Cloud is installed on no GitHub account you reach",{hint:"install it (palbase github:connect, in a browser) or have the account's owner install it"});let r=e.flags.installation;if(r!==void 0){let a=s.find(i=>String(i.id)===r||i.account.toLowerCase()===r.toLowerCase());if(!a)throw new m("no_such_installation",`you reach no installation ${JSON.stringify(r)}: ${s.map(i=>i.account).join(", ")}`);return a}if(s.length===1)return s[0];if(!e.interactive)throw g("which installation? --installation <account>",`yours: ${s.map(a=>a.account).join(", ")}`);for(s.forEach((a,i)=>e.out.note(` ${i+1}) ${a.account}`));;){let a=Number((await e.io.prompt(`Which account? [1-${s.length}] `)).trim());if(Number.isInteger(a)&&a>=1&&a<=s.length)return s[a-1]}}var Wo={name:"github:list",summary:"the GitHub accounts linked to the environment, and who linked each",scope:"environment",async run(e){let t=await h(e),{installations:n}=await D(e,t,"GET","/machines/github/installations");e.out.result({installations:n},()=>{n.length===0&&e.out.line("No GitHub account is linked: palbase github:connect links one."),e.out.table(n,[{header:"account",value:s=>s.account},{header:"installation",value:s=>String(s.installation_id)},{header:"linked by",value:s=>s.linked_by}])})}},Fo={name:"github:repos",summary:"the repositories machines of the environment may be built from",scope:"environment",async run(e){let t=await h(e),{repositories:n}=await D(e,t,"GET","/machines/github/repositories");e.out.result({repositories:n},()=>e.out.table(n,[{header:"repository",value:s=>s.repository},{header:"default branch",value:s=>s.default_branch},{header:"",value:s=>s.private?"private":""}]))}},Vo={name:"github:disconnect",summary:"unlink a GitHub account from the environment; its repositories are no longer built",usage:"<account|installation-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),{installations:n}=await D(e,t,"GET","/machines/github/installations"),s=n.find(r=>String(r.installation_id)===e.args[0]||r.account.toLowerCase()===e.args[0].toLowerCase());if(!s)throw new m("no_such_installation",`no linked GitHub account ${JSON.stringify(e.args[0])}`);await b(e,t,`unlink the GitHub account ${s.account}`),await D(e,t,"DELETE",`/machines/github/installations/${s.installation_id}`),e.out.result({unlinked:s},()=>e.out.line(`Unlinked ${s.account}.`))}};import{createHash as Yg}from"node:crypto";import{lstat as Zg,mkdir as Qg,mkdtemp as eh,readdir as Tr,readFile as Er,rm as dp,rmdir as th,writeFile as nh}from"node:fs/promises";import{tmpdir as sh}from"node:os";import{dirname as rh,join as Oe,relative as ds,resolve as op,sep as ps}from"node:path";function Kc(e){return e.replace(/\s+/g," ").trim()}var cS=String.raw`
35
35
  import Foundation
36
36
 
37
37
  /// Any JSON value: what a contract leaves open.
@@ -521,11 +521,11 @@ public typealias PalbaseInstant = @Serializable(with = PalbaseInstantSerializer:
521
521
 
522
522
  /** Any JSON value: what a contract leaves open. */
523
523
  public typealias JSONValue = JsonElement
524
- `;var xe=class{spent=0;charge(t,n,s,r){if(n>64)throw new Error(`${t}: a schema nests more than 64 deep, which no contract does: refused`);if(this.spent+=1+4*n+s+r,this.spent>1e6)throw new Error(`${t}: the contract's schemas, every $ref followed, are more to write than 1000000 (palbe-budget.ts says what that counts), which no contract comes near: refused`)}};var Bc="studio.palbase.generated",Ke=class extends Error{kind;constructor(t,n){super(n),this.kind=t,this.name=`CodegenError.${t}`}},Jc=()=>new Ke("InvalidRoot","OpenAPI root must be an object"),zc=()=>new Ke("MissingPaths","OpenAPI has no object-valued `paths`"),hr=e=>new Ke("InvalidRoles",`Roles artifact is not valid JSON: ${e}`),ll=e=>new Ke("SchemaLoss",`Schema cannot be generated without loss: ${e}`);function U(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Lt(e){return e===null||typeof e=="string"||typeof e=="number"||typeof e=="boolean"}function I(e,t){return Object.hasOwn(e,t)?e[t]:void 0}function ke(e,t){let n=I(e,t);return typeof n=="string"?n:void 0}function cn(e){return typeof e=="boolean"?e:typeof e!="string"?null:Zo(e,"true")?!0:Zo(e,"false")?!1:null}function Zo(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++){let s=e.charAt(n),r=t.charAt(n);if(s!==r&&s.toUpperCase()!==r.toUpperCase()&&s.toLowerCase()!==r.toLowerCase())return!1}return!0}function Wc(e){return typeof e=="number"?e:typeof e!="string"?null:Vc(e)}var Fc=/^[\x00-\x20]*([+-]?)(?:(NaN)|(Infinity)|(?:([0-9]+\.?[0-9]*(?:[eE][+-]?[0-9]+)?|\.[0-9]+(?:[eE][+-]?[0-9]+)?)|0[xX]([0-9a-fA-F]+\.?|[0-9a-fA-F]*\.[0-9a-fA-F]+)[pP]([+-]?[0-9]+))[fFdD]?)[\x00-\x20]*$/;function Vc(e){let t=Fc.exec(e);if(!t)return null;let n=t[1]==="-",s;if(t[2]!==void 0)return NaN;if(t[3]!==void 0)s=1/0;else if(t[4]!==void 0)s=Number(t[4]);else{let[r,a=""]=t[5].split("."),i=BigInt(`0x${r}${a}`);s=i===0n?0:Hc(i,Number(t[6])-4*a.length)}return n?-s:s}function Hc(e,t){let n=e.toString(2).length-1+t;if(n>1023)return 1/0;if(n<-1076)return 0;let s=Math.max(n-52,-1074),r=s-t;if(r<=0)return Number(e)*2**t;let a=e>>BigInt(r),i=e-(a<<BigInt(r)),o=1n<<BigInt(r-1);return(i>o||i===o&&(a&1n)===1n)&&(a+=1n),Number(a)*2**s}function Nt(e,t){return e<t?-1:e>t?1:0}function yt(e,t){return[...e].sort((n,s)=>Nt(t(n),t(s)))}function Xc(e,t=new xe){if(!U(e))throw Jc();let n=I(e,"paths");if(!U(n))throw zc();let s=Object.entries(n).flatMap(([r,a])=>U(a)?Object.entries(a).flatMap(([i,o])=>{if(!U(o))return[];let l=ke(o,"operationId");if(l===void 0||l==="")return[];let d={where:`operation ${Se(l)}`,budget:t,through:l.length};return[{operationId:l,method:i.toUpperCase(),path:r,pathParameters:rm(r),input:tm(o,d),output:nm(o,d),headers:el(o,"header",d),query:el(o,"query",d),errors:Yc(o,d),upload:Qc(o),sse:Object.hasOwn(o,"x-palbase-sse"),idempotent:sm(o)}]}):[]);return yt(s,r=>r.operationId)}function Yc(e,t){let n=I(e,"x-palbase-errors");if(!U(n))return[];let s=I(e,"responses"),r=Object.entries(n).flatMap(([a,i])=>{if(!U(i))return[];let o=I(i,"status"),l=(Lt(o)?Wc(o):null)??0,d=ke(i,"code")??"";if(l===0||Number.isNaN(l)||d==="")return[];let u=Zc(l),p=I(i,"hasData"),f=(Lt(p)?cn(p):null)??!1;return[{name:a,code:d,status:u,description:ke(i,"description")??"",data:f?em(U(s)?s:void 0,u,d,{...t,through:t.through+a.length}):void 0}]});return yt(r,a=>a.name)}function Zc(e){return Number.isNaN(e)?0:e>=2147483647?2147483647:e<=-2147483648?-2147483648:Math.trunc(e)+0}function Qc(e){let t=I(e,"x-palbase-upload");if(!U(t))return;let n=ke(t,"bucket")??"",s=ke(t,"pathTemplate")??"";return n===""||s===""?void 0:{bucket:n,pathTemplate:s}}function em(e,t,n,s){let r=e===void 0?void 0:I(e,String(t));if(!U(r))return;let a=pl(I(r,"content"));if(a===void 0)return;let i=I(a,"oneOf");if(Array.isArray(i)){let o=i.filter(U).find(l=>{let d=I(l,"properties"),u=U(d)?I(d,"error"):void 0;return U(u)&&ke(u,"const")===n});return o===void 0?void 0:Qo(o,s)}return Qo(a,s)}function Qo(e,t){let n=I(e,"properties");if(!U(n))return;let s=I(n,"data");return U(s)?ft(s,t):void 0}function tm(e,t){let n=I(e,"requestBody");return U(n)?dl(I(n,"content"),t):void 0}function nm(e,t){let n=I(e,"responses");if(!U(n))return;let s=["200","201",...Object.keys(n).filter(r=>r.startsWith("2")&&r!=="200"&&r!=="201").sort(Nt)];for(let r of s){let a=I(n,r),i=U(a)?dl(I(a,"content"),t):void 0;if(i!==void 0)return i}}function dl(e,t){let n=pl(e);if(!(n===void 0||Object.hasOwn(n,"$ref")))return ft(n,t)}function pl(e){if(!U(e))return;let t=I(e,"application/json");if(!U(t))return;let n=I(t,"schema");return U(n)?n:void 0}function ul(e){return e.toLowerCase()==="idempotency-key"}function sm(e){let t=I(e,"parameters");return Array.isArray(t)&&t.some(n=>U(n)&&ke(n,"in")==="header"&&ul(ke(n,"name")??""))}function el(e,t,n){let s=I(e,"parameters");if(!Array.isArray(s))return;let r=s.flatMap(a=>{if(!U(a))return[];if(ke(a,"in")!==t)return[];let i=ke(a,"name");if(i===void 0||i==="")return[];if(t==="header"&&ul(i))return[];let o=I(a,"schema"),l=U(o)?ft(o,{...n,through:n.through+i.length}):{kind:"string",nullable:!1},d=I(a,"required"),u=(Lt(d)?cn(d):null)??!1;return[{name:i,schema:l,required:u}]});return r.length===0?void 0:{kind:"object",properties:yt(r,a=>a.name),nullable:!1}}function rm(e){return[...e.matchAll(/\{([^{}]+)\}/g)].map(t=>t[1]).filter(t=>t!=="")}function tl(e){let t=I(e,"type");return Array.isArray(t)?t.filter(n=>typeof n=="string"):void 0}function ft(e,t,n=0,s=t.through){let r=I(e,"properties"),a=I(e,"enum");t.budget.charge(t.where,n,s,nl(U(r)?Object.keys(r):[])+nl(Array.isArray(a)?a:[]));let i=I(e,"nullable"),o=(Lt(i)?cn(i):null)??!1;tl(e)?.includes("null")&&(o=!0);let l=I(e,"enum");if(Array.isArray(l)){let p=l.filter(y=>typeof y=="string"),f=l.filter(y=>y===null).length;if(p.length>0&&p.length+f===l.length)return{kind:"enum",values:p,nullable:o||f>0}}let d=I(e,"type"),u=typeof d=="string"?d:"";if(u==="")for(let p of tl(e)??[])p==="null"?o=!0:u===""&&(u=p);switch(u){case"string":case"number":case"integer":case"boolean":return{kind:u,nullable:o};case"array":{let p=I(e,"items");return{kind:"array",element:U(p)?ft(p,t,n+1,s):{kind:"any",nullable:!1},nullable:o}}case"object":return sl(e,o,t,n,s);default:return U(I(e,"properties"))||Object.hasOwn(e,"additionalProperties")?sl(e,o,t,n,s):{kind:"any",nullable:o}}}function nl(e){return e.reduce((t,n)=>t+(typeof n=="string"?n.length:0),0)}function sl(e,t,n,s,r){let a=I(e,"properties"),i=U(a)?a:{},o=Object.keys(i),l=I(e,"additionalProperties"),d=U(l)&&Object.keys(l).length>0?l:void 0;if(o.length===0)return Lt(l)&&cn(l)===!1?{kind:"object",properties:[],nullable:t}:d!==void 0?{kind:"map",values:ft(d,n,s+1,r),nullable:t}:{kind:"map",values:{kind:"any",nullable:!1},nullable:t};if(d!==void 0)throw ll(`an object with declared properties (${[...o].sort(Nt).join(", ")}) also declares a typed additionalProperties`);let u=I(e,"required"),p=new Set(Array.isArray(u)?u.filter(y=>typeof y=="string"):[]);return{kind:"object",properties:[...o].sort(Nt).map(y=>{let v=i[y];return{name:y,schema:U(v)?ft(v,n,s+1,r+y.length):{kind:"any",nullable:!1},required:p.has(y)}}),nullable:t}}function am(e,t=new xe){if(!U(e))return[];let n=I(e,"x-palbase-room");if(!U(n))return[];let s=Object.entries(n).flatMap(([r,a])=>U(a)?[{pattern:r,params:im(r),events:rl(I(a,"events"),e,{where:`room ${Se(r)}`,budget:t,through:r.length}),messages:rl(I(a,"messages"),e,{where:`room ${Se(r)}`,budget:t,through:r.length})}]:[]);return yt(s,r=>r.pattern)}function im(e){return e.split(":").filter(t=>t.length>2&&t.startsWith("{")&&t.endsWith("}")).map(t=>t.slice(1,-1))}function rl(e,t,n){if(!U(e))return[];let s=Object.entries(e).flatMap(([r,a])=>{if(!U(a))return[];let i=om(a,t)??a;return[{name:r,schema:ft(i,{...n,through:n.through+r.length})}]});return yt(s,r=>r.name)}function om(e,t){let n=ke(e,"$ref");if(n===void 0||!n.startsWith("#/"))return;let s=t;for(let r of n.slice(2).split("/")){let a=r.replaceAll("~1","/").replaceAll("~0","~");if(!U(s)||!Object.hasOwn(s,a))return;s=s[a]}return U(s)?s:void 0}var lm=/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;function dm(e,t){if(!lm.test(t)||t.split(".").some(n=>Sl.has(n)))throw new Error(`Invalid generated package name: ${t}`);if(!/^[a-z][A-Za-z0-9]*$/.test(e.client))throw new Error(`client ${JSON.stringify(e.client)} is not a lowerCamel name`);for(let n of e.header)if(cl.test(n))throw new Error(`header line ${JSON.stringify(n)} breaks the comment`)}var cl=/[\x00-\x08\x0a-\x1f\x7f\x85\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/;function ml(e,t){let n=t.package??Bc;dm(t,n);let s=new xe,r=pm(n,t,Xc(e,s),am(e,s)),a=Pm(e);return a===void 0?r:r+Rm(a)}function pm(e,t,n,s){let r=n.some(d=>d.sse&&d.output!==void 0),a=s.some(d=>d.messages.length===0),i=n.some(d=>d.upload!==void 0),o=[];for(let d of t.header)o.push(d===""?"//":`// ${d}`);o.push("@file:OptIn(PalbaseGeneratedApi::class)"),o.push(""),o.push(`package ${e}`),o.push(""),o.push("import studio.palbase.PalbaseClient"),o.push("import studio.palbase.backend.BackendError"),o.push("import studio.palbase.backend.GeneratedRequest"),o.push("import studio.palbase.backend.PBBackend"),o.push("import studio.palbase.backend.PalBackendClient"),o.push("import studio.palbase.backend.PalbaseGeneratedApi"),o.push("import studio.palbase.backend.PathPart"),i&&o.push("import studio.palbase.backend.UploadProgress"),s.length>0&&o.push("import studio.palbase.backend.palbaseGeneratedJson"),o.push("import studio.palbase.backend.palbe"),i&&o.push("import studio.palbase.backend.uploadGenerated"),s.length>0&&o.push("import studio.palbase.realtime.PBRoom"),a&&o.push("import studio.palbase.realtime.PBRoomError"),r&&o.push("import kotlinx.coroutines.flow.Flow"),o.push("import kotlinx.serialization.SerialName"),o.push("import kotlinx.serialization.Serializable"),o.push("import kotlinx.serialization.builtins.ListSerializer"),o.push("import kotlinx.serialization.builtins.MapSerializer"),o.push("import kotlinx.serialization.builtins.nullable"),o.push("import kotlinx.serialization.builtins.serializer"),s.length>0&&(o.push("import kotlinx.serialization.json.JsonObject"),o.push("import kotlinx.serialization.json.jsonObject")),o.push(""),o.push("// Request / response types"),o.push(""),n.forEach((d,u)=>{cm(o,d),u!==n.length-1&&o.push("")}),n.length>0&&o.push(""),o.push(`// Namespaced calls (pb.${t.client}.*)`),o.push("");let l=um(o,t);if(gm(o,n,l),s.length>0){o.push(""),o.push("// Rooms (pb.join)");for(let d of s)o.push(""),bm(o,d)}return Am(o.join(`
524
+ `;var Ue=class{spent=0;charge(t,n,s,r){if(n>64)throw new Error(`${t}: a schema nests more than 64 deep, which no contract does: refused`);if(this.spent+=1+4*n+s+r,this.spent>1e6)throw new Error(`${t}: the contract's schemas, every $ref followed, are more to write than 1000000 (palbe-budget.ts says what that counts), which no contract comes near: refused`)}};var Bc="studio.palbase.generated",Ke=class extends Error{kind;constructor(t,n){super(n),this.kind=t,this.name=`CodegenError.${t}`}},Jc=()=>new Ke("InvalidRoot","OpenAPI root must be an object"),zc=()=>new Ke("MissingPaths","OpenAPI has no object-valued `paths`"),hr=e=>new Ke("InvalidRoles",`Roles artifact is not valid JSON: ${e}`),ll=e=>new Ke("SchemaLoss",`Schema cannot be generated without loss: ${e}`);function M(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Lt(e){return e===null||typeof e=="string"||typeof e=="number"||typeof e=="boolean"}function I(e,t){return Object.hasOwn(e,t)?e[t]:void 0}function ke(e,t){let n=I(e,t);return typeof n=="string"?n:void 0}function cn(e){return typeof e=="boolean"?e:typeof e!="string"?null:Zo(e,"true")?!0:Zo(e,"false")?!1:null}function Zo(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++){let s=e.charAt(n),r=t.charAt(n);if(s!==r&&s.toUpperCase()!==r.toUpperCase()&&s.toLowerCase()!==r.toLowerCase())return!1}return!0}function Wc(e){return typeof e=="number"?e:typeof e!="string"?null:Vc(e)}var Fc=/^[\x00-\x20]*([+-]?)(?:(NaN)|(Infinity)|(?:([0-9]+\.?[0-9]*(?:[eE][+-]?[0-9]+)?|\.[0-9]+(?:[eE][+-]?[0-9]+)?)|0[xX]([0-9a-fA-F]+\.?|[0-9a-fA-F]*\.[0-9a-fA-F]+)[pP]([+-]?[0-9]+))[fFdD]?)[\x00-\x20]*$/;function Vc(e){let t=Fc.exec(e);if(!t)return null;let n=t[1]==="-",s;if(t[2]!==void 0)return NaN;if(t[3]!==void 0)s=1/0;else if(t[4]!==void 0)s=Number(t[4]);else{let[r,a=""]=t[5].split("."),i=BigInt(`0x${r}${a}`);s=i===0n?0:Hc(i,Number(t[6])-4*a.length)}return n?-s:s}function Hc(e,t){let n=e.toString(2).length-1+t;if(n>1023)return 1/0;if(n<-1076)return 0;let s=Math.max(n-52,-1074),r=s-t;if(r<=0)return Number(e)*2**t;let a=e>>BigInt(r),i=e-(a<<BigInt(r)),o=1n<<BigInt(r-1);return(i>o||i===o&&(a&1n)===1n)&&(a+=1n),Number(a)*2**s}function Nt(e,t){return e<t?-1:e>t?1:0}function yt(e,t){return[...e].sort((n,s)=>Nt(t(n),t(s)))}function Xc(e,t=new Ue){if(!M(e))throw Jc();let n=I(e,"paths");if(!M(n))throw zc();let s=Object.entries(n).flatMap(([r,a])=>M(a)?Object.entries(a).flatMap(([i,o])=>{if(!M(o))return[];let l=ke(o,"operationId");if(l===void 0||l==="")return[];let p={where:`operation ${Se(l)}`,budget:t,through:l.length};return[{operationId:l,method:i.toUpperCase(),path:r,pathParameters:rm(r),input:tm(o,p),output:nm(o,p),headers:el(o,"header",p),query:el(o,"query",p),errors:Yc(o,p),upload:Qc(o),sse:Object.hasOwn(o,"x-palbase-sse"),idempotent:sm(o)}]}):[]);return yt(s,r=>r.operationId)}function Yc(e,t){let n=I(e,"x-palbase-errors");if(!M(n))return[];let s=I(e,"responses"),r=Object.entries(n).flatMap(([a,i])=>{if(!M(i))return[];let o=I(i,"status"),l=(Lt(o)?Wc(o):null)??0,p=ke(i,"code")??"";if(l===0||Number.isNaN(l)||p==="")return[];let u=Zc(l),d=I(i,"hasData"),f=(Lt(d)?cn(d):null)??!1;return[{name:a,code:p,status:u,description:ke(i,"description")??"",data:f?em(M(s)?s:void 0,u,p,{...t,through:t.through+a.length}):void 0}]});return yt(r,a=>a.name)}function Zc(e){return Number.isNaN(e)?0:e>=2147483647?2147483647:e<=-2147483648?-2147483648:Math.trunc(e)+0}function Qc(e){let t=I(e,"x-palbase-upload");if(!M(t))return;let n=ke(t,"bucket")??"",s=ke(t,"pathTemplate")??"";return n===""||s===""?void 0:{bucket:n,pathTemplate:s}}function em(e,t,n,s){let r=e===void 0?void 0:I(e,String(t));if(!M(r))return;let a=dl(I(r,"content"));if(a===void 0)return;let i=I(a,"oneOf");if(Array.isArray(i)){let o=i.filter(M).find(l=>{let p=I(l,"properties"),u=M(p)?I(p,"error"):void 0;return M(u)&&ke(u,"const")===n});return o===void 0?void 0:Qo(o,s)}return Qo(a,s)}function Qo(e,t){let n=I(e,"properties");if(!M(n))return;let s=I(n,"data");return M(s)?ft(s,t):void 0}function tm(e,t){let n=I(e,"requestBody");return M(n)?pl(I(n,"content"),t):void 0}function nm(e,t){let n=I(e,"responses");if(!M(n))return;let s=["200","201",...Object.keys(n).filter(r=>r.startsWith("2")&&r!=="200"&&r!=="201").sort(Nt)];for(let r of s){let a=I(n,r),i=M(a)?pl(I(a,"content"),t):void 0;if(i!==void 0)return i}}function pl(e,t){let n=dl(e);if(!(n===void 0||Object.hasOwn(n,"$ref")))return ft(n,t)}function dl(e){if(!M(e))return;let t=I(e,"application/json");if(!M(t))return;let n=I(t,"schema");return M(n)?n:void 0}function ul(e){return e.toLowerCase()==="idempotency-key"}function sm(e){let t=I(e,"parameters");return Array.isArray(t)&&t.some(n=>M(n)&&ke(n,"in")==="header"&&ul(ke(n,"name")??""))}function el(e,t,n){let s=I(e,"parameters");if(!Array.isArray(s))return;let r=s.flatMap(a=>{if(!M(a))return[];if(ke(a,"in")!==t)return[];let i=ke(a,"name");if(i===void 0||i==="")return[];if(t==="header"&&ul(i))return[];let o=I(a,"schema"),l=M(o)?ft(o,{...n,through:n.through+i.length}):{kind:"string",nullable:!1},p=I(a,"required"),u=(Lt(p)?cn(p):null)??!1;return[{name:i,schema:l,required:u}]});return r.length===0?void 0:{kind:"object",properties:yt(r,a=>a.name),nullable:!1}}function rm(e){return[...e.matchAll(/\{([^{}]+)\}/g)].map(t=>t[1]).filter(t=>t!=="")}function tl(e){let t=I(e,"type");return Array.isArray(t)?t.filter(n=>typeof n=="string"):void 0}function ft(e,t,n=0,s=t.through){let r=I(e,"properties"),a=I(e,"enum");t.budget.charge(t.where,n,s,nl(M(r)?Object.keys(r):[])+nl(Array.isArray(a)?a:[]));let i=I(e,"nullable"),o=(Lt(i)?cn(i):null)??!1;tl(e)?.includes("null")&&(o=!0);let l=I(e,"enum");if(Array.isArray(l)){let d=l.filter(y=>typeof y=="string"),f=l.filter(y=>y===null).length;if(d.length>0&&d.length+f===l.length)return{kind:"enum",values:d,nullable:o||f>0}}let p=I(e,"type"),u=typeof p=="string"?p:"";if(u==="")for(let d of tl(e)??[])d==="null"?o=!0:u===""&&(u=d);switch(u){case"string":case"number":case"integer":case"boolean":return{kind:u,nullable:o};case"array":{let d=I(e,"items");return{kind:"array",element:M(d)?ft(d,t,n+1,s):{kind:"any",nullable:!1},nullable:o}}case"object":return sl(e,o,t,n,s);default:return M(I(e,"properties"))||Object.hasOwn(e,"additionalProperties")?sl(e,o,t,n,s):{kind:"any",nullable:o}}}function nl(e){return e.reduce((t,n)=>t+(typeof n=="string"?n.length:0),0)}function sl(e,t,n,s,r){let a=I(e,"properties"),i=M(a)?a:{},o=Object.keys(i),l=I(e,"additionalProperties"),p=M(l)&&Object.keys(l).length>0?l:void 0;if(o.length===0)return Lt(l)&&cn(l)===!1?{kind:"object",properties:[],nullable:t}:p!==void 0?{kind:"map",values:ft(p,n,s+1,r),nullable:t}:{kind:"map",values:{kind:"any",nullable:!1},nullable:t};if(p!==void 0)throw ll(`an object with declared properties (${[...o].sort(Nt).join(", ")}) also declares a typed additionalProperties`);let u=I(e,"required"),d=new Set(Array.isArray(u)?u.filter(y=>typeof y=="string"):[]);return{kind:"object",properties:[...o].sort(Nt).map(y=>{let v=i[y];return{name:y,schema:M(v)?ft(v,n,s+1,r+y.length):{kind:"any",nullable:!1},required:d.has(y)}}),nullable:t}}function am(e,t=new Ue){if(!M(e))return[];let n=I(e,"x-palbase-room");if(!M(n))return[];let s=Object.entries(n).flatMap(([r,a])=>M(a)?[{pattern:r,params:im(r),events:rl(I(a,"events"),e,{where:`room ${Se(r)}`,budget:t,through:r.length}),messages:rl(I(a,"messages"),e,{where:`room ${Se(r)}`,budget:t,through:r.length})}]:[]);return yt(s,r=>r.pattern)}function im(e){return e.split(":").filter(t=>t.length>2&&t.startsWith("{")&&t.endsWith("}")).map(t=>t.slice(1,-1))}function rl(e,t,n){if(!M(e))return[];let s=Object.entries(e).flatMap(([r,a])=>{if(!M(a))return[];let i=om(a,t)??a;return[{name:r,schema:ft(i,{...n,through:n.through+r.length})}]});return yt(s,r=>r.name)}function om(e,t){let n=ke(e,"$ref");if(n===void 0||!n.startsWith("#/"))return;let s=t;for(let r of n.slice(2).split("/")){let a=r.replaceAll("~1","/").replaceAll("~0","~");if(!M(s)||!Object.hasOwn(s,a))return;s=s[a]}return M(s)?s:void 0}var lm=/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;function pm(e,t){if(!lm.test(t)||t.split(".").some(n=>Sl.has(n)))throw new Error(`Invalid generated package name: ${t}`);if(!/^[a-z][A-Za-z0-9]*$/.test(e.client))throw new Error(`client ${JSON.stringify(e.client)} is not a lowerCamel name`);for(let n of e.header)if(cl.test(n))throw new Error(`header line ${JSON.stringify(n)} breaks the comment`)}var cl=/[\x00-\x08\x0a-\x1f\x7f\x85\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/;function ml(e,t){let n=t.package??Bc;pm(t,n);let s=new Ue,r=dm(n,t,Xc(e,s),am(e,s)),a=Pm(e);return a===void 0?r:r+Rm(a)}function dm(e,t,n,s){let r=n.some(p=>p.sse&&p.output!==void 0),a=s.some(p=>p.messages.length===0),i=n.some(p=>p.upload!==void 0),o=[];for(let p of t.header)o.push(p===""?"//":`// ${p}`);o.push("@file:OptIn(PalbaseGeneratedApi::class)"),o.push(""),o.push(`package ${e}`),o.push(""),o.push("import studio.palbase.PalbaseClient"),o.push("import studio.palbase.backend.BackendError"),o.push("import studio.palbase.backend.GeneratedRequest"),o.push("import studio.palbase.backend.PBBackend"),o.push("import studio.palbase.backend.PalBackendClient"),o.push("import studio.palbase.backend.PalbaseGeneratedApi"),o.push("import studio.palbase.backend.PathPart"),i&&o.push("import studio.palbase.backend.UploadProgress"),s.length>0&&o.push("import studio.palbase.backend.palbaseGeneratedJson"),o.push("import studio.palbase.backend.palbe"),i&&o.push("import studio.palbase.backend.uploadGenerated"),s.length>0&&o.push("import studio.palbase.realtime.PBRoom"),a&&o.push("import studio.palbase.realtime.PBRoomError"),r&&o.push("import kotlinx.coroutines.flow.Flow"),o.push("import kotlinx.serialization.SerialName"),o.push("import kotlinx.serialization.Serializable"),o.push("import kotlinx.serialization.builtins.ListSerializer"),o.push("import kotlinx.serialization.builtins.MapSerializer"),o.push("import kotlinx.serialization.builtins.nullable"),o.push("import kotlinx.serialization.builtins.serializer"),s.length>0&&(o.push("import kotlinx.serialization.json.JsonObject"),o.push("import kotlinx.serialization.json.jsonObject")),o.push(""),o.push("// Request / response types"),o.push(""),n.forEach((p,u)=>{cm(o,p),u!==n.length-1&&o.push("")}),n.length>0&&o.push(""),o.push(`// Namespaced calls (pb.${t.client}.*)`),o.push("");let l=um(o,t);if(gm(o,n,l),s.length>0){o.push(""),o.push("// Rooms (pb.join)");for(let p of s)o.push(""),bm(o,p)}return Am(o.join(`
525
525
  `))+`
526
- `}function um(e,t){let n=`PB${ye(t.client)}Backend`;return e.push(`val PalbaseClient.${_e(t.client)}: ${n}`),e.push(` get() = ${n}(palbe(${te(t.machine)}))`),e.push(""),e.push(`class ${n} internal constructor(override val _pb: PalBackendClient) : PBBackend`),n}function cm(e,t){let n=Bn(t),s=!1;t.input!==void 0&&(It(e,`${n}Request`,t.input),s=!0),t.output!==void 0&&(s&&e.push(""),It(e,`${n}Response`,t.output),s=!0),t.headers!==void 0&&t.upload===void 0&&(s&&e.push(""),It(e,`${n}Headers`,t.headers),s=!0),t.query!==void 0&&(s&&e.push(""),It(e,`${n}Query`,t.query),s=!0),s&&e.push(""),mm(e,t)}function It(e,t,n){switch(n.kind){case"object":gl(e,t,n,0);return;case"array":{let s=[],r=ht(n.element,`${t}Item`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = List<${r}>`);return}case"enum":hl(e,t,n,0);return;case"map":{let s=[],r=ht(n.values,`${t}Value`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = Map<String, ${r}>${n.nullable?"?":""}`);return}default:e.push(`typealias ${t} = ${ht(n,t,[])}`)}}function gl(e,t,n,s){let r=" ".repeat(s),a=[],i=bl(n.properties).map(o=>{let l=ht(o.property.schema,t+ye(o.property.name),a,!o.property.required);return{field:o,type:l,defaultNull:!o.property.required||o.property.schema.nullable}});a.length>0&&(e.push(...a.map(o=>r+o)),e.push("")),e.push(`${r}@Serializable`),e.push(`${r}data class ${t}(`);for(let{field:o,type:l,defaultNull:d}of i)e.push(`${r} @SerialName(${te(o.property.name)})`),e.push(`${r} val ${o.identifier}: ${l}${d?" = null":""},`);e.push(`${r})`)}function ht(e,t,n,s=!1){let r;switch(e.kind){case"string":r="String";break;case"number":r="Double";break;case"integer":r="Int";break;case"boolean":r="Boolean";break;case"any":r="kotlinx.serialization.json.JsonElement";break;case"enum":hl(n,`${t}Value`,e,0),r=`${t}Value`;break;case"object":gl(n,t,e,0),r=t;break;case"array":r=`List<${ht(e.element,`${t}Item`,n)}>`;break;case"map":r=`Map<String, ${ht(e.values,`${t}Value`,n)}>`;break}return(e.nullable||s)&&!r.endsWith("?")?`${r}?`:r}function hl(e,t,n,s){let r=" ".repeat(s);e.push(`${r}@Serializable`),e.push(`${r}enum class ${t}(val wireValue: String) {`);let a=new Map;for(let i of n.values){let o=wl(i),l=(a.get(o)??0)+1;a.set(o,l);let d=l===1?o:`${o}_${l}`;e.push(`${r} @SerialName(${te(i)})`),e.push(`${r} ${d}(${te(i)}),`)}e.push(`${r}}`)}function mm(e,t){let n=`${Bn(t)}Error`;e.push(`sealed class ${n}(message: String, cause: Throwable? = null) : Exception(message, cause) {`);let s=new Map;for(let r of t.errors){if(r.data===void 0)continue;let a=[];s.set(r.name,ht(r.data,`${ye(r.name)}Data`,a)),e.push(...a.map(i=>` ${i}`)),e.push("")}for(let r of t.errors){let a=ye(r.name);r.data===void 0?e.push(` data object ${a} : ${n}(${te(r.code)})`):e.push(` data class ${a}(val data: ${s.get(r.name)}) : ${n}(${te(r.code)})`)}if(e.push(` data class Other(val backend: BackendError) : ${n}("Backend error", backend)`),e.push(""),e.push(" companion object {"),t.errors.length===0)e.push(` fun from(backend: BackendError): ${n} = Other(backend)`);else{e.push(` fun from(backend: BackendError): ${n} {`),e.push(" val response = backend.envelope ?: return Other(backend)"),e.push(" return when {");for(let r of t.errors){let a=`response.status == ${_m(r.status)} && response.code == ${te(r.code)}`;if(r.data===void 0)e.push(` ${a} -> ${ye(r.name)}`);else{let i=Sm(s.get(r.name),"?");e.push(` ${a} ->`),e.push(` response.decodeData(${un(r.data,i)})?.let(::${ye(r.name)}) ?: Other(backend)`)}}e.push(" else -> Other(backend)"),e.push(" }"),e.push(" }")}e.push(" }"),e.push("}")}function al(e){return[...e.children.keys()].sort(Nt).map(t=>e.children.get(t))}function gm(e,t,n){if(t.length===0)return;let s={path:[],children:new Map,operations:[]};for(let o of t){let l=s;for(let d of Gn(o).slice(0,-1)){let u=l.children.get(d);u===void 0&&(u={path:[...l.path,d],children:new Map,operations:[]},l.children.set(d,u)),l=u}l.operations.push(o)}e.push("");let r=al(s);r.forEach((o,l)=>{let d=_e(o.path[o.path.length-1]);e.push(`val ${n}.${d}: ${pn(o.path)}`),e.push(` get() = ${pn(o.path)}(_pb)`),l!==r.length-1&&e.push("")}),e.push("");let a=[...r],i=!0;for(;a.length>0;){let o=a.shift();i||e.push(""),i=!1,e.push(`class ${pn(o.path)} internal constructor(private val client: PalBackendClient) {`);for(let d of al(o)){let u=_e(d.path[d.path.length-1]);e.push(` val ${u}: ${pn(d.path)}`),e.push(` get() = ${pn(d.path)}(client)`),e.push(""),a.push(d)}let l=yt(o.operations,d=>d.operationId);l.forEach((d,u)=>{hm(e,d),u!==l.length-1&&e.push("")}),e.length>0&&e[e.length-1]===""&&e.pop(),e.push("}")}}function hm(e,t){if(t.upload!==void 0){ym(e,t);return}let n=Bn(t),s=t.method===""?"POST":t.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the method ${Se(s)} is not an HTTP method's name`);for(let d of t.headers?.properties??[])if(!vm.test(d.name))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the header ${Se(d.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);let r=[];for(let d of t.pathParameters)r.push(`${_e(d)}: String`);t.input!==void 0&&r.push(`input: ${n}Request`),t.query!==void 0&&r.push(`query: ${n}Query`),t.headers!==void 0&&r.push(`headers: ${n}Headers`);let a=t.sse&&t.output!==void 0,i=a?`: Flow<${n}Response>`:t.output!==void 0?`: ${n}Response`:"",o=a?"streamGenerated":"invokeGenerated",l=Gn(t);e.push(` suspend fun ${_e(l[l.length-1])}(${r.join(", ")})${i} = try {`),e.push(` client.${o}(`),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${te(s)},`),e.push(` path = ${yl(t)},`),t.query!==void 0&&e.push(...fr("query",t.query,"query",4)),t.headers!==void 0&&e.push(...fr("headers",t.headers,"headers",4)),t.input!==void 0&&e.push(" body = input,"),t.idempotent&&!t.sse&&e.push(" idempotent = true,"),e.push(" ),"),e.push(` ${t.input===void 0?"Unit.serializer()":`${n}Request.serializer()`},`),t.output!==void 0&&e.push(` ${un(t.output,`${n}Response`)},`),e.push(" )"),e.push(" } catch (error: BackendError) {"),e.push(` throw ${n}Error.from(error)`),e.push(" }")}var fm=["file","filename","contentType","onProgress"];function ym(e,t){let n=Bn(t);for(let i of t.pathParameters)if(fm.includes(_e(i)))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the path parameter ${Se(i)} is the name of the upload's ${_e(i)}: rename it in the backend`);let s=[];for(let i of t.pathParameters)s.push(`${_e(i)}: String`);t.input!==void 0&&s.push(`input: ${n}Request`),t.query!==void 0&&s.push(`query: ${n}Query`),s.push("file: ByteArray","filename: String","contentType: String? = null","onProgress: ((UploadProgress) -> Unit)? = null");let r=t.output!==void 0?`: ${n}Response`:"",a=Gn(t);e.push(` suspend fun ${_e(a[a.length-1])}(${s.join(", ")})${r} = try {`),e.push(" client.uploadGenerated("),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${te("POST")},`),e.push(` path = ${yl(t)},`),t.query!==void 0&&e.push(...fr("query",t.query,"query",4)),t.input!==void 0&&e.push(" body = input,"),e.push(" ),"),e.push(` ${t.input===void 0?"Unit.serializer()":`${n}Request.serializer()`},`),t.output!==void 0&&e.push(` ${un(t.output,`${n}Response`)},`),e.push(" file = file,"),e.push(" filename = filename,"),e.push(" contentType = contentType,"),e.push(" onProgress = onProgress,"),e.push(" )"),e.push(" } catch (error: BackendError) {"),e.push(` throw ${n}Error.from(error)`),e.push(" }")}function bm(e,t){let n=fl(t),s=t.events.length===0?"Nothing":`${n}RoomEvent`,r=t.messages.length===0?"Nothing":`${n}RoomSend`;for(let i of t.events)It(e,Dt(t,i,"Event"),i.schema),e.push("");if(t.events.length>0){e.push(`sealed interface ${n}RoomEvent {`);for(let i of t.events)e.push(` data class ${ye(i.name)}(val payload: ${Dt(t,i,"Event")}) : ${n}RoomEvent`);e.push("}"),e.push("")}for(let i of t.messages)It(e,Dt(t,i,"Message"),i.schema),e.push("");if(t.messages.length>0){e.push(`sealed interface ${n}RoomSend {`);for(let i of t.messages)e.push(` data class ${ye(i.name)}(val payload: ${Dt(t,i,"Message")}) : ${n}RoomSend`);e.push("}"),e.push("")}let a=t.params.map(i=>` private val ${_e(i)}: String,`);if(a.length===0?e.push(`class ${n}Room : PBRoom<${s}, ${r}> {`):(e.push(`class ${n}Room(`),e.push(...a),e.push(`) : PBRoom<${s}, ${r}> {`)),e.push(` override val pattern: String = ${te(t.pattern)}`),e.push(` override val eventNames: List<String> = listOf(${t.events.map(i=>te(i.name)).join(", ")})`),e.push(` override val topic: String get() = ${wm(t)}`),e.push(""),e.push(` override fun decodeEvent(event: String, payload: JsonObject): ${s}? =`),t.events.length===0)e.push(" null");else{e.push(" when (event) {");for(let i of t.events)e.push(` ${te(i.name)} -> ${n}RoomEvent.${ye(i.name)}(`),e.push(` palbaseGeneratedJson.decodeFromJsonElement(${Dt(t,i,"Event")}.serializer(), payload),`),e.push(" )");e.push(" else -> null"),e.push(" }")}if(e.push(""),e.push(` override fun encodeMessage(message: ${r}): Pair<String, JsonObject> =`),t.messages.length===0)e.push(" throw PBRoomError.NoMessagesDeclared(pattern)");else{e.push(" when (message) {");for(let i of t.messages)e.push(` is ${n}RoomSend.${ye(i.name)} -> ${te(i.name)} to`),e.push(` palbaseGeneratedJson.encodeToJsonElement(${Dt(t,i,"Message")}.serializer(), message.payload).jsonObject`);e.push(" }")}e.push("}")}function fl(e){return e.pattern.split(":").filter(t=>!(t.startsWith("{")&&t.endsWith("}"))).filter(t=>t!=="").map(ye).join("")}function Dt(e,t,n){return fl(e)+ye(t.name)+n}function wm(e){return e.pattern.split(":").map(n=>{let s=n.length>2&&n.startsWith("{")&&n.endsWith("}")?n.slice(1,-1):void 0;return s===void 0?te(n):_e(s)}).join(` + ${te(":")} + `)}function yl(e){let t=new Set(e.pathParameters);return`listOf(${e.path.split("/").filter(s=>s!=="").map(s=>{let r=s.startsWith("{")&&s.endsWith("}")?s.slice(1,-1):void 0,a=r!==void 0&&t.has(r)?r:void 0;return a===void 0?`PathPart.Literal(${te(s)})`:`PathPart.Parameter(${_e(a)})`}).join(", ")})`}function fr(e,t,n,s){let r=" ".repeat(s),a=[`${r}${e} = buildMap {`];for(let i of bl(t.properties)){let o=`${n}.${i.identifier}`;!i.property.required||i.property.schema.nullable?a.push(`${r} ${o}?.let { value -> put(${te(i.property.name)}, ${il("value",i.property.schema)}) }`):a.push(`${r} put(${te(i.property.name)}, ${il(o,i.property.schema)})`)}return a.push(`${r}},`),a}var vm=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function _m(e){return e===-2147483648?"(-2147483647 - 1)":String(e)}function il(e,t){switch(t.kind){case"string":return e;case"enum":return`${e}.wireValue`;case"array":return`${e}.joinToString(",")`;default:return`${e}.toString()`}}function un(e,t){let n;switch(e.kind){case"string":n="String.serializer()";break;case"number":n="Double.serializer()";break;case"integer":n="Int.serializer()";break;case"boolean":n="Boolean.serializer()";break;case"any":n="kotlinx.serialization.json.JsonElement.serializer()";break;case"object":case"enum":n=`${t}.serializer()`;break;case"array":n=`ListSerializer(${un(e.element,`${t}Item`)})`;break;case"map":n=`MapSerializer(String.serializer(), ${un(e.values,`${t}Value`)})`;break}return e.nullable?`${n}.nullable`:n}function bl(e){let t=new Map;return e.map(n=>{let s=_e(n.name),r=s.length>=2&&s.startsWith("`")&&s.endsWith("`")?s.slice(1,-1):s,a=t.get(r);if(a!==void 0)throw ll(`wire keys ${Se(a)} and ${Se(n.name)} both become the Kotlin property \`${r}\``);return t.set(r,n.name),{property:n,identifier:s}})}function Gn(e){let t=e.operationId.split(".").filter(n=>n!=="");return t.length===0?["op"]:t}function Bn(e){return Gn(e).map(ye).join("")}function pn(e){return`PB${e.map(ye).join("")}Namespace`}function ye(e){return yr(vl(e,!0),e)}function _e(e){let t=yr(vl(e,!1),e);return Sl.has(t)?`\`${t}\``:t}function wl(e){let t=e.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").toUpperCase()||"VALUE";return yr(/^[0-9]/.test(t)?`_${t}`:t,e)}function yr(e,t){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)||/^_+$/.test(e))throw new Ke("InvalidName",`${Se(t)} gives ${Se(e)}, which is no Kotlin identifier`);return e}function vl(e,t){let n=e.match(/[A-Za-z0-9]+/g)??[];if(n.length===0)return t?"Op":"op";let s=n.map((r,a)=>(a===0&&!t?r.charAt(0).toLowerCase():r.charAt(0).toUpperCase())+r.slice(1)).join("");return/^[0-9]/.test(s)?`_${s}`:s}function Sm(e,t){return e.endsWith(t)?e.slice(0,-t.length):e}var km=/[\x85\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g,_l=e=>`\\u${e.codePointAt(0).toString(16).padStart(4,"0")}`;function te(e){return Yo(e).replace(km,_l)}function $m(e){return e.replace(/\r\n|[\r\n\x85\u2028\u2029]/g," ").replace(new RegExp(cl.source,"g"),_l).replaceAll("*/","* /").replaceAll("/*","/ *").replace(/^(\s*)@/,"$1\\@")}function Se(e){return`"${e.replace(/[\\"\n\r\t]/g,t=>({"\\":"\\\\",'"':'\\"',"\n":"\\n","\r":"\\r"," ":"\\t"})[t])}"`}function Am(e){return e.replace(/[\t\n\v\f\r\x1c-\x1f\p{Zs}\u2028\u2029]+$/u,"")}var Sl=new Set(["as","break","class","continue","do","else","false","for","fun","if","in","interface","is","null","object","package","return","super","this","throw","true","try","typealias","typeof","val","var","when","while"]);function Pm(e){if(!U(e))throw hr("the contract is not a JSON object");return Object.hasOwn(e,"x-palbase-roles")?e["x-palbase-roles"]:void 0}function Rm(e){if(!U(e))throw hr("root must be an object");let t=I(e,"roles");if(!Array.isArray(t))throw hr("the contract's `x-palbase-roles` carries no roles list, so what the stack defines cannot be read from it");let n=t.flatMap(i=>{if(!U(i))return[];let o=ke(i,"name");if(o===void 0||o==="")return[];let l=I(i,"isDefault"),d=I(i,"permissions");return[{name:o,description:ke(i,"description"),isDefault:(Lt(l)?cn(l):null)??!1,permissions:Array.isArray(d)?d.filter(u=>typeof u=="string"&&u!==""):[]}]}),s=yt(n,i=>i.name);if(s.length===0)return"";let r=[];r.push(""),r.push("// Roles and permissions (generated \u2014 do not edit)"),r.push("//"),r.push("// The roles this environment defines and the permissions they grant. A name"),r.push("// typed by hand is a string the server answers 403 to; a name typed here is a"),r.push("// compile error. Regenerated by `palbase spec` \u2014 a role withdrawn upstream"),r.push("// disappears from this file, so code still naming it stops building rather"),r.push("// than silently losing access."),r.push("enum class PalbaseRole(val key: String) {"),ol(r,s.map(i=>({key:i.name,doc:i.description??(i.isDefault?"Assigned to every new user at signup.":void 0)}))),r.push("}"),r.push(""),r.push("enum class PalbasePermission(val key: String) {");let a=[...new Set(s.flatMap(i=>i.permissions))].sort(Nt);return ol(r,a.map(i=>({key:i,doc:void 0}))),r.push("}"),r.join(`
526
+ `}function um(e,t){let n=`PB${ye(t.client)}Backend`;return e.push(`val PalbaseClient.${_e(t.client)}: ${n}`),e.push(` get() = ${n}(palbe(${te(t.machine)}))`),e.push(""),e.push(`class ${n} internal constructor(override val _pb: PalBackendClient) : PBBackend`),n}function cm(e,t){let n=Bn(t),s=!1;t.input!==void 0&&(It(e,`${n}Request`,t.input),s=!0),t.output!==void 0&&(s&&e.push(""),It(e,`${n}Response`,t.output),s=!0),t.headers!==void 0&&t.upload===void 0&&(s&&e.push(""),It(e,`${n}Headers`,t.headers),s=!0),t.query!==void 0&&(s&&e.push(""),It(e,`${n}Query`,t.query),s=!0),s&&e.push(""),mm(e,t)}function It(e,t,n){switch(n.kind){case"object":gl(e,t,n,0);return;case"array":{let s=[],r=ht(n.element,`${t}Item`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = List<${r}>`);return}case"enum":hl(e,t,n,0);return;case"map":{let s=[],r=ht(n.values,`${t}Value`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = Map<String, ${r}>${n.nullable?"?":""}`);return}default:e.push(`typealias ${t} = ${ht(n,t,[])}`)}}function gl(e,t,n,s){let r=" ".repeat(s),a=[],i=bl(n.properties).map(o=>{let l=ht(o.property.schema,t+ye(o.property.name),a,!o.property.required);return{field:o,type:l,defaultNull:!o.property.required||o.property.schema.nullable}});a.length>0&&(e.push(...a.map(o=>r+o)),e.push("")),e.push(`${r}@Serializable`),e.push(`${r}data class ${t}(`);for(let{field:o,type:l,defaultNull:p}of i)e.push(`${r} @SerialName(${te(o.property.name)})`),e.push(`${r} val ${o.identifier}: ${l}${p?" = null":""},`);e.push(`${r})`)}function ht(e,t,n,s=!1){let r;switch(e.kind){case"string":r="String";break;case"number":r="Double";break;case"integer":r="Int";break;case"boolean":r="Boolean";break;case"any":r="kotlinx.serialization.json.JsonElement";break;case"enum":hl(n,`${t}Value`,e,0),r=`${t}Value`;break;case"object":gl(n,t,e,0),r=t;break;case"array":r=`List<${ht(e.element,`${t}Item`,n)}>`;break;case"map":r=`Map<String, ${ht(e.values,`${t}Value`,n)}>`;break}return(e.nullable||s)&&!r.endsWith("?")?`${r}?`:r}function hl(e,t,n,s){let r=" ".repeat(s);e.push(`${r}@Serializable`),e.push(`${r}enum class ${t}(val wireValue: String) {`);let a=new Map;for(let i of n.values){let o=wl(i),l=(a.get(o)??0)+1;a.set(o,l);let p=l===1?o:`${o}_${l}`;e.push(`${r} @SerialName(${te(i)})`),e.push(`${r} ${p}(${te(i)}),`)}e.push(`${r}}`)}function mm(e,t){let n=`${Bn(t)}Error`;e.push(`sealed class ${n}(message: String, cause: Throwable? = null) : Exception(message, cause) {`);let s=new Map;for(let r of t.errors){if(r.data===void 0)continue;let a=[];s.set(r.name,ht(r.data,`${ye(r.name)}Data`,a)),e.push(...a.map(i=>` ${i}`)),e.push("")}for(let r of t.errors){let a=ye(r.name);r.data===void 0?e.push(` data object ${a} : ${n}(${te(r.code)})`):e.push(` data class ${a}(val data: ${s.get(r.name)}) : ${n}(${te(r.code)})`)}if(e.push(` data class Other(val backend: BackendError) : ${n}("Backend error", backend)`),e.push(""),e.push(" companion object {"),t.errors.length===0)e.push(` fun from(backend: BackendError): ${n} = Other(backend)`);else{e.push(` fun from(backend: BackendError): ${n} {`),e.push(" val response = backend.envelope ?: return Other(backend)"),e.push(" return when {");for(let r of t.errors){let a=`response.status == ${_m(r.status)} && response.code == ${te(r.code)}`;if(r.data===void 0)e.push(` ${a} -> ${ye(r.name)}`);else{let i=Sm(s.get(r.name),"?");e.push(` ${a} ->`),e.push(` response.decodeData(${un(r.data,i)})?.let(::${ye(r.name)}) ?: Other(backend)`)}}e.push(" else -> Other(backend)"),e.push(" }"),e.push(" }")}e.push(" }"),e.push("}")}function al(e){return[...e.children.keys()].sort(Nt).map(t=>e.children.get(t))}function gm(e,t,n){if(t.length===0)return;let s={path:[],children:new Map,operations:[]};for(let o of t){let l=s;for(let p of Gn(o).slice(0,-1)){let u=l.children.get(p);u===void 0&&(u={path:[...l.path,p],children:new Map,operations:[]},l.children.set(p,u)),l=u}l.operations.push(o)}e.push("");let r=al(s);r.forEach((o,l)=>{let p=_e(o.path[o.path.length-1]);e.push(`val ${n}.${p}: ${dn(o.path)}`),e.push(` get() = ${dn(o.path)}(_pb)`),l!==r.length-1&&e.push("")}),e.push("");let a=[...r],i=!0;for(;a.length>0;){let o=a.shift();i||e.push(""),i=!1,e.push(`class ${dn(o.path)} internal constructor(private val client: PalBackendClient) {`);for(let p of al(o)){let u=_e(p.path[p.path.length-1]);e.push(` val ${u}: ${dn(p.path)}`),e.push(` get() = ${dn(p.path)}(client)`),e.push(""),a.push(p)}let l=yt(o.operations,p=>p.operationId);l.forEach((p,u)=>{hm(e,p),u!==l.length-1&&e.push("")}),e.length>0&&e[e.length-1]===""&&e.pop(),e.push("}")}}function hm(e,t){if(t.upload!==void 0){ym(e,t);return}let n=Bn(t),s=t.method===""?"POST":t.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the method ${Se(s)} is not an HTTP method's name`);for(let p of t.headers?.properties??[])if(!vm.test(p.name))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the header ${Se(p.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);let r=[];for(let p of t.pathParameters)r.push(`${_e(p)}: String`);t.input!==void 0&&r.push(`input: ${n}Request`),t.query!==void 0&&r.push(`query: ${n}Query`),t.headers!==void 0&&r.push(`headers: ${n}Headers`);let a=t.sse&&t.output!==void 0,i=a?`: Flow<${n}Response>`:t.output!==void 0?`: ${n}Response`:"",o=a?"streamGenerated":"invokeGenerated",l=Gn(t);e.push(` suspend fun ${_e(l[l.length-1])}(${r.join(", ")})${i} = try {`),e.push(` client.${o}(`),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${te(s)},`),e.push(` path = ${yl(t)},`),t.query!==void 0&&e.push(...fr("query",t.query,"query",4)),t.headers!==void 0&&e.push(...fr("headers",t.headers,"headers",4)),t.input!==void 0&&e.push(" body = input,"),t.idempotent&&!t.sse&&e.push(" idempotent = true,"),e.push(" ),"),e.push(` ${t.input===void 0?"Unit.serializer()":`${n}Request.serializer()`},`),t.output!==void 0&&e.push(` ${un(t.output,`${n}Response`)},`),e.push(" )"),e.push(" } catch (error: BackendError) {"),e.push(` throw ${n}Error.from(error)`),e.push(" }")}var fm=["file","filename","contentType","onProgress"];function ym(e,t){let n=Bn(t);for(let i of t.pathParameters)if(fm.includes(_e(i)))throw new Ke("InvalidName",`operation ${Se(t.operationId)}: the path parameter ${Se(i)} is the name of the upload's ${_e(i)}: rename it in the backend`);let s=[];for(let i of t.pathParameters)s.push(`${_e(i)}: String`);t.input!==void 0&&s.push(`input: ${n}Request`),t.query!==void 0&&s.push(`query: ${n}Query`),s.push("file: ByteArray","filename: String","contentType: String? = null","onProgress: ((UploadProgress) -> Unit)? = null");let r=t.output!==void 0?`: ${n}Response`:"",a=Gn(t);e.push(` suspend fun ${_e(a[a.length-1])}(${s.join(", ")})${r} = try {`),e.push(" client.uploadGenerated("),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${te("POST")},`),e.push(` path = ${yl(t)},`),t.query!==void 0&&e.push(...fr("query",t.query,"query",4)),t.input!==void 0&&e.push(" body = input,"),e.push(" ),"),e.push(` ${t.input===void 0?"Unit.serializer()":`${n}Request.serializer()`},`),t.output!==void 0&&e.push(` ${un(t.output,`${n}Response`)},`),e.push(" file = file,"),e.push(" filename = filename,"),e.push(" contentType = contentType,"),e.push(" onProgress = onProgress,"),e.push(" )"),e.push(" } catch (error: BackendError) {"),e.push(` throw ${n}Error.from(error)`),e.push(" }")}function bm(e,t){let n=fl(t),s=t.events.length===0?"Nothing":`${n}RoomEvent`,r=t.messages.length===0?"Nothing":`${n}RoomSend`;for(let i of t.events)It(e,Dt(t,i,"Event"),i.schema),e.push("");if(t.events.length>0){e.push(`sealed interface ${n}RoomEvent {`);for(let i of t.events)e.push(` data class ${ye(i.name)}(val payload: ${Dt(t,i,"Event")}) : ${n}RoomEvent`);e.push("}"),e.push("")}for(let i of t.messages)It(e,Dt(t,i,"Message"),i.schema),e.push("");if(t.messages.length>0){e.push(`sealed interface ${n}RoomSend {`);for(let i of t.messages)e.push(` data class ${ye(i.name)}(val payload: ${Dt(t,i,"Message")}) : ${n}RoomSend`);e.push("}"),e.push("")}let a=t.params.map(i=>` private val ${_e(i)}: String,`);if(a.length===0?e.push(`class ${n}Room : PBRoom<${s}, ${r}> {`):(e.push(`class ${n}Room(`),e.push(...a),e.push(`) : PBRoom<${s}, ${r}> {`)),e.push(` override val pattern: String = ${te(t.pattern)}`),e.push(` override val eventNames: List<String> = listOf(${t.events.map(i=>te(i.name)).join(", ")})`),e.push(` override val topic: String get() = ${wm(t)}`),e.push(""),e.push(` override fun decodeEvent(event: String, payload: JsonObject): ${s}? =`),t.events.length===0)e.push(" null");else{e.push(" when (event) {");for(let i of t.events)e.push(` ${te(i.name)} -> ${n}RoomEvent.${ye(i.name)}(`),e.push(` palbaseGeneratedJson.decodeFromJsonElement(${Dt(t,i,"Event")}.serializer(), payload),`),e.push(" )");e.push(" else -> null"),e.push(" }")}if(e.push(""),e.push(` override fun encodeMessage(message: ${r}): Pair<String, JsonObject> =`),t.messages.length===0)e.push(" throw PBRoomError.NoMessagesDeclared(pattern)");else{e.push(" when (message) {");for(let i of t.messages)e.push(` is ${n}RoomSend.${ye(i.name)} -> ${te(i.name)} to`),e.push(` palbaseGeneratedJson.encodeToJsonElement(${Dt(t,i,"Message")}.serializer(), message.payload).jsonObject`);e.push(" }")}e.push("}")}function fl(e){return e.pattern.split(":").filter(t=>!(t.startsWith("{")&&t.endsWith("}"))).filter(t=>t!=="").map(ye).join("")}function Dt(e,t,n){return fl(e)+ye(t.name)+n}function wm(e){return e.pattern.split(":").map(n=>{let s=n.length>2&&n.startsWith("{")&&n.endsWith("}")?n.slice(1,-1):void 0;return s===void 0?te(n):_e(s)}).join(` + ${te(":")} + `)}function yl(e){let t=new Set(e.pathParameters);return`listOf(${e.path.split("/").filter(s=>s!=="").map(s=>{let r=s.startsWith("{")&&s.endsWith("}")?s.slice(1,-1):void 0,a=r!==void 0&&t.has(r)?r:void 0;return a===void 0?`PathPart.Literal(${te(s)})`:`PathPart.Parameter(${_e(a)})`}).join(", ")})`}function fr(e,t,n,s){let r=" ".repeat(s),a=[`${r}${e} = buildMap {`];for(let i of bl(t.properties)){let o=`${n}.${i.identifier}`;!i.property.required||i.property.schema.nullable?a.push(`${r} ${o}?.let { value -> put(${te(i.property.name)}, ${il("value",i.property.schema)}) }`):a.push(`${r} put(${te(i.property.name)}, ${il(o,i.property.schema)})`)}return a.push(`${r}},`),a}var vm=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function _m(e){return e===-2147483648?"(-2147483647 - 1)":String(e)}function il(e,t){switch(t.kind){case"string":return e;case"enum":return`${e}.wireValue`;case"array":return`${e}.joinToString(",")`;default:return`${e}.toString()`}}function un(e,t){let n;switch(e.kind){case"string":n="String.serializer()";break;case"number":n="Double.serializer()";break;case"integer":n="Int.serializer()";break;case"boolean":n="Boolean.serializer()";break;case"any":n="kotlinx.serialization.json.JsonElement.serializer()";break;case"object":case"enum":n=`${t}.serializer()`;break;case"array":n=`ListSerializer(${un(e.element,`${t}Item`)})`;break;case"map":n=`MapSerializer(String.serializer(), ${un(e.values,`${t}Value`)})`;break}return e.nullable?`${n}.nullable`:n}function bl(e){let t=new Map;return e.map(n=>{let s=_e(n.name),r=s.length>=2&&s.startsWith("`")&&s.endsWith("`")?s.slice(1,-1):s,a=t.get(r);if(a!==void 0)throw ll(`wire keys ${Se(a)} and ${Se(n.name)} both become the Kotlin property \`${r}\``);return t.set(r,n.name),{property:n,identifier:s}})}function Gn(e){let t=e.operationId.split(".").filter(n=>n!=="");return t.length===0?["op"]:t}function Bn(e){return Gn(e).map(ye).join("")}function dn(e){return`PB${e.map(ye).join("")}Namespace`}function ye(e){return yr(vl(e,!0),e)}function _e(e){let t=yr(vl(e,!1),e);return Sl.has(t)?`\`${t}\``:t}function wl(e){let t=e.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").toUpperCase()||"VALUE";return yr(/^[0-9]/.test(t)?`_${t}`:t,e)}function yr(e,t){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)||/^_+$/.test(e))throw new Ke("InvalidName",`${Se(t)} gives ${Se(e)}, which is no Kotlin identifier`);return e}function vl(e,t){let n=e.match(/[A-Za-z0-9]+/g)??[];if(n.length===0)return t?"Op":"op";let s=n.map((r,a)=>(a===0&&!t?r.charAt(0).toLowerCase():r.charAt(0).toUpperCase())+r.slice(1)).join("");return/^[0-9]/.test(s)?`_${s}`:s}function Sm(e,t){return e.endsWith(t)?e.slice(0,-t.length):e}var km=/[\x85\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g,_l=e=>`\\u${e.codePointAt(0).toString(16).padStart(4,"0")}`;function te(e){return Yo(e).replace(km,_l)}function $m(e){return e.replace(/\r\n|[\r\n\x85\u2028\u2029]/g," ").replace(new RegExp(cl.source,"g"),_l).replaceAll("*/","* /").replaceAll("/*","/ *").replace(/^(\s*)@/,"$1\\@")}function Se(e){return`"${e.replace(/[\\"\n\r\t]/g,t=>({"\\":"\\\\",'"':'\\"',"\n":"\\n","\r":"\\r"," ":"\\t"})[t])}"`}function Am(e){return e.replace(/[\t\n\v\f\r\x1c-\x1f\p{Zs}\u2028\u2029]+$/u,"")}var Sl=new Set(["as","break","class","continue","do","else","false","for","fun","if","in","interface","is","null","object","package","return","super","this","throw","true","try","typealias","typeof","val","var","when","while"]);function Pm(e){if(!M(e))throw hr("the contract is not a JSON object");return Object.hasOwn(e,"x-palbase-roles")?e["x-palbase-roles"]:void 0}function Rm(e){if(!M(e))throw hr("root must be an object");let t=I(e,"roles");if(!Array.isArray(t))throw hr("the contract's `x-palbase-roles` carries no roles list, so what the stack defines cannot be read from it");let n=t.flatMap(i=>{if(!M(i))return[];let o=ke(i,"name");if(o===void 0||o==="")return[];let l=I(i,"isDefault"),p=I(i,"permissions");return[{name:o,description:ke(i,"description"),isDefault:(Lt(l)?cn(l):null)??!1,permissions:Array.isArray(p)?p.filter(u=>typeof u=="string"&&u!==""):[]}]}),s=yt(n,i=>i.name);if(s.length===0)return"";let r=[];r.push(""),r.push("// Roles and permissions (generated \u2014 do not edit)"),r.push("//"),r.push("// The roles this environment defines and the permissions they grant. A name"),r.push("// typed by hand is a string the server answers 403 to; a name typed here is a"),r.push("// compile error. Regenerated by `palbase spec` \u2014 a role withdrawn upstream"),r.push("// disappears from this file, so code still naming it stops building rather"),r.push("// than silently losing access."),r.push("enum class PalbaseRole(val key: String) {"),ol(r,s.map(i=>({key:i.name,doc:i.description??(i.isDefault?"Assigned to every new user at signup.":void 0)}))),r.push("}"),r.push(""),r.push("enum class PalbasePermission(val key: String) {");let a=[...new Set(s.flatMap(i=>i.permissions))].sort(Nt);return ol(r,a.map(i=>({key:i,doc:void 0}))),r.push("}"),r.join(`
527
527
  `)+`
528
- `}function ol(e,t){let n=new Map;for(let{key:s,doc:r}of t){let a=Tm(s),i=n.get(a);if(i!==void 0){e.push(` // codegen: skipped duplicate key ${te(s)} \u2014 collides with ${te(i)}`);continue}n.set(a,s),r!==void 0&&e.push(` /** ${$m(r)} */`),e.push(` ${a}(${te(s)}),`)}}function Tm(e){return wl(e)}var kl=new Map([["AEBuildError","CoreServices"],["BackendError","Palbe"],["CFError","CoreFoundation"],["CFNetServicesError","CFNetwork"],["CFSocketError","CoreFoundation"],["CFStreamError","CoreFoundation"],["CSIdentityQuery","CoreServices"],["CachedURLResponse","Foundation"],["CancellationError","_Concurrency"],["CocoaError","Foundation"],["ConfigurationError","Palbase"],["CustomNSError","Foundation"],["DecodingError","Swift"],["EncodingError","Swift"],["FieldError","Palbe"],["HTTPURLResponse","Foundation"],["LocalizedError","Foundation"],["MDQuery","CoreServices"],["MachError","Foundation"],["MutableURLRequest","Foundation"],["NSBundleResourceRequest","Foundation"],["NSError","Foundation"],["NSMetadataQuery","Foundation"],["NSMutableURLRequest","Foundation"],["NSURLRequest","Foundation"],["NSXPCListenerEndpoint","Foundation"],["PBEndpoint","Palbe"],["PBError","Palbe"],["PBRequest","Palbe"],["PBRoom","Palbe"],["PBRoomError","Palbe"],["PBSseEndpoint","Palbe"],["PBUploadEndpoint","Palbe"],["PBVoidEndpoint","Palbe"],["POSIXError","Foundation"],["PalbaseError","Palbase"],["PredicateError","Foundation"],["RecoverableError","Foundation"],["SessionChangedError","Palbase"],["URLError","Foundation"],["URLRequest","Foundation"],["URLResponse","Foundation"],["XPCEndpoint","XPC"],["XPCRichError","XPC"]]);function jl(e,t){Cm(t);let n=new xe,s=Nm(e,n),r=Fm(e,n),a="PB"+Ee(t.client)+"Backend";jm(s,t.client,a);let i="";for(let o of t.header)i+="// "+o+`
528
+ `}function ol(e,t){let n=new Map;for(let{key:s,doc:r}of t){let a=Tm(s),i=n.get(a);if(i!==void 0){e.push(` // codegen: skipped duplicate key ${te(s)} \u2014 collides with ${te(i)}`);continue}n.set(a,s),r!==void 0&&e.push(` /** ${$m(r)} */`),e.push(` ${a}(${te(s)}),`)}}function Tm(e){return wl(e)}var kl=new Map([["AEBuildError","CoreServices"],["BackendError","Palbe"],["CFError","CoreFoundation"],["CFNetServicesError","CFNetwork"],["CFSocketError","CoreFoundation"],["CFStreamError","CoreFoundation"],["CSIdentityQuery","CoreServices"],["CachedURLResponse","Foundation"],["CancellationError","_Concurrency"],["CocoaError","Foundation"],["ConfigurationError","Palbase"],["CustomNSError","Foundation"],["DecodingError","Swift"],["EncodingError","Swift"],["FieldError","Palbe"],["HTTPURLResponse","Foundation"],["LocalizedError","Foundation"],["MDQuery","CoreServices"],["MachError","Foundation"],["MutableURLRequest","Foundation"],["NSBundleResourceRequest","Foundation"],["NSError","Foundation"],["NSMetadataQuery","Foundation"],["NSMutableURLRequest","Foundation"],["NSURLRequest","Foundation"],["NSXPCListenerEndpoint","Foundation"],["PBEndpoint","Palbe"],["PBError","Palbe"],["PBRequest","Palbe"],["PBRoom","Palbe"],["PBRoomError","Palbe"],["PBSseEndpoint","Palbe"],["PBUploadEndpoint","Palbe"],["PBVoidEndpoint","Palbe"],["POSIXError","Foundation"],["PalbaseError","Palbase"],["PredicateError","Foundation"],["RecoverableError","Foundation"],["SessionChangedError","Palbase"],["URLError","Foundation"],["URLRequest","Foundation"],["URLResponse","Foundation"],["XPCEndpoint","XPC"],["XPCRichError","XPC"]]);function jl(e,t){Cm(t);let n=new Ue,s=Nm(e,n),r=Fm(e,n),a="PB"+Ee(t.client)+"Backend";jm(s,t.client,a);let i="";for(let o of t.header)i+="// "+o+`
529
529
  `;if(i+=`import Foundation
530
530
  `,i+=`import Palbase
531
531
  `,i+=`import Palbe
@@ -547,14 +547,14 @@ public typealias JSONValue = JsonElement
547
547
  // MARK: - Rooms (@Room)
548
548
 
549
549
  `;let o=new Map;for(let l of r)i+=ug(l,o)+`
550
- `}return i}var Em=new Set("associatedtype borrowing class consuming deinit enum extension fileprivate func import init inout internal let operator precedencegroup private protocol public rethrows static struct subscript typealias var break case catch continue default defer do else fallthrough for guard if in repeat return throw switch where while as await false is nil self super throws true try".split(" "));function Cm(e){if(!/^[a-z][A-Za-z0-9]*$/.test(e.client)||Em.has(e.client))throw new Error(`client ${JSON.stringify(e.client)} is not a lowerCamel Swift identifier`);for(let t of e.header)if(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/.test(t))throw new Error(`header line ${JSON.stringify(t)} would end its comment, or read as other than it is`)}function zn(e,t){let n=kl.get(t);if(n!==void 0)throw new Error(`${e} declares the type ${t}, which in the app would stand in for ${n}'s ${t}: rename it in the backend`)}function jm(e,t,n){zn(`client ${JSON.stringify(t)}`,n);for(let s of e){let r=Hn(s.operationID);for(let a=1;a<r.length;a++)zn(`operation ${JSON.stringify(s.operationID)}, in namespace ${JSON.stringify(r.slice(0,a).join("."))},`,br(r.slice(0,a),n))}}function Om(e){let t='"';for(let n of e){let s=n.codePointAt(0);if(s>=55296&&s<=57343)throw new Error(`machine ${JSON.stringify(e)} is not a Swift string`);n==='"'||n==="\\"?t+="\\"+n:s>=32&&s<127?t+=n:t+="\\u{"+s.toString(16)+"}"}return t+'"'}var Dm=new Intl.Segmenter("en",{granularity:"grapheme"});function Le(e){return Array.from(Dm.segment(e),t=>t.segment)}function ee(e){return e.normalize("NFC")}function ne(e,t){return ee(e)===ee(t)}function Ve(e,t){let n=Array.from(ee(e)),s=Array.from(ee(t));for(let r=0;r<n.length&&r<s.length;r++){let a=n[r].codePointAt(0)-s[r].codePointAt(0);if(a!==0)return a}return n.length-s.length}var Ol=e=>[...e].sort(Ve);function wt(e,t,n){let s=[],r="";for(let a of Le(e))ne(a,t)?((!n||r!=="")&&s.push(r),r=""):r+=a;return(!n||r!=="")&&s.push(r),s}function Mt(e,t,n){let s=Le(e),r=Le(t),a="";for(let i=0;i<s.length;)i+r.length<=s.length&&r.every((o,l)=>ne(s[i+l],o))?(a+=n,i+=r.length):a+=s[i++];return a}var $l=new WeakMap;function wr(e){let t=$l.get(e);if(t===void 0){t=new Map;for(let n of Object.keys(e))e[n]!==void 0&&(t.delete(ee(n)),t.set(ee(n),[n,e[n]]));$l.set(e,t)}return t}var E=(e,t)=>wr(e).get(ee(t))?.[1],ot=(e,t)=>wr(e).has(ee(t)),He=e=>[...wr(e).values()],vr=e=>He(e).map(([t])=>t);function N(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0}function Ie(e){return Array.isArray(e)?e:void 0}function de(e){return typeof e=="string"?e:void 0}function Vn(e){if(typeof e=="boolean")return e;if(e===1)return!0;if(e===0)return!1}function Im(e){if(typeof e=="number")return e;if(typeof e=="boolean")return e?1:0}var bt=(e,t=!1)=>({kind:e,nullable:t,props:[],enumVals:[]}),Dl=2n**63n-1n,Il=-(2n**63n);function Lm(e){return Number.isNaN(e)?0n:e>=2**63?Dl:e<=-(2**63)?Il:BigInt(Math.trunc(e))}function Nm(e,t=new xe){let n=N(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=N(E(n,"paths"));if(s===void 0)throw new Error("openapi.json has no `paths`");let r=[];for(let[a,i]of He(s)){let o=N(i);if(o!==void 0)for(let[l,d]of He(o)){let u=N(d);if(u===void 0)continue;let p=de(E(u,"operationId"))??"";if(p==="")continue;let f={where:`operation ${JSON.stringify(p)}`,budget:t,through:p.length,path:new Set};r.push({operationID:p,method:l.toUpperCase(),path:a,pathParams:Gm(a),input:xm(u,f),output:Bm(u,f),headers:Pl(u,"header",f),query:Pl(u,"query",f),errors:qm(u,f),upload:Mm(u),sse:ot(u,"x-palbase-sse"),idempotent:Km(u)})}}return r.sort((a,i)=>Ve(a.operationID,i.operationID)),r}function qm(e,t){let n=N(E(e,"x-palbase-errors"));if(n===void 0)return[];let s=N(E(e,"responses")),r=[];for(let[a,i]of He(n)){let o=N(i);if(o===void 0)continue;let l=Im(E(o,"status"))??0,d=de(E(o,"code"))??"",u=de(E(o,"description"))??"",p=Vn(E(o,"hasData"))??!1;if(d===""||l===0)continue;let f={name:a,code:d,status:Lm(l),description:u};p&&(f.data=Um(s,f.status,d,{...t,through:t.through+a.length})),r.push(f)}return r.sort((a,i)=>Ve(a.name,i.name)),r}function Mm(e){let t=N(E(e,"x-palbase-upload"));if(t===void 0||He(t).length===0)return;let n=de(E(t,"bucket"))??"",s=de(E(t,"pathTemplate"))??"";if(!(n===""||s===""))return{bucket:n,pathTemplate:s}}function Um(e,t,n,s){if(e===void 0)return;let r=N(E(e,t.toString()));if(r===void 0)return;let a=N(E(r,"content"));if(a===void 0)return;let i=N(E(a,"application/json"));if(i===void 0)return;let o=N(E(i,"schema"));if(o===void 0)return;let l=Ie(E(o,"oneOf"));if(l!==void 0){for(let d of l){let u=N(d);if(u===void 0)continue;let p=N(E(u,"properties")),f=p===void 0?void 0:N(E(p,"error"));if(f===void 0)continue;let y=de(E(f,"const"));if(y!==void 0&&ne(y,n))return Al(u,s)}return}return Al(o,s)}function Al(e,t){let n=N(E(e,"properties"));if(n===void 0)return;let s=N(E(n,"data"));if(s!==void 0)return lt(s,s,t)}function xm(e,t){let n=N(E(e,"requestBody"));if(n!==void 0)return Nl(E(n,"content"),t)}var Ll=e=>ee(e).toLowerCase()==="idempotency-key";function Km(e){return(Ie(E(e,"parameters"))??[]).some(t=>{let n=N(t);return n!==void 0&&ne(de(E(n,"in"))??"","header")&&Ll(de(E(n,"name"))??"")})}function Pl(e,t,n){let s=Ie(E(e,"parameters"));if(s===void 0||s.length===0)return;let r=[];for(let a of s){let i=N(a);if(i===void 0||!ne(de(E(i,"in"))??"",t))continue;let o=de(E(i,"name"))??"";if(o===""||t==="header"&&Ll(o))continue;let l=Vn(E(i,"required"))??!1,d=N(E(i,"schema"));r.push({name:o,schema:d===void 0?bt("string"):lt(d,d,{...n,through:n.through+o.length}),required:l})}if(r.length!==0)return r.sort((a,i)=>Ve(a.name,i.name)),{kind:"object",nullable:!1,props:r,enumVals:[]}}function Gm(e){let t=[],n=e;for(;;){let s=n.indexOf("{");if(s<0)break;let r=n.indexOf("}",s);if(r<0)break;let a=n.slice(s+1,r);a!==""&&t.push(a),n=n.slice(r+1)}return t}function Bm(e,t){let n=N(E(e,"responses"));if(n===void 0)return;let s=vr(n).filter(r=>ne(Le(r)[0]??"","2")&&!ne(r,"200")&&!ne(r,"201"));for(let r of["200","201",...Ol(s)]){let a=N(E(n,r));if(a===void 0)continue;let i=Nl(E(a,"content"),t);if(i!==void 0)return i}}function Nl(e,t){let n=N(e);if(n===void 0)return;let s=N(E(n,"application/json"));if(s===void 0)return;let r=N(E(s,"schema"));if(r!==void 0&&!ot(r,"$ref"))return lt(r,r,t)}function lt(e,t,n,s=0,r=n.through){let a=e,i=Wm(a,t);if(i!==void 0&&s<16){if(n.path.has(i))throw new Error(`${n.where}: the $ref ${JSON.stringify(de(E(a,"$ref")))} points back into a schema it is part of, a recursive schema, which no Swift type is written for: refused`);a=i}else if(ot(a,"$ref"))return bt("any");let o=N(E(a,"properties")),l=(o===void 0?0:Rl(vr(o)))+Rl(Ie(E(a,"enum"))??[]);n.budget.charge(n.where,s,r,l),n.path.add(a);try{return Jm(a,t,n,s,r)}finally{n.path.delete(a)}}var Rl=e=>e.reduce((t,n)=>t+(de(n)?.length??0),0);function Jm(e,t,n,s,r){let a=Vn(E(e,"nullable"))??!1,i=Ie(E(e,"type"));if(i!==void 0)for(let u of i)de(u)==="null"&&(a=!0);let o=Ie(E(e,"anyOf"))??Ie(E(e,"oneOf"));if(o!==void 0){let u=o.map(N).filter(f=>f!==void 0),p=u.filter(f=>!zm(f));if(u.length===o.length&&p.length===1&&u.length>p.length){let f=lt(p[0],t,n,s+1,r);return f.nullable=!0,f}}let l=Ie(E(e,"enum"));if(l!==void 0){let u=[],p=!0;for(let f of l){let y=de(f);if(y===void 0){p=!1;break}u.push(y)}if(p&&u.length>0)return{kind:"enum",nullable:a,props:[],enumVals:u}}let d=de(E(e,"type"))??"";if(d===""&&i!==void 0)for(let u of i){let p=de(u);p!==void 0&&(p==="null"?a=!0:d===""&&(d=p))}switch(d){case"string":case"number":case"integer":case"boolean":return bt(d,a);case"array":{let u=N(E(e,"items"));return{kind:"array",nullable:a,props:[],elem:u===void 0?bt("any"):lt(u,t,n,s+1,r),enumVals:[]}}case"object":return Tl(e,a,t,n,s,r);default:return ot(e,"properties")||ot(e,"additionalProperties")?Tl(e,a,t,n,s,r):bt("any",a)}}function Tl(e,t,n,s,r,a){let i=N(E(e,"properties"))??{},o=E(e,"additionalProperties"),l,d=N(o);if(d!==void 0?l=lt(d,n,s,r+1,a):(Vn(o)===!0||He(i).length===0&&!ot(e,"additionalProperties"))&&(l=bt("any")),l!==void 0)return{kind:He(i).length===0?"map":"mixedObject",nullable:t,props:[],elem:l,enumVals:[]};let u=new Set;for(let f of Ie(E(e,"required"))??[]){let y=de(f);y!==void 0&&u.add(ee(y))}let p=[];for(let f of Ol(vr(i))){let y=N(E(i,f));p.push({name:f,schema:y===void 0?bt("any"):lt(y,n,s,r+1,a+f.length),required:u.has(ee(f))})}return{kind:"object",nullable:t,props:p,enumVals:[]}}function zm(e){let t=E(e,"type");if(typeof t=="string")return t==="null";let n=Ie(t);if(n!==void 0){let s=n.filter(r=>typeof r=="string");return s.length===1&&s[0]==="null"}return!1}function Wm(e,t){let n=de(E(e,"$ref"));if(n===void 0)return;let s=Le(n);if(!ne(s[0]??"","#")||!ne(s[1]??"","/"))return;let r=t;for(let a of wt(s.slice(2).join(""),"/",!0)){let i=Mt(Mt(a,"~1","/"),"~0","~"),o=N(r),l=Ie(r),d=Xm(i);if(o!==void 0&&ot(o,i))r=E(o,i);else if(l!==void 0&&d!==void 0&&d>=0n&&d<BigInt(l.length))r=l[Number(d)];else return}return N(r)}function Fm(e,t=new xe){let n=N(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=N(E(n,"x-palbase-room"));if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=N(i);if(o===void 0)continue;let l={where:`room ${JSON.stringify(a)}`,budget:t,through:a.length,path:new Set};r.push({pattern:a,params:Vm(a),events:El(E(o,"events"),n,l),messages:El(E(o,"messages"),n,l)})}return r.sort((a,i)=>Ve(a.pattern,i.pattern)),r}function ql(e){let t=Le(e);return t.length>2&&ne(t[0],"{")&&ne(t[t.length-1],"}")?t.slice(1,-1).join(""):void 0}function Vm(e){return wt(e,":",!1).flatMap(t=>ql(t)??[])}function El(e,t,n){let s=N(e);if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=N(i);if(o===void 0)continue;let l=Hm(o,t)??o;r.push({name:a,schema:lt(l,l,{...n,through:n.through+a.length})})}return r.sort((a,i)=>Ve(a.name,i.name)),r}function Hm(e,t){let n=de(E(e,"$ref"));if(n===void 0)return;let s=Le(n);if(!ne(s[0]??"","#")||!ne(s[1]??"","/"))return;let r=t;for(let a of wt(s.slice(2).join(""),"/",!0)){let i=Mt(Mt(a,"~1","/"),"~0","~"),o=N(r);if(o===void 0||!ot(o,i))return;r=E(o,i)}return N(r)}function Xm(e){if(!/^[+-]?[0-9]+$/.test(e))return;let t=BigInt(e);return t>Dl||t<Il?void 0:t}function ge(e){let t='"';for(let n of e){let s=n.codePointAt(0);if(n==='"')t+='\\"';else if(n==="\\")t+="\\\\";else if(n===`
551
- `)t+="\\n";else if(n==="\r")t+="\\r";else if(n===" ")t+="\\t";else{if(s>=55296&&s<=57343)throw new Error(`${JSON.stringify(e)} is not a Swift string: it carries a lone surrogate`);s<32||s>=127&&s<=159||s===8232||s===8233||Ym.has(s)?t+="\\u{"+s.toString(16)+"}":t+=n}}return t+'"'}var Ym=new Set([1564,8206,8207,8234,8235,8236,8237,8238,8294,8295,8296,8297]),Hn=e=>wt(e,".",!0),Ee=e=>Ml(e,!0);function W(e){let t=Ml(e,!1);if(eg.has(t))throw new Error(`the name ${JSON.stringify(e)} becomes the Swift keyword ${t}, which v2 did not put in backticks and Swift takes as no name: rename it in the backend`);return ng(t)}var xt=e=>Hn(e).map(Ee).join(""),Xn=e=>xt(e)+"Request",Ut=e=>xt(e)+"Response",Yn=e=>xt(e)+"Error",_r=e=>xt(e)+"Headers",gn=e=>xt(e)+"Query";function Zm(e){let t=(n,s)=>Ve(e,n)>=0&&Ve(e,s)<=0;return t("a","z")||t("A","Z")||t("0","9")}function Ml(e,t){let n=[],s=[];for(let i of Le(e))Zm(i)?s.push(i):s.length>0&&(n.push(s),s=[]);if(s.length>0&&n.push(s),n.length===0)return t?"Op":"op";let r="";n.forEach((i,o)=>{r+=(o===0&&!t?i[0].toLowerCase():i[0].toUpperCase())+i.slice(1).join("")});let a=r.codePointAt(0);if(a>=48&&a<=57&&(r="_"+r),!/^[A-Za-z0-9_]+$/.test(r))throw new Error(`the name ${JSON.stringify(e)} becomes ${JSON.stringify(r)} in Swift, which is more than A-Z, a-z, 0-9 and _: rename it in the backend`);return r}var Qm=new Set(["class","struct","enum","func","let","var","return","if","else","for","while","switch","case","default","import","public","private","internal","protocol","extension","self","init","deinit","in","where","as","is","try","throw","throws","async","await","nil","true","false","guard","defer","do","catch","repeat","operator","associatedtype","any"]),eg=new Set(["break","continue","fallthrough","fileprivate","inout","precedencegroup","rethrows","static","subscript","super","typealias"]),tg=new Set(["Any","Self","Type","Protocol"]);function ng(e){return Qm.has(ee(e))?"`"+e+"`":e}var it=e=>Mt(e,"`","");function Cl(e,t){if(ne(e,t))return!0;let n=Le(t);if(!n.some(l=>ne(l,"_")))return!1;let s=0;for(;s<n.length&&ne(n[s],"_");)s++;let r=0;for(;r<n.length-s&&ne(n[n.length-1-r],"_");)r++;let a=n.slice(s,n.length-r).join("");if(a==="")return!1;let i=n.slice(0,s).join(""),o=wt(a,"_",!1);for(let l=0;l<o.length;l++){if(o[l]==="")return!1;if(l===0)i+=o[l].toLowerCase();else{let d=Le(o[l]);i+=d[0].toUpperCase()+d.slice(1).join("").toLowerCase()}}return i+=n.slice(n.length-r).join(""),ne(i,e)}var w=e=>" ".repeat(e);function De(e,t){for(let n of t)e.push(n)}function sg(e){let t=[];for(let n of e){let s=t.length,r=!1;if(n.input!==void 0&&(De(t,mn(Xn(n.operationID),n.input,!0)),r=!0),n.output!==void 0?(r&&t.push(""),De(t,mn(Ut(n.operationID),n.output)),r=!0):n.upload!==void 0&&(r&&t.push(""),t.push("public nonisolated struct "+Ut(n.operationID)+": Codable, Sendable {"),t.push("}"),r=!0),n.headers!==void 0){for(let a of n.headers.props)if(!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(a.name))throw new Error(`operation ${JSON.stringify(n.operationID)}: the header ${JSON.stringify(a.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);r&&t.push(""),De(t,ig(_r(n.operationID),n.headers)),r=!0}n.query!==void 0&&(r&&t.push(""),De(t,Qn(gn(n.operationID),n.query.props,0)),r=!0),r&&t.push(""),De(t,og(Yn(n.operationID),n.errors)),t.push(""),De(t,rg(n)),t.push("");for(let a of t.slice(s)){let i=/^public (?:nonisolated )?(?:struct|enum|typealias) ([A-Za-z0-9_]+)/.exec(a);i!==null&&zn(`operation ${JSON.stringify(n.operationID)}`,i[1])}}return t.join(`
552
- `)}var Zn=e=>xt(e)+"Endpoint";function Ul(e){let t=[];for(let n of wt(e,"/",!1)){if(n==="")continue;let s=Le(n);if(s.length>=2&&ne(s[0],"{")&&ne(s[s.length-1],"}")){let r=s.slice(1,s.length-1).join("");if(r==="")continue;t.push(".param("+W(r)+")")}else t.push(ge(n))}return"["+t.join(", ")+"]"}function xl(e){return e.path===""?"/"+Mt(e.operationID,".","/"):e.path}function rg(e){if(e.upload!==void 0)return ag(e);let t=Zn(e.operationID),n=[];e.sse?(n.push("public nonisolated struct "+t+": PBSseEndpoint {"),n.push(w(1)+"public typealias Element = "+Ut(e.operationID))):e.output!==void 0?(n.push("public nonisolated struct "+t+": PBEndpoint {"),n.push(w(1)+"public typealias Response = "+Ut(e.operationID))):n.push("public nonisolated struct "+t+": PBVoidEndpoint {"),n.push(w(1)+"public typealias Failure = "+Yn(e.operationID));for(let a of e.pathParams)n.push(w(1)+"let "+W(a)+": String");e.input!==void 0&&n.push(w(1)+"let input: "+Xn(e.operationID)),e.query!==void 0&&n.push(w(1)+"let query: "+gn(e.operationID)),e.headers!==void 0&&n.push(w(1)+"let headers: "+_r(e.operationID));let s=e.method===""?"POST":e.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Error(`operation ${JSON.stringify(e.operationID)}: the method ${JSON.stringify(s)} is not a Swift name`);let r=["."+s.toLowerCase(),Ul(xl(e))];if(e.input!==void 0&&r.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&r.push("query: query"),e.headers!==void 0&&r.push("headers: headers.asHeaderDict()"),n.push(w(1)+"public var pbRequest: PBRequest { PBRequest("+r.join(", ")+") }"),e.idempotent&&!e.sse){if(e.pathParams.some(a=>it(W(a))==="idempotent"))throw new Error(`operation ${JSON.stringify(e.operationID)}: the path parameter idempotent is the name the endpoint says it may be sent again by: rename it in the backend`);n.push(w(1)+"public var idempotent: Bool { true }")}return n.push("}"),n}function ag(e){let t=[];t.push("public nonisolated struct "+Zn(e.operationID)+": PBUploadEndpoint {"),t.push(w(1)+"public typealias Response = "+Ut(e.operationID)),t.push(w(1)+"public typealias Failure = "+Yn(e.operationID));for(let s of e.pathParams)t.push(w(1)+"let "+W(s)+": String");e.input!==void 0&&t.push(w(1)+"let input: "+Xn(e.operationID)),e.query!==void 0&&t.push(w(1)+"let query: "+gn(e.operationID));let n=[".post",Ul(xl(e))];return e.input!==void 0&&n.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&n.push("query: query"),t.push(w(1)+"public var authorizeRequest: PBRequest { PBRequest("+n.join(", ")+") }"),t.push("}"),t}function mn(e,t,n=!1,s=0){if(t.kind==="object")return Qn(e,t.props,s,n);let[r,a]=t.kind==="enum"?["String",[]]:Fn(t,e,s,n),i=[...a];return i.length>0&&i.push(""),i.push(w(s)+"public typealias "+e+" = "+r+(t.nullable?"?":"")),i}function ig(e,t){let n=Qn(e,t.props,0);n[n.length-1].trim()==="}"&&n.pop(),n.push(w(1)+"public func asHeaderDict() -> [String: String] {"),n.push(w(2)+"var out: [String: String] = [:]");for(let s of t.props){let r=W(s.name),a=ge(s.name),i=s.schema.kind==="enum";!s.required||s.schema.nullable?n.push(w(2)+"if let v = "+r+" { out["+a+"] = "+(i?"v.rawValue":"v")+" }"):n.push(w(2)+"out["+a+"] = "+r+(i?".rawValue":""))}return n.push(w(2)+"return out"),n.push(w(1)+"}"),n.push("}"),n}function og(e,t){let n=["public nonisolated enum "+e+": PBError {"],s=1,r=new Map([["other","the other(BackendError) fallback"]]),a=[];for(let i of t){let o=it(W(i.name)),l=r.get(ee(o));if(l!==void 0){n.push(w(s)+"#error("+ge("palbase: "+e+" declares error "+i.name+" ("+i.code+"), which maps to the same Swift case `"+o+"` as "+l+". Rename one of the errors in the backend and regenerate the client.")+")");continue}r.set(ee(o),i.name+" ("+i.code+")"),a.push(i)}for(let i of a)i.data!==void 0&&De(n,mn(Ee(i.name)+"Data",i.data,!1,s));for(let i of a)n.push(w(s)+"case "+W(i.name)+(i.data!==void 0?"("+Ee(i.name)+"Data)":""));if(n.push(w(s)+"case other(BackendError)"),a.length===0)return n.push(w(s)+"public nonisolated init(_ backend: BackendError) { self = .other(backend) }"),n.push("}"),n;n.push(w(s)+"public nonisolated init(_ backend: BackendError) {"),n.push(w(s+1)+"guard let f = backend.failure else { self = .other(backend); return }"),n.push(w(s+1)+"switch f.code {");for(let i of a){let o=W(i.name);i.data!==void 0?(n.push(w(s+1)+"case "+ge(i.code)+":"),n.push(w(s+2)+"if let data = f.decodeData("+Ee(i.name)+"Data.self) { self = ."+o+"(data) } else { self = .other(backend) }")):n.push(w(s+1)+"case "+ge(i.code)+": self = ."+o)}return n.push(w(s+1)+"default: self = .other(backend)"),n.push(w(s+1)+"}"),n.push(w(s)+"}"),n.push("}"),n}function Qn(e,t,n,s=!1){let r=[w(n)+"public nonisolated struct "+e+": Codable, Sendable {"],a=n+1,i=[],o=new Wn,l=new Map;for(let u of t){let p=W(u.name),f=l.get(ee(p));if(f!==void 0){r.push(w(a)+"#error("+ge("palbase: "+e+" has colliding Swift property `"+it(p)+"` for wire keys "+f+" and "+u.name+". This schema is unsupported; use distinct property names and regenerate the client.")+")");continue}l.set(ee(p),u.name);let y=!u.required||u.schema.nullable,[v,T]=Fn(u.schema,u.name,a,s,o);s&&!u.required&&u.schema.nullable&&(v="Nullable<"+v+">"),De(r,T),i.push({ident:p,key:u.name,typ:v,optional:y,required:u.required})}for(let u of i)r.push(w(a)+"public let "+u.ident+": "+u.typ+(u.optional?"?":""));let d=i.map(u=>u.ident+": "+u.typ+(u.optional?"? = nil":""));r.push(w(a)+"public init("+d.join(", ")+") {");for(let u of i)r.push(w(a+1)+"self."+u.ident+" = "+u.ident);if(r.push(w(a)+"}"),s){r.push(w(a)+"public func encode(to encoder: any Swift.Encoder) throws {"),r.push(w(a+1)+(i.length===0?"let":"var")+" fields: [Swift.String: Palbe.PBWireValue] = [:]");for(let u of i){let p="fields["+ge(u.key)+"] = Palbe.PBWireValue(";u.required?r.push(w(a+1)+p+"self."+u.ident+")"):r.push(w(a+1)+"if let value = self."+u.ident+" { "+p+"value) }")}r.push(w(a+1)+"var container = encoder.singleValueContainer()"),r.push(w(a+1)+"try container.encode(fields)"),r.push(w(a)+"}")}if(i.some(u=>!Cl(it(u.ident),u.key))){r.push(w(a)+"enum CodingKeys: String, CodingKey {");for(let u of i)Cl(it(u.ident),u.key)?r.push(w(a+1)+"case "+u.ident):r.push(w(a+1)+"case "+u.ident+" = "+ge(u.key));r.push(w(a)+"}")}return r.push(w(n)+"}"),r}var Wn=class{used=new Set(["Swift","Palbe","Foundation","String","Int","Double","Bool","Encoder","Decoder","Encodable","Decodable","Codable","Sendable","CodingKey","CodingKeys","AnyCodableValue","Nullable","PBWireValue"]);allocate(t){let n=t;for(let s=2;this.used.has(ee(n));s++)n=t+s;return this.used.add(ee(n)),n}};function Fn(e,t,n,s=!1,r=new Wn){switch(e.kind){case"string":return["String",[]];case"number":return["Double",[]];case"integer":return["Int",[]];case"boolean":return["Bool",[]];case"any":return["AnyCodableValue",[]];case"enum":{let a=r.allocate(Ee(t)+"Value"),i=[w(n)+"public nonisolated enum "+a+": String, Codable, Sendable {"];for(let o of e.enumVals)i.push(w(n+1)+"case "+W(o)+" = "+ge(o));return i.push(w(n)+"}"),[a,i]}case"array":{let a=e.elem,[i,o]=Fn(a,t+"Item",n,s,r);return["["+i+(a.nullable?"?":"")+"]",o]}case"map":{let a=e.elem,[i,o]=Fn(a,t+"Value",n,s,r);return["[String: "+i+(a.nullable?"?":"")+"]",o]}case"mixedObject":return["AnyCodableValue",[w(n)+"#error("+ge("palbase: "+t+" combines declared properties with additionalProperties. This mixed object schema is unsupported; use a separate typed map field and regenerate the client.")+")"]];case"object":{if(tg.has(Ee(t)))throw new Error(`the name ${JSON.stringify(t)} becomes the type ${Ee(t)}, a name Swift gives no type of a program's: rename it in the backend`);let a=r.allocate(Ee(t));return[a,Qn(a,e.props,n,s)]}default:return["AnyCodableValue",[]]}}function lg(e,t){let n={path:[],children:new Map,methods:[]};for(let a of e){let i=Hn(a.operationID),o=n;for(let l of i.slice(0,Math.max(i.length-1,0))){let d=o.children.get(ee(l));d===void 0&&(d=[l,{path:[...o.path,l],children:new Map,methods:[]}],o.children.set(ee(l),d)),o=d[1]}o.methods.push(a)}let s=[],r=[n];for(;r.length>0;){let a=r.shift();s.push(a),r.push(...Kl(a).map(([,i])=>i))}return s.map(a=>dg(a,t)+`
553
- `).join("")}var Kl=e=>[...e.children.values()].sort((t,n)=>Ve(t[0],n[0]));function br(e,t){return e.length===0?t:"PB"+e.map(Ee).join("")+"Namespace"}function dg(e,t){let n=e.path.length===0,s=[];n?s.push("public extension "+t+" {"):(s.push("public nonisolated struct "+br(e.path,t)+": Sendable {"),s.push(" let _pb: PalBackendClient"));let r=n?"":"public ",a="_pb";for(let[,i]of Kl(e)){let o=br(i.path,t);s.push(w(1)+r+"nonisolated var "+W(i.path[i.path.length-1])+": "+o+" { "+o+"(_pb: "+a+") }")}for(let i of e.methods){let o=Hn(i.operationID);if(o.length===0)throw new Error(`operationId ${JSON.stringify(i.operationID)} names no method`);let l=W(o[o.length-1]),d=Xn(i.operationID),u=Ut(i.operationID),p="throws("+Yn(i.operationID)+")";if(i.upload!==void 0){De(s,cg(i,l,d,u,p,r,a));continue}let f=i.pathParams.map(T=>W(T)+": String");i.input!==void 0&&f.push("_ input: "+d),i.query!==void 0&&f.push("query: "+gn(i.operationID)),i.headers!==void 0&&f.push("headers: "+_r(i.operationID));let y=i.pathParams.map(T=>W(T)+": "+W(T));i.input!==void 0&&y.push("input: input"),i.query!==void 0&&y.push("query: query"),i.headers!==void 0&&y.push("headers: headers");let v=Zn(i.operationID)+"("+y.join(", ")+")";i.sse?(s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async throws -> AsyncThrowingStream<"+u+", Error> {"),s.push(w(2)+"return try await "+a+".stream("+v+")")):i.output!==void 0?(s.push(w(1)+"@discardableResult"),s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async "+p+" -> "+u+" {"),s.push(w(2)+"return try await "+a+".call("+v+")")):(s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async "+p+" {"),s.push(w(2)+"try await "+a+".call("+v+")")),s.push(w(1)+"}")}return s.push("}"),s.join(`
550
+ `}return i}var Em=new Set("associatedtype borrowing class consuming deinit enum extension fileprivate func import init inout internal let operator precedencegroup private protocol public rethrows static struct subscript typealias var break case catch continue default defer do else fallthrough for guard if in repeat return throw switch where while as await false is nil self super throws true try".split(" "));function Cm(e){if(!/^[a-z][A-Za-z0-9]*$/.test(e.client)||Em.has(e.client))throw new Error(`client ${JSON.stringify(e.client)} is not a lowerCamel Swift identifier`);for(let t of e.header)if(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/.test(t))throw new Error(`header line ${JSON.stringify(t)} would end its comment, or read as other than it is`)}function zn(e,t){let n=kl.get(t);if(n!==void 0)throw new Error(`${e} declares the type ${t}, which in the app would stand in for ${n}'s ${t}: rename it in the backend`)}function jm(e,t,n){zn(`client ${JSON.stringify(t)}`,n);for(let s of e){let r=Hn(s.operationID);for(let a=1;a<r.length;a++)zn(`operation ${JSON.stringify(s.operationID)}, in namespace ${JSON.stringify(r.slice(0,a).join("."))},`,br(r.slice(0,a),n))}}function Om(e){let t='"';for(let n of e){let s=n.codePointAt(0);if(s>=55296&&s<=57343)throw new Error(`machine ${JSON.stringify(e)} is not a Swift string`);n==='"'||n==="\\"?t+="\\"+n:s>=32&&s<127?t+=n:t+="\\u{"+s.toString(16)+"}"}return t+'"'}var Dm=new Intl.Segmenter("en",{granularity:"grapheme"});function Le(e){return Array.from(Dm.segment(e),t=>t.segment)}function ee(e){return e.normalize("NFC")}function ne(e,t){return ee(e)===ee(t)}function Ve(e,t){let n=Array.from(ee(e)),s=Array.from(ee(t));for(let r=0;r<n.length&&r<s.length;r++){let a=n[r].codePointAt(0)-s[r].codePointAt(0);if(a!==0)return a}return n.length-s.length}var Ol=e=>[...e].sort(Ve);function wt(e,t,n){let s=[],r="";for(let a of Le(e))ne(a,t)?((!n||r!=="")&&s.push(r),r=""):r+=a;return(!n||r!=="")&&s.push(r),s}function xt(e,t,n){let s=Le(e),r=Le(t),a="";for(let i=0;i<s.length;)i+r.length<=s.length&&r.every((o,l)=>ne(s[i+l],o))?(a+=n,i+=r.length):a+=s[i++];return a}var $l=new WeakMap;function wr(e){let t=$l.get(e);if(t===void 0){t=new Map;for(let n of Object.keys(e))e[n]!==void 0&&(t.delete(ee(n)),t.set(ee(n),[n,e[n]]));$l.set(e,t)}return t}var E=(e,t)=>wr(e).get(ee(t))?.[1],ot=(e,t)=>wr(e).has(ee(t)),He=e=>[...wr(e).values()],vr=e=>He(e).map(([t])=>t);function N(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0}function Ie(e){return Array.isArray(e)?e:void 0}function pe(e){return typeof e=="string"?e:void 0}function Vn(e){if(typeof e=="boolean")return e;if(e===1)return!0;if(e===0)return!1}function Im(e){if(typeof e=="number")return e;if(typeof e=="boolean")return e?1:0}var bt=(e,t=!1)=>({kind:e,nullable:t,props:[],enumVals:[]}),Dl=2n**63n-1n,Il=-(2n**63n);function Lm(e){return Number.isNaN(e)?0n:e>=2**63?Dl:e<=-(2**63)?Il:BigInt(Math.trunc(e))}function Nm(e,t=new Ue){let n=N(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=N(E(n,"paths"));if(s===void 0)throw new Error("openapi.json has no `paths`");let r=[];for(let[a,i]of He(s)){let o=N(i);if(o!==void 0)for(let[l,p]of He(o)){let u=N(p);if(u===void 0)continue;let d=pe(E(u,"operationId"))??"";if(d==="")continue;let f={where:`operation ${JSON.stringify(d)}`,budget:t,through:d.length,path:new Set};r.push({operationID:d,method:l.toUpperCase(),path:a,pathParams:Gm(a),input:Um(u,f),output:Bm(u,f),headers:Pl(u,"header",f),query:Pl(u,"query",f),errors:qm(u,f),upload:xm(u),sse:ot(u,"x-palbase-sse"),idempotent:Km(u)})}}return r.sort((a,i)=>Ve(a.operationID,i.operationID)),r}function qm(e,t){let n=N(E(e,"x-palbase-errors"));if(n===void 0)return[];let s=N(E(e,"responses")),r=[];for(let[a,i]of He(n)){let o=N(i);if(o===void 0)continue;let l=Im(E(o,"status"))??0,p=pe(E(o,"code"))??"",u=pe(E(o,"description"))??"",d=Vn(E(o,"hasData"))??!1;if(p===""||l===0)continue;let f={name:a,code:p,status:Lm(l),description:u};d&&(f.data=Mm(s,f.status,p,{...t,through:t.through+a.length})),r.push(f)}return r.sort((a,i)=>Ve(a.name,i.name)),r}function xm(e){let t=N(E(e,"x-palbase-upload"));if(t===void 0||He(t).length===0)return;let n=pe(E(t,"bucket"))??"",s=pe(E(t,"pathTemplate"))??"";if(!(n===""||s===""))return{bucket:n,pathTemplate:s}}function Mm(e,t,n,s){if(e===void 0)return;let r=N(E(e,t.toString()));if(r===void 0)return;let a=N(E(r,"content"));if(a===void 0)return;let i=N(E(a,"application/json"));if(i===void 0)return;let o=N(E(i,"schema"));if(o===void 0)return;let l=Ie(E(o,"oneOf"));if(l!==void 0){for(let p of l){let u=N(p);if(u===void 0)continue;let d=N(E(u,"properties")),f=d===void 0?void 0:N(E(d,"error"));if(f===void 0)continue;let y=pe(E(f,"const"));if(y!==void 0&&ne(y,n))return Al(u,s)}return}return Al(o,s)}function Al(e,t){let n=N(E(e,"properties"));if(n===void 0)return;let s=N(E(n,"data"));if(s!==void 0)return lt(s,s,t)}function Um(e,t){let n=N(E(e,"requestBody"));if(n!==void 0)return Nl(E(n,"content"),t)}var Ll=e=>ee(e).toLowerCase()==="idempotency-key";function Km(e){return(Ie(E(e,"parameters"))??[]).some(t=>{let n=N(t);return n!==void 0&&ne(pe(E(n,"in"))??"","header")&&Ll(pe(E(n,"name"))??"")})}function Pl(e,t,n){let s=Ie(E(e,"parameters"));if(s===void 0||s.length===0)return;let r=[];for(let a of s){let i=N(a);if(i===void 0||!ne(pe(E(i,"in"))??"",t))continue;let o=pe(E(i,"name"))??"";if(o===""||t==="header"&&Ll(o))continue;let l=Vn(E(i,"required"))??!1,p=N(E(i,"schema"));r.push({name:o,schema:p===void 0?bt("string"):lt(p,p,{...n,through:n.through+o.length}),required:l})}if(r.length!==0)return r.sort((a,i)=>Ve(a.name,i.name)),{kind:"object",nullable:!1,props:r,enumVals:[]}}function Gm(e){let t=[],n=e;for(;;){let s=n.indexOf("{");if(s<0)break;let r=n.indexOf("}",s);if(r<0)break;let a=n.slice(s+1,r);a!==""&&t.push(a),n=n.slice(r+1)}return t}function Bm(e,t){let n=N(E(e,"responses"));if(n===void 0)return;let s=vr(n).filter(r=>ne(Le(r)[0]??"","2")&&!ne(r,"200")&&!ne(r,"201"));for(let r of["200","201",...Ol(s)]){let a=N(E(n,r));if(a===void 0)continue;let i=Nl(E(a,"content"),t);if(i!==void 0)return i}}function Nl(e,t){let n=N(e);if(n===void 0)return;let s=N(E(n,"application/json"));if(s===void 0)return;let r=N(E(s,"schema"));if(r!==void 0&&!ot(r,"$ref"))return lt(r,r,t)}function lt(e,t,n,s=0,r=n.through){let a=e,i=Wm(a,t);if(i!==void 0&&s<16){if(n.path.has(i))throw new Error(`${n.where}: the $ref ${JSON.stringify(pe(E(a,"$ref")))} points back into a schema it is part of, a recursive schema, which no Swift type is written for: refused`);a=i}else if(ot(a,"$ref"))return bt("any");let o=N(E(a,"properties")),l=(o===void 0?0:Rl(vr(o)))+Rl(Ie(E(a,"enum"))??[]);n.budget.charge(n.where,s,r,l),n.path.add(a);try{return Jm(a,t,n,s,r)}finally{n.path.delete(a)}}var Rl=e=>e.reduce((t,n)=>t+(pe(n)?.length??0),0);function Jm(e,t,n,s,r){let a=Vn(E(e,"nullable"))??!1,i=Ie(E(e,"type"));if(i!==void 0)for(let u of i)pe(u)==="null"&&(a=!0);let o=Ie(E(e,"anyOf"))??Ie(E(e,"oneOf"));if(o!==void 0){let u=o.map(N).filter(f=>f!==void 0),d=u.filter(f=>!zm(f));if(u.length===o.length&&d.length===1&&u.length>d.length){let f=lt(d[0],t,n,s+1,r);return f.nullable=!0,f}}let l=Ie(E(e,"enum"));if(l!==void 0){let u=[],d=!0;for(let f of l){let y=pe(f);if(y===void 0){d=!1;break}u.push(y)}if(d&&u.length>0)return{kind:"enum",nullable:a,props:[],enumVals:u}}let p=pe(E(e,"type"))??"";if(p===""&&i!==void 0)for(let u of i){let d=pe(u);d!==void 0&&(d==="null"?a=!0:p===""&&(p=d))}switch(p){case"string":case"number":case"integer":case"boolean":return bt(p,a);case"array":{let u=N(E(e,"items"));return{kind:"array",nullable:a,props:[],elem:u===void 0?bt("any"):lt(u,t,n,s+1,r),enumVals:[]}}case"object":return Tl(e,a,t,n,s,r);default:return ot(e,"properties")||ot(e,"additionalProperties")?Tl(e,a,t,n,s,r):bt("any",a)}}function Tl(e,t,n,s,r,a){let i=N(E(e,"properties"))??{},o=E(e,"additionalProperties"),l,p=N(o);if(p!==void 0?l=lt(p,n,s,r+1,a):(Vn(o)===!0||He(i).length===0&&!ot(e,"additionalProperties"))&&(l=bt("any")),l!==void 0)return{kind:He(i).length===0?"map":"mixedObject",nullable:t,props:[],elem:l,enumVals:[]};let u=new Set;for(let f of Ie(E(e,"required"))??[]){let y=pe(f);y!==void 0&&u.add(ee(y))}let d=[];for(let f of Ol(vr(i))){let y=N(E(i,f));d.push({name:f,schema:y===void 0?bt("any"):lt(y,n,s,r+1,a+f.length),required:u.has(ee(f))})}return{kind:"object",nullable:t,props:d,enumVals:[]}}function zm(e){let t=E(e,"type");if(typeof t=="string")return t==="null";let n=Ie(t);if(n!==void 0){let s=n.filter(r=>typeof r=="string");return s.length===1&&s[0]==="null"}return!1}function Wm(e,t){let n=pe(E(e,"$ref"));if(n===void 0)return;let s=Le(n);if(!ne(s[0]??"","#")||!ne(s[1]??"","/"))return;let r=t;for(let a of wt(s.slice(2).join(""),"/",!0)){let i=xt(xt(a,"~1","/"),"~0","~"),o=N(r),l=Ie(r),p=Xm(i);if(o!==void 0&&ot(o,i))r=E(o,i);else if(l!==void 0&&p!==void 0&&p>=0n&&p<BigInt(l.length))r=l[Number(p)];else return}return N(r)}function Fm(e,t=new Ue){let n=N(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=N(E(n,"x-palbase-room"));if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=N(i);if(o===void 0)continue;let l={where:`room ${JSON.stringify(a)}`,budget:t,through:a.length,path:new Set};r.push({pattern:a,params:Vm(a),events:El(E(o,"events"),n,l),messages:El(E(o,"messages"),n,l)})}return r.sort((a,i)=>Ve(a.pattern,i.pattern)),r}function ql(e){let t=Le(e);return t.length>2&&ne(t[0],"{")&&ne(t[t.length-1],"}")?t.slice(1,-1).join(""):void 0}function Vm(e){return wt(e,":",!1).flatMap(t=>ql(t)??[])}function El(e,t,n){let s=N(e);if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=N(i);if(o===void 0)continue;let l=Hm(o,t)??o;r.push({name:a,schema:lt(l,l,{...n,through:n.through+a.length})})}return r.sort((a,i)=>Ve(a.name,i.name)),r}function Hm(e,t){let n=pe(E(e,"$ref"));if(n===void 0)return;let s=Le(n);if(!ne(s[0]??"","#")||!ne(s[1]??"","/"))return;let r=t;for(let a of wt(s.slice(2).join(""),"/",!0)){let i=xt(xt(a,"~1","/"),"~0","~"),o=N(r);if(o===void 0||!ot(o,i))return;r=E(o,i)}return N(r)}function Xm(e){if(!/^[+-]?[0-9]+$/.test(e))return;let t=BigInt(e);return t>Dl||t<Il?void 0:t}function ge(e){let t='"';for(let n of e){let s=n.codePointAt(0);if(n==='"')t+='\\"';else if(n==="\\")t+="\\\\";else if(n===`
551
+ `)t+="\\n";else if(n==="\r")t+="\\r";else if(n===" ")t+="\\t";else{if(s>=55296&&s<=57343)throw new Error(`${JSON.stringify(e)} is not a Swift string: it carries a lone surrogate`);s<32||s>=127&&s<=159||s===8232||s===8233||Ym.has(s)?t+="\\u{"+s.toString(16)+"}":t+=n}}return t+'"'}var Ym=new Set([1564,8206,8207,8234,8235,8236,8237,8238,8294,8295,8296,8297]),Hn=e=>wt(e,".",!0),Ee=e=>xl(e,!0);function W(e){let t=xl(e,!1);if(eg.has(t))throw new Error(`the name ${JSON.stringify(e)} becomes the Swift keyword ${t}, which v2 did not put in backticks and Swift takes as no name: rename it in the backend`);return ng(t)}var Ut=e=>Hn(e).map(Ee).join(""),Xn=e=>Ut(e)+"Request",Mt=e=>Ut(e)+"Response",Yn=e=>Ut(e)+"Error",_r=e=>Ut(e)+"Headers",gn=e=>Ut(e)+"Query";function Zm(e){let t=(n,s)=>Ve(e,n)>=0&&Ve(e,s)<=0;return t("a","z")||t("A","Z")||t("0","9")}function xl(e,t){let n=[],s=[];for(let i of Le(e))Zm(i)?s.push(i):s.length>0&&(n.push(s),s=[]);if(s.length>0&&n.push(s),n.length===0)return t?"Op":"op";let r="";n.forEach((i,o)=>{r+=(o===0&&!t?i[0].toLowerCase():i[0].toUpperCase())+i.slice(1).join("")});let a=r.codePointAt(0);if(a>=48&&a<=57&&(r="_"+r),!/^[A-Za-z0-9_]+$/.test(r))throw new Error(`the name ${JSON.stringify(e)} becomes ${JSON.stringify(r)} in Swift, which is more than A-Z, a-z, 0-9 and _: rename it in the backend`);return r}var Qm=new Set(["class","struct","enum","func","let","var","return","if","else","for","while","switch","case","default","import","public","private","internal","protocol","extension","self","init","deinit","in","where","as","is","try","throw","throws","async","await","nil","true","false","guard","defer","do","catch","repeat","operator","associatedtype","any"]),eg=new Set(["break","continue","fallthrough","fileprivate","inout","precedencegroup","rethrows","static","subscript","super","typealias"]),tg=new Set(["Any","Self","Type","Protocol"]);function ng(e){return Qm.has(ee(e))?"`"+e+"`":e}var it=e=>xt(e,"`","");function Cl(e,t){if(ne(e,t))return!0;let n=Le(t);if(!n.some(l=>ne(l,"_")))return!1;let s=0;for(;s<n.length&&ne(n[s],"_");)s++;let r=0;for(;r<n.length-s&&ne(n[n.length-1-r],"_");)r++;let a=n.slice(s,n.length-r).join("");if(a==="")return!1;let i=n.slice(0,s).join(""),o=wt(a,"_",!1);for(let l=0;l<o.length;l++){if(o[l]==="")return!1;if(l===0)i+=o[l].toLowerCase();else{let p=Le(o[l]);i+=p[0].toUpperCase()+p.slice(1).join("").toLowerCase()}}return i+=n.slice(n.length-r).join(""),ne(i,e)}var w=e=>" ".repeat(e);function De(e,t){for(let n of t)e.push(n)}function sg(e){let t=[];for(let n of e){let s=t.length,r=!1;if(n.input!==void 0&&(De(t,mn(Xn(n.operationID),n.input,!0)),r=!0),n.output!==void 0?(r&&t.push(""),De(t,mn(Mt(n.operationID),n.output)),r=!0):n.upload!==void 0&&(r&&t.push(""),t.push("public nonisolated struct "+Mt(n.operationID)+": Codable, Sendable {"),t.push("}"),r=!0),n.headers!==void 0){for(let a of n.headers.props)if(!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(a.name))throw new Error(`operation ${JSON.stringify(n.operationID)}: the header ${JSON.stringify(a.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);r&&t.push(""),De(t,ig(_r(n.operationID),n.headers)),r=!0}n.query!==void 0&&(r&&t.push(""),De(t,Qn(gn(n.operationID),n.query.props,0)),r=!0),r&&t.push(""),De(t,og(Yn(n.operationID),n.errors)),t.push(""),De(t,rg(n)),t.push("");for(let a of t.slice(s)){let i=/^public (?:nonisolated )?(?:struct|enum|typealias) ([A-Za-z0-9_]+)/.exec(a);i!==null&&zn(`operation ${JSON.stringify(n.operationID)}`,i[1])}}return t.join(`
552
+ `)}var Zn=e=>Ut(e)+"Endpoint";function Ml(e){let t=[];for(let n of wt(e,"/",!1)){if(n==="")continue;let s=Le(n);if(s.length>=2&&ne(s[0],"{")&&ne(s[s.length-1],"}")){let r=s.slice(1,s.length-1).join("");if(r==="")continue;t.push(".param("+W(r)+")")}else t.push(ge(n))}return"["+t.join(", ")+"]"}function Ul(e){return e.path===""?"/"+xt(e.operationID,".","/"):e.path}function rg(e){if(e.upload!==void 0)return ag(e);let t=Zn(e.operationID),n=[];e.sse?(n.push("public nonisolated struct "+t+": PBSseEndpoint {"),n.push(w(1)+"public typealias Element = "+Mt(e.operationID))):e.output!==void 0?(n.push("public nonisolated struct "+t+": PBEndpoint {"),n.push(w(1)+"public typealias Response = "+Mt(e.operationID))):n.push("public nonisolated struct "+t+": PBVoidEndpoint {"),n.push(w(1)+"public typealias Failure = "+Yn(e.operationID));for(let a of e.pathParams)n.push(w(1)+"let "+W(a)+": String");e.input!==void 0&&n.push(w(1)+"let input: "+Xn(e.operationID)),e.query!==void 0&&n.push(w(1)+"let query: "+gn(e.operationID)),e.headers!==void 0&&n.push(w(1)+"let headers: "+_r(e.operationID));let s=e.method===""?"POST":e.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Error(`operation ${JSON.stringify(e.operationID)}: the method ${JSON.stringify(s)} is not a Swift name`);let r=["."+s.toLowerCase(),Ml(Ul(e))];if(e.input!==void 0&&r.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&r.push("query: query"),e.headers!==void 0&&r.push("headers: headers.asHeaderDict()"),n.push(w(1)+"public var pbRequest: PBRequest { PBRequest("+r.join(", ")+") }"),e.idempotent&&!e.sse){if(e.pathParams.some(a=>it(W(a))==="idempotent"))throw new Error(`operation ${JSON.stringify(e.operationID)}: the path parameter idempotent is the name the endpoint says it may be sent again by: rename it in the backend`);n.push(w(1)+"public var idempotent: Bool { true }")}return n.push("}"),n}function ag(e){let t=[];t.push("public nonisolated struct "+Zn(e.operationID)+": PBUploadEndpoint {"),t.push(w(1)+"public typealias Response = "+Mt(e.operationID)),t.push(w(1)+"public typealias Failure = "+Yn(e.operationID));for(let s of e.pathParams)t.push(w(1)+"let "+W(s)+": String");e.input!==void 0&&t.push(w(1)+"let input: "+Xn(e.operationID)),e.query!==void 0&&t.push(w(1)+"let query: "+gn(e.operationID));let n=[".post",Ml(Ul(e))];return e.input!==void 0&&n.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&n.push("query: query"),t.push(w(1)+"public var authorizeRequest: PBRequest { PBRequest("+n.join(", ")+") }"),t.push("}"),t}function mn(e,t,n=!1,s=0){if(t.kind==="object")return Qn(e,t.props,s,n);let[r,a]=t.kind==="enum"?["String",[]]:Fn(t,e,s,n),i=[...a];return i.length>0&&i.push(""),i.push(w(s)+"public typealias "+e+" = "+r+(t.nullable?"?":"")),i}function ig(e,t){let n=Qn(e,t.props,0);n[n.length-1].trim()==="}"&&n.pop(),n.push(w(1)+"public func asHeaderDict() -> [String: String] {"),n.push(w(2)+"var out: [String: String] = [:]");for(let s of t.props){let r=W(s.name),a=ge(s.name),i=s.schema.kind==="enum";!s.required||s.schema.nullable?n.push(w(2)+"if let v = "+r+" { out["+a+"] = "+(i?"v.rawValue":"v")+" }"):n.push(w(2)+"out["+a+"] = "+r+(i?".rawValue":""))}return n.push(w(2)+"return out"),n.push(w(1)+"}"),n.push("}"),n}function og(e,t){let n=["public nonisolated enum "+e+": PBError {"],s=1,r=new Map([["other","the other(BackendError) fallback"]]),a=[];for(let i of t){let o=it(W(i.name)),l=r.get(ee(o));if(l!==void 0){n.push(w(s)+"#error("+ge("palbase: "+e+" declares error "+i.name+" ("+i.code+"), which maps to the same Swift case `"+o+"` as "+l+". Rename one of the errors in the backend and regenerate the client.")+")");continue}r.set(ee(o),i.name+" ("+i.code+")"),a.push(i)}for(let i of a)i.data!==void 0&&De(n,mn(Ee(i.name)+"Data",i.data,!1,s));for(let i of a)n.push(w(s)+"case "+W(i.name)+(i.data!==void 0?"("+Ee(i.name)+"Data)":""));if(n.push(w(s)+"case other(BackendError)"),a.length===0)return n.push(w(s)+"public nonisolated init(_ backend: BackendError) { self = .other(backend) }"),n.push("}"),n;n.push(w(s)+"public nonisolated init(_ backend: BackendError) {"),n.push(w(s+1)+"guard let f = backend.failure else { self = .other(backend); return }"),n.push(w(s+1)+"switch f.code {");for(let i of a){let o=W(i.name);i.data!==void 0?(n.push(w(s+1)+"case "+ge(i.code)+":"),n.push(w(s+2)+"if let data = f.decodeData("+Ee(i.name)+"Data.self) { self = ."+o+"(data) } else { self = .other(backend) }")):n.push(w(s+1)+"case "+ge(i.code)+": self = ."+o)}return n.push(w(s+1)+"default: self = .other(backend)"),n.push(w(s+1)+"}"),n.push(w(s)+"}"),n.push("}"),n}function Qn(e,t,n,s=!1){let r=[w(n)+"public nonisolated struct "+e+": Codable, Sendable {"],a=n+1,i=[],o=new Wn,l=new Map;for(let u of t){let d=W(u.name),f=l.get(ee(d));if(f!==void 0){r.push(w(a)+"#error("+ge("palbase: "+e+" has colliding Swift property `"+it(d)+"` for wire keys "+f+" and "+u.name+". This schema is unsupported; use distinct property names and regenerate the client.")+")");continue}l.set(ee(d),u.name);let y=!u.required||u.schema.nullable,[v,T]=Fn(u.schema,u.name,a,s,o);s&&!u.required&&u.schema.nullable&&(v="Nullable<"+v+">"),De(r,T),i.push({ident:d,key:u.name,typ:v,optional:y,required:u.required})}for(let u of i)r.push(w(a)+"public let "+u.ident+": "+u.typ+(u.optional?"?":""));let p=i.map(u=>u.ident+": "+u.typ+(u.optional?"? = nil":""));r.push(w(a)+"public init("+p.join(", ")+") {");for(let u of i)r.push(w(a+1)+"self."+u.ident+" = "+u.ident);if(r.push(w(a)+"}"),s){r.push(w(a)+"public func encode(to encoder: any Swift.Encoder) throws {"),r.push(w(a+1)+(i.length===0?"let":"var")+" fields: [Swift.String: Palbe.PBWireValue] = [:]");for(let u of i){let d="fields["+ge(u.key)+"] = Palbe.PBWireValue(";u.required?r.push(w(a+1)+d+"self."+u.ident+")"):r.push(w(a+1)+"if let value = self."+u.ident+" { "+d+"value) }")}r.push(w(a+1)+"var container = encoder.singleValueContainer()"),r.push(w(a+1)+"try container.encode(fields)"),r.push(w(a)+"}")}if(i.some(u=>!Cl(it(u.ident),u.key))){r.push(w(a)+"enum CodingKeys: String, CodingKey {");for(let u of i)Cl(it(u.ident),u.key)?r.push(w(a+1)+"case "+u.ident):r.push(w(a+1)+"case "+u.ident+" = "+ge(u.key));r.push(w(a)+"}")}return r.push(w(n)+"}"),r}var Wn=class{used=new Set(["Swift","Palbe","Foundation","String","Int","Double","Bool","Encoder","Decoder","Encodable","Decodable","Codable","Sendable","CodingKey","CodingKeys","AnyCodableValue","Nullable","PBWireValue"]);allocate(t){let n=t;for(let s=2;this.used.has(ee(n));s++)n=t+s;return this.used.add(ee(n)),n}};function Fn(e,t,n,s=!1,r=new Wn){switch(e.kind){case"string":return["String",[]];case"number":return["Double",[]];case"integer":return["Int",[]];case"boolean":return["Bool",[]];case"any":return["AnyCodableValue",[]];case"enum":{let a=r.allocate(Ee(t)+"Value"),i=[w(n)+"public nonisolated enum "+a+": String, Codable, Sendable {"];for(let o of e.enumVals)i.push(w(n+1)+"case "+W(o)+" = "+ge(o));return i.push(w(n)+"}"),[a,i]}case"array":{let a=e.elem,[i,o]=Fn(a,t+"Item",n,s,r);return["["+i+(a.nullable?"?":"")+"]",o]}case"map":{let a=e.elem,[i,o]=Fn(a,t+"Value",n,s,r);return["[String: "+i+(a.nullable?"?":"")+"]",o]}case"mixedObject":return["AnyCodableValue",[w(n)+"#error("+ge("palbase: "+t+" combines declared properties with additionalProperties. This mixed object schema is unsupported; use a separate typed map field and regenerate the client.")+")"]];case"object":{if(tg.has(Ee(t)))throw new Error(`the name ${JSON.stringify(t)} becomes the type ${Ee(t)}, a name Swift gives no type of a program's: rename it in the backend`);let a=r.allocate(Ee(t));return[a,Qn(a,e.props,n,s)]}default:return["AnyCodableValue",[]]}}function lg(e,t){let n={path:[],children:new Map,methods:[]};for(let a of e){let i=Hn(a.operationID),o=n;for(let l of i.slice(0,Math.max(i.length-1,0))){let p=o.children.get(ee(l));p===void 0&&(p=[l,{path:[...o.path,l],children:new Map,methods:[]}],o.children.set(ee(l),p)),o=p[1]}o.methods.push(a)}let s=[],r=[n];for(;r.length>0;){let a=r.shift();s.push(a),r.push(...Kl(a).map(([,i])=>i))}return s.map(a=>pg(a,t)+`
553
+ `).join("")}var Kl=e=>[...e.children.values()].sort((t,n)=>Ve(t[0],n[0]));function br(e,t){return e.length===0?t:"PB"+e.map(Ee).join("")+"Namespace"}function pg(e,t){let n=e.path.length===0,s=[];n?s.push("public extension "+t+" {"):(s.push("public nonisolated struct "+br(e.path,t)+": Sendable {"),s.push(" let _pb: PalBackendClient"));let r=n?"":"public ",a="_pb";for(let[,i]of Kl(e)){let o=br(i.path,t);s.push(w(1)+r+"nonisolated var "+W(i.path[i.path.length-1])+": "+o+" { "+o+"(_pb: "+a+") }")}for(let i of e.methods){let o=Hn(i.operationID);if(o.length===0)throw new Error(`operationId ${JSON.stringify(i.operationID)} names no method`);let l=W(o[o.length-1]),p=Xn(i.operationID),u=Mt(i.operationID),d="throws("+Yn(i.operationID)+")";if(i.upload!==void 0){De(s,cg(i,l,p,u,d,r,a));continue}let f=i.pathParams.map(T=>W(T)+": String");i.input!==void 0&&f.push("_ input: "+p),i.query!==void 0&&f.push("query: "+gn(i.operationID)),i.headers!==void 0&&f.push("headers: "+_r(i.operationID));let y=i.pathParams.map(T=>W(T)+": "+W(T));i.input!==void 0&&y.push("input: input"),i.query!==void 0&&y.push("query: query"),i.headers!==void 0&&y.push("headers: headers");let v=Zn(i.operationID)+"("+y.join(", ")+")";i.sse?(s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async throws -> AsyncThrowingStream<"+u+", Error> {"),s.push(w(2)+"return try await "+a+".stream("+v+")")):i.output!==void 0?(s.push(w(1)+"@discardableResult"),s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async "+d+" -> "+u+" {"),s.push(w(2)+"return try await "+a+".call("+v+")")):(s.push(w(1)+r+"func "+l+"("+f.join(", ")+") async "+d+" {"),s.push(w(2)+"try await "+a+".call("+v+")")),s.push(w(1)+"}")}return s.push("}"),s.join(`
554
554
  `)+`
555
- `}var pg=e=>Ee(e)+"Room",qt=(e,t)=>Ee(e)+t;function Jn(e,t,n){let s=e.get(ee(t));if(s!==void 0)throw new Error(`${s} and ${n} both become ${t} in Swift: rename one in the backend`);e.set(ee(t),n)}function ug(e,t){let n=pg(e.pattern),s=`room ${JSON.stringify(e.pattern)}`;Jn(t,n,s),zn(s,n);let r=new Map([["topic","the room's topic"]]);for(let o of e.params){let l=it(W(o));if(Jn(r,l,`${s}'s parameter ${JSON.stringify(o)}`),l==="self"||l==="init")throw new Error(`${s}'s parameter ${JSON.stringify(o)} becomes ${l} in Swift, which the room's initializer cannot set: rename it in the backend`)}for(let[o,l]of[["Event",e.events],["Message",e.messages]]){let d=new Map;for(let u of l)Jn(d,it(W(u.name)),`${s}'s ${o.toLowerCase()} ${JSON.stringify(u.name)}`),Jn(r,qt(u.name,o),`${s}'s ${o.toLowerCase()} ${JSON.stringify(u.name)}`)}let a=[];a.push("public nonisolated struct "+n+": PBRoom {"),a.push(w(1)+"public static let pattern = "+ge(e.pattern));for(let o of e.params)a.push(w(1)+"public let "+W(o)+": String");if(e.params.length===0)a.push(w(1)+"public init() {}");else{a.push(w(1)+"public init("+e.params.map(o=>W(o)+": String").join(", ")+") {");for(let o of e.params)a.push(w(2)+"self."+it(W(o))+" = "+W(o));a.push(w(1)+"}")}a.push(w(1)+"public var topic: String {");let i="";for(let o of wt(e.pattern,":",!1)){i!==""&&(i+=":");let l=ql(o);i+=l!==void 0?"\\("+W(l)+")":ge(o).slice(1,-1)}a.push(w(2)+'return "'+i+'"'),a.push(w(1)+"}"),a.push("");for(let o of e.events)De(a,mn(qt(o.name,"Event"),o.schema,!1,1));a.push(w(1)+"public enum Event: Sendable {");for(let o of e.events)a.push(w(2)+"case "+W(o.name)+"("+qt(o.name,"Event")+")");if(e.events.length===0&&a.push(w(2)+"// this room declares no events"),a.push(w(1)+"}"),a.push(""),a.push(w(1)+"public static let eventNames: [String] = ["+e.events.map(o=>ge(o.name)).join(", ")+"]"),a.push(""),a.push(w(1)+"public static func decode(event: String, data: Data) throws -> Event? {"),e.events.length===0)a.push(w(2)+"_ = (event, data)"),a.push(w(2)+"return nil");else{a.push(w(2)+"switch event {");for(let o of e.events)a.push(w(2)+"case "+ge(o.name)+":"),a.push(w(3)+"return ."+W(o.name)+"(try JSONDecoder.palbaseDefault.decode("+qt(o.name,"Event")+".self, from: data))");a.push(w(2)+"default:"),a.push(w(3)+"return nil"),a.push(w(2)+"}")}a.push(w(1)+"}"),a.push("");for(let o of e.messages)De(a,mn(qt(o.name,"Message"),o.schema,!0,1));a.push(w(1)+"public enum Send: Sendable {");for(let o of e.messages)a.push(w(2)+"case "+W(o.name)+"("+qt(o.name,"Message")+")");if(e.messages.length===0&&a.push(w(2)+"// this room accepts no client messages"),a.push(w(1)+"}"),a.push(""),a.push(w(1)+"public static func encode(_ message: Send) throws -> (event: String, data: Data) {"),e.messages.length===0)a.push(w(2)+"_ = message"),a.push(w(2)+"throw PBRoomError.noMessagesDeclared(pattern)");else{a.push(w(2)+"switch message {");for(let o of e.messages)a.push(w(2)+"case ."+W(o.name)+"(let value):"),a.push(w(3)+"return ("+ge(o.name)+", try JSONEncoder.palbaseDefault.encode(value))");a.push(w(2)+"}")}return a.push(w(1)+"}"),a.push("}"),a.join(`
555
+ `}var dg=e=>Ee(e)+"Room",qt=(e,t)=>Ee(e)+t;function Jn(e,t,n){let s=e.get(ee(t));if(s!==void 0)throw new Error(`${s} and ${n} both become ${t} in Swift: rename one in the backend`);e.set(ee(t),n)}function ug(e,t){let n=dg(e.pattern),s=`room ${JSON.stringify(e.pattern)}`;Jn(t,n,s),zn(s,n);let r=new Map([["topic","the room's topic"]]);for(let o of e.params){let l=it(W(o));if(Jn(r,l,`${s}'s parameter ${JSON.stringify(o)}`),l==="self"||l==="init")throw new Error(`${s}'s parameter ${JSON.stringify(o)} becomes ${l} in Swift, which the room's initializer cannot set: rename it in the backend`)}for(let[o,l]of[["Event",e.events],["Message",e.messages]]){let p=new Map;for(let u of l)Jn(p,it(W(u.name)),`${s}'s ${o.toLowerCase()} ${JSON.stringify(u.name)}`),Jn(r,qt(u.name,o),`${s}'s ${o.toLowerCase()} ${JSON.stringify(u.name)}`)}let a=[];a.push("public nonisolated struct "+n+": PBRoom {"),a.push(w(1)+"public static let pattern = "+ge(e.pattern));for(let o of e.params)a.push(w(1)+"public let "+W(o)+": String");if(e.params.length===0)a.push(w(1)+"public init() {}");else{a.push(w(1)+"public init("+e.params.map(o=>W(o)+": String").join(", ")+") {");for(let o of e.params)a.push(w(2)+"self."+it(W(o))+" = "+W(o));a.push(w(1)+"}")}a.push(w(1)+"public var topic: String {");let i="";for(let o of wt(e.pattern,":",!1)){i!==""&&(i+=":");let l=ql(o);i+=l!==void 0?"\\("+W(l)+")":ge(o).slice(1,-1)}a.push(w(2)+'return "'+i+'"'),a.push(w(1)+"}"),a.push("");for(let o of e.events)De(a,mn(qt(o.name,"Event"),o.schema,!1,1));a.push(w(1)+"public enum Event: Sendable {");for(let o of e.events)a.push(w(2)+"case "+W(o.name)+"("+qt(o.name,"Event")+")");if(e.events.length===0&&a.push(w(2)+"// this room declares no events"),a.push(w(1)+"}"),a.push(""),a.push(w(1)+"public static let eventNames: [String] = ["+e.events.map(o=>ge(o.name)).join(", ")+"]"),a.push(""),a.push(w(1)+"public static func decode(event: String, data: Data) throws -> Event? {"),e.events.length===0)a.push(w(2)+"_ = (event, data)"),a.push(w(2)+"return nil");else{a.push(w(2)+"switch event {");for(let o of e.events)a.push(w(2)+"case "+ge(o.name)+":"),a.push(w(3)+"return ."+W(o.name)+"(try JSONDecoder.palbaseDefault.decode("+qt(o.name,"Event")+".self, from: data))");a.push(w(2)+"default:"),a.push(w(3)+"return nil"),a.push(w(2)+"}")}a.push(w(1)+"}"),a.push("");for(let o of e.messages)De(a,mn(qt(o.name,"Message"),o.schema,!0,1));a.push(w(1)+"public enum Send: Sendable {");for(let o of e.messages)a.push(w(2)+"case "+W(o.name)+"("+qt(o.name,"Message")+")");if(e.messages.length===0&&a.push(w(2)+"// this room accepts no client messages"),a.push(w(1)+"}"),a.push(""),a.push(w(1)+"public static func encode(_ message: Send) throws -> (event: String, data: Data) {"),e.messages.length===0)a.push(w(2)+"_ = message"),a.push(w(2)+"throw PBRoomError.noMessagesDeclared(pattern)");else{a.push(w(2)+"switch message {");for(let o of e.messages)a.push(w(2)+"case ."+W(o.name)+"(let value):"),a.push(w(3)+"return ("+ge(o.name)+", try JSONEncoder.palbaseDefault.encode(value))");a.push(w(2)+"}")}return a.push(w(1)+"}"),a.push("}"),a.join(`
556
556
  `)+`
557
- `}function cg(e,t,n,s,r,a,i){let o=e.pathParams.map(u=>W(u)+": String");o.push("file: PBFileSource"),e.input!==void 0&&o.push("input: "+n),e.query!==void 0&&o.push("query: "+gn(e.operationID)),o.push("onProgress: (@Sendable (BackendUploadProgress) -> Void)? = nil");let l=e.pathParams.map(u=>W(u)+": "+W(u));e.input!==void 0&&l.push("input: input"),e.query!==void 0&&l.push("query: query");let d=Zn(e.operationID)+"("+l.join(", ")+")";return[w(1)+"@discardableResult",w(1)+a+"func "+t+"("+o.join(", ")+") async "+r+" -> "+s+" {",w(2)+"return try await "+i+".call("+d+", file: file, onProgress: onProgress)",w(1)+"}"]}var es=/^[A-Za-z_$][A-Za-z0-9_$]*$/,Sr=Object.freeze(["adoptSession","analytics","appContext","appVersion","auth","callOnSignOut","cancelCallOnSignOut","configuration","consent","currentSession","flags","installation","module","moduleDirectory","notify","onSessionChange","onSignOut","preferences","realtime","scope","send","socket","storage","storedUserId","telemetry","transport","version","palbe","data","stores","purchases","messaging","notifications","perf","debug","equals","hashCode","toString","self","Type","init","deinit","subscript","then","constructor","__proto__"]),mg=new Set(Sr),gg=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function ts(e){return e==="then"||e==="toJSON"||gg.has(e)}function Gl(e){return e==="call"||e==="join"||ts(e)}function ns(e){return typeof e=="string"&&es.test(e)&&!mg.has(e)&&!ts(e)}function Bl(e){return typeof e=="string"&&/^[a-z][a-z0-9-]{0,62}$/.test(e)}function Jl(e){Es(e)}function zl(e){return Es(e).origin}function q(e){if(typeof e=="object"&&e!==null&&!Array.isArray(e))return e}function je(e){return typeof e=="string"?e:""}function hg(e,t=new fn){let n=q(q(e)?.paths);if(!n)throw new Error("the contract has no `paths`");let s=[];for(let[r,a]of Object.entries(n)){let i=q(a);if(i)for(let[o,l]of Object.entries(i)){let d=q(l);if(!d)continue;let u=je(d.operationId);u!==""&&s.push({operationId:u,method:o.toUpperCase(),path:Zl(r),pathParams:_g(r),input:bg(d,t),output:Sg(d,t),headers:Fl(d,"header",t),query:Fl(d,"query",t),errors:fg(d,t),sse:d["x-palbase-sse"]!==void 0,idempotent:vg(d),upload:wg(d)})}}return s.sort((r,a)=>r.operationId<a.operationId?-1:r.operationId>a.operationId?1:0),s}function fg(e,t){let n=q(e["x-palbase-errors"]);if(!n)return[];let s=q(e.responses),r=[];for(let[a,i]of Object.entries(n)){let o=q(i);if(!o)continue;let l=typeof o.status=="number"&&Number.isFinite(o.status)?Math.trunc(o.status):0,d=je(o.code),u=je(o.description),p=o.hasData===!0;if(d===""||l===0)continue;let f={name:a,code:d,status:l,description:u};p&&(f.data=yg(s,l,d,t)),r.push(f)}return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),r}function yg(e,t,n,s){let r=q(e?.[String(t)]),a=q(q(r?.content)?.["application/json"]),i=q(a?.schema);if(!i)return;let o=i.oneOf;if(Array.isArray(o)){for(let l of o){let d=q(l);if(!d)continue;let u=q(q(d.properties)?.error);if(u&&je(u.const)===n)return Wl(d,s)}return}return Wl(i,s)}function Wl(e,t){let n=q(q(e.properties)?.data);if(n)return Ge(n,t)}function bg(e,t){let n=q(e.requestBody);if(n)return Ql(n.content,t)}function Fl(e,t,n){let s=e.parameters;if(!Array.isArray(s)||s.length===0)return;let r=[];for(let a of s){let i=q(a);if(!i||je(i.in)!==t)continue;let o=je(i.name);if(o===""||t==="header"&&Yl(o))continue;let l=i.required===!0,d=q(i.schema),u=d?Ge(d,n):be("string");r.push({name:o,schema:u,required:l})}if(r.length!==0)return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),{...be("object"),props:r}}function Yl(e){return e.toLowerCase()==="idempotency-key"}function wg(e){let t=q(e["x-palbase-upload"]);return je(t?.bucket)!==""&&je(t?.pathTemplate)!==""}function vg(e){let t=e.parameters;return Array.isArray(t)&&t.some(n=>je(q(n)?.in)==="header"&&Yl(je(q(n)?.name)))}function Zl(e){return e.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g,"{$1}")}function _g(e){let t=Zl(e),n=[],s=t;for(;;){let r=s.indexOf("{");if(r<0)break;let a=s.indexOf("}",r);if(a<0)break;let i=s.slice(r+1,a);i!==""&&n.push(i),s=s.slice(a+1)}return n}function Sg(e,t){let n=q(e.responses);if(!n)return;let s=[];for(let r of Object.keys(n))r.startsWith("2")&&r!=="200"&&r!=="201"&&s.push(r);s.sort();for(let r of["200","201",...s]){let a=q(n[r]);if(!a)continue;let i=Ql(a.content,t);if(i)return i}}function Ql(e,t){let n=q(q(e)?.["application/json"]),s=q(n?.schema);if(s&&!("$ref"in s))return Ge(s,t)}function be(e){return{kind:e,nullable:!1,props:[],enumVals:[]}}var Vl=2e5,fn=class{left=Vl;spend(){if(--this.left<0)throw new Error(`the contract's schemas make more than ${Vl} nodes once read (each type a \`type\` list names is the schema read again, at every level it is nested) \u2014 simplify the backend's schemas and push again`)}};function Ge(e,t){t.spend();let n=e.nullable===!0||Array.isArray(e.type)&&e.type.includes("null");if(e.const===null)return be("null");if(["string","number","boolean"].includes(typeof e.const))return{...be("literal"),nullable:n,literal:e.const};let s=e.anyOf??e.oneOf;if(Array.isArray(s)&&s.length>0){let i=s.map(l=>Ge(q(l)??{},t));n||=i.some(l=>l.kind==="null");let o=i.filter(l=>l.kind!=="null");return o.length===0?be("null"):o.length===1?{...o[0],nullable:n||o[0].nullable}:{...be("union"),nullable:n,members:o}}let r=e.enum;if(Array.isArray(r)){let i=[],o=!0;for(let l of r)if(typeof l=="string")i.push(l);else{o=!1;break}if(o&&i.length>0)return{...be("enum"),nullable:n,enumVals:i};if(r.length>0&&r.every(l=>l===null||["string","number","boolean"].includes(typeof l)))return Ge({anyOf:r.map(l=>({const:l})),nullable:n},t)}let a=je(e.type);if(a===""&&Array.isArray(e.type)){let i=[...new Set(e.type)];if(i.filter(l=>typeof l=="string"&&l!=="null").length>1)return Ge({anyOf:i.map(l=>({...e,type:l})),nullable:n},t);for(let l of i)typeof l=="string"&&(l==="null"?n=!0:a===""&&(a=l))}switch(a){case"null":return be("null");case"string":case"number":case"integer":case"boolean":return{...be(a),nullable:n};case"array":{let i=q(e.items),o=i?Ge(i,t):be("any");return{...be("array"),nullable:n,elem:o,...e.maxItems===0?{empty:!0}:{}}}case"object":return Hl(e,n,t);default:return"properties"in e?Hl(e,n,t):{...be("any"),nullable:n}}}function Hl(e,t,n){let s=q(e.properties)??{},r=new Set;if(Array.isArray(e.required))for(let l of e.required)typeof l=="string"&&r.add(l);let a=Object.keys(s).sort(),i=[];for(let l of a){let d=q(s[l]),u=d?Ge(d,n):be("any");i.push({name:l,schema:u,required:r.has(l)})}let o=q(e.additionalProperties);return{...be("object"),nullable:t,props:i,...o?{additional:Ge(o,n)}:e.additionalProperties===!1?{additional:!1}:{}}}function kg(e,t=new fn){let n=q(e),s=q(n?.["x-palbase-room"]);if(!s||!n)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=q(i);o&&r.push({pattern:a,params:$g(a),events:Xl(o.events,n,t),messages:Xl(o.messages,n,t)})}return r.sort((a,i)=>a.pattern<i.pattern?-1:a.pattern>i.pattern?1:0),r}function $g(e){let t=[];for(let n of e.split(":"))n.startsWith("{")&&n.endsWith("}")&&n.length>2&&t.push(n.slice(1,-1));return t}function Xl(e,t,n){let s=q(e);if(!s)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=q(i);if(!o)continue;let l=Ag(o,t)??o;r.push({name:a,schema:Ge(l,n)})}return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),r}function Ag(e,t){let n=je(e.$ref);if(!n.startsWith("#/"))return;let s=t;for(let r of n.slice(2).split("/")){let a=r.replace(/~1/g,"/").replace(/~0/g,"~"),i=q(s);if(!i||!Object.hasOwn(i,a))return;s=i[a]}return q(s)}function is(e){return e.split(".").filter(t=>t!=="")}function Ce(e){return rs(e,!0)}function os(e){return is(e).map(Ce).join("")}function rs(e,t){let n=[],s="";for(let a of e)/^[A-Za-z0-9]$/.test(a)?s+=a:s.length>0&&(n.push(s),s="");if(s.length>0&&n.push(s),n.length===0)return t?"Op":"op";let r="";if(n.forEach((a,i)=>{r+=i===0&&!t?a.charAt(0).toLowerCase()+a.slice(1):a.charAt(0).toUpperCase()+a.slice(1)}),r.charAt(0)>="0"&&r.charAt(0)<="9"&&(r=`_${r}`),!es.test(r))throw new Error(`${JSON.stringify(e)} does not make an identifier`);return r}function ed(e,t){if(!ns(t.client))throw new Error(`${JSON.stringify(t.client)} cannot name a backend: it is an identifier the client does not keep (RESERVED_CLIENT_NAMES)`);try{zl(t.project)}catch($){throw new Error(`the project's address ${JSON.stringify(t.project)}: ${$ instanceof Error?$.message:String($)}`)}if(!Bl(t.machine))throw new Error(`${JSON.stringify(t.machine)} is not a machine's name: a lowercase letter, then lowercase letters, digits and -`);try{Jl(t.url)}catch($){throw new Error(`the machine's address ${JSON.stringify(t.url)}: ${$ instanceof Error?$.message:String($)}`)}let n=new fn,s=hg(e,n),r=kg(e,n),a=Pg(e),i=[],o=[],l=new Set;for(let $ of s){let[K]=is($.operationId);if(K!==void 0){if(Gl(K)){let X=K.toLowerCase();l.has(X)||(l.add(X),o.push(K));continue}i.push($)}}o.sort();let[d,u]=Rg(i),p=new $r,[f,y,v]=Eg(d,p),T=d.filter($=>!v.has($.operationId)),[A,P]=Ig(T,p),S="";for(let $ of t.header)S+=$===""?`//
557
+ `}function cg(e,t,n,s,r,a,i){let o=e.pathParams.map(u=>W(u)+": String");o.push("file: PBFileSource"),e.input!==void 0&&o.push("input: "+n),e.query!==void 0&&o.push("query: "+gn(e.operationID)),o.push("onProgress: (@Sendable (BackendUploadProgress) -> Void)? = nil");let l=e.pathParams.map(u=>W(u)+": "+W(u));e.input!==void 0&&l.push("input: input"),e.query!==void 0&&l.push("query: query");let p=Zn(e.operationID)+"("+l.join(", ")+")";return[w(1)+"@discardableResult",w(1)+a+"func "+t+"("+o.join(", ")+") async "+r+" -> "+s+" {",w(2)+"return try await "+i+".call("+p+", file: file, onProgress: onProgress)",w(1)+"}"]}var es=/^[A-Za-z_$][A-Za-z0-9_$]*$/,Sr=Object.freeze(["adoptSession","analytics","appContext","appVersion","auth","callOnSignOut","cancelCallOnSignOut","configuration","consent","currentSession","flags","installation","module","moduleDirectory","notify","onSessionChange","onSignOut","preferences","realtime","scope","send","socket","storage","storedUserId","telemetry","transport","version","palbe","data","stores","purchases","messaging","notifications","perf","debug","equals","hashCode","toString","self","Type","init","deinit","subscript","then","constructor","__proto__"]),mg=new Set(Sr),gg=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function ts(e){return e==="then"||e==="toJSON"||gg.has(e)}function Gl(e){return e==="call"||e==="join"||ts(e)}function ns(e){return typeof e=="string"&&es.test(e)&&!mg.has(e)&&!ts(e)}function Bl(e){return typeof e=="string"&&/^[a-z][a-z0-9-]{0,62}$/.test(e)}function Jl(e){Es(e)}function zl(e){return Es(e).origin}function q(e){if(typeof e=="object"&&e!==null&&!Array.isArray(e))return e}function je(e){return typeof e=="string"?e:""}function hg(e,t=new fn){let n=q(q(e)?.paths);if(!n)throw new Error("the contract has no `paths`");let s=[];for(let[r,a]of Object.entries(n)){let i=q(a);if(i)for(let[o,l]of Object.entries(i)){let p=q(l);if(!p)continue;let u=je(p.operationId);u!==""&&s.push({operationId:u,method:o.toUpperCase(),path:Zl(r),pathParams:_g(r),input:bg(p,t),output:Sg(p,t),headers:Fl(p,"header",t),query:Fl(p,"query",t),errors:fg(p,t),sse:p["x-palbase-sse"]!==void 0,idempotent:vg(p),upload:wg(p)})}}return s.sort((r,a)=>r.operationId<a.operationId?-1:r.operationId>a.operationId?1:0),s}function fg(e,t){let n=q(e["x-palbase-errors"]);if(!n)return[];let s=q(e.responses),r=[];for(let[a,i]of Object.entries(n)){let o=q(i);if(!o)continue;let l=typeof o.status=="number"&&Number.isFinite(o.status)?Math.trunc(o.status):0,p=je(o.code),u=je(o.description),d=o.hasData===!0;if(p===""||l===0)continue;let f={name:a,code:p,status:l,description:u};d&&(f.data=yg(s,l,p,t)),r.push(f)}return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),r}function yg(e,t,n,s){let r=q(e?.[String(t)]),a=q(q(r?.content)?.["application/json"]),i=q(a?.schema);if(!i)return;let o=i.oneOf;if(Array.isArray(o)){for(let l of o){let p=q(l);if(!p)continue;let u=q(q(p.properties)?.error);if(u&&je(u.const)===n)return Wl(p,s)}return}return Wl(i,s)}function Wl(e,t){let n=q(q(e.properties)?.data);if(n)return Ge(n,t)}function bg(e,t){let n=q(e.requestBody);if(n)return Ql(n.content,t)}function Fl(e,t,n){let s=e.parameters;if(!Array.isArray(s)||s.length===0)return;let r=[];for(let a of s){let i=q(a);if(!i||je(i.in)!==t)continue;let o=je(i.name);if(o===""||t==="header"&&Yl(o))continue;let l=i.required===!0,p=q(i.schema),u=p?Ge(p,n):be("string");r.push({name:o,schema:u,required:l})}if(r.length!==0)return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),{...be("object"),props:r}}function Yl(e){return e.toLowerCase()==="idempotency-key"}function wg(e){let t=q(e["x-palbase-upload"]);return je(t?.bucket)!==""&&je(t?.pathTemplate)!==""}function vg(e){let t=e.parameters;return Array.isArray(t)&&t.some(n=>je(q(n)?.in)==="header"&&Yl(je(q(n)?.name)))}function Zl(e){return e.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g,"{$1}")}function _g(e){let t=Zl(e),n=[],s=t;for(;;){let r=s.indexOf("{");if(r<0)break;let a=s.indexOf("}",r);if(a<0)break;let i=s.slice(r+1,a);i!==""&&n.push(i),s=s.slice(a+1)}return n}function Sg(e,t){let n=q(e.responses);if(!n)return;let s=[];for(let r of Object.keys(n))r.startsWith("2")&&r!=="200"&&r!=="201"&&s.push(r);s.sort();for(let r of["200","201",...s]){let a=q(n[r]);if(!a)continue;let i=Ql(a.content,t);if(i)return i}}function Ql(e,t){let n=q(q(e)?.["application/json"]),s=q(n?.schema);if(s&&!("$ref"in s))return Ge(s,t)}function be(e){return{kind:e,nullable:!1,props:[],enumVals:[]}}var Vl=2e5,fn=class{left=Vl;spend(){if(--this.left<0)throw new Error(`the contract's schemas make more than ${Vl} nodes once read (each type a \`type\` list names is the schema read again, at every level it is nested) \u2014 simplify the backend's schemas and push again`)}};function Ge(e,t){t.spend();let n=e.nullable===!0||Array.isArray(e.type)&&e.type.includes("null");if(e.const===null)return be("null");if(["string","number","boolean"].includes(typeof e.const))return{...be("literal"),nullable:n,literal:e.const};let s=e.anyOf??e.oneOf;if(Array.isArray(s)&&s.length>0){let i=s.map(l=>Ge(q(l)??{},t));n||=i.some(l=>l.kind==="null");let o=i.filter(l=>l.kind!=="null");return o.length===0?be("null"):o.length===1?{...o[0],nullable:n||o[0].nullable}:{...be("union"),nullable:n,members:o}}let r=e.enum;if(Array.isArray(r)){let i=[],o=!0;for(let l of r)if(typeof l=="string")i.push(l);else{o=!1;break}if(o&&i.length>0)return{...be("enum"),nullable:n,enumVals:i};if(r.length>0&&r.every(l=>l===null||["string","number","boolean"].includes(typeof l)))return Ge({anyOf:r.map(l=>({const:l})),nullable:n},t)}let a=je(e.type);if(a===""&&Array.isArray(e.type)){let i=[...new Set(e.type)];if(i.filter(l=>typeof l=="string"&&l!=="null").length>1)return Ge({anyOf:i.map(l=>({...e,type:l})),nullable:n},t);for(let l of i)typeof l=="string"&&(l==="null"?n=!0:a===""&&(a=l))}switch(a){case"null":return be("null");case"string":case"number":case"integer":case"boolean":return{...be(a),nullable:n};case"array":{let i=q(e.items),o=i?Ge(i,t):be("any");return{...be("array"),nullable:n,elem:o,...e.maxItems===0?{empty:!0}:{}}}case"object":return Hl(e,n,t);default:return"properties"in e?Hl(e,n,t):{...be("any"),nullable:n}}}function Hl(e,t,n){let s=q(e.properties)??{},r=new Set;if(Array.isArray(e.required))for(let l of e.required)typeof l=="string"&&r.add(l);let a=Object.keys(s).sort(),i=[];for(let l of a){let p=q(s[l]),u=p?Ge(p,n):be("any");i.push({name:l,schema:u,required:r.has(l)})}let o=q(e.additionalProperties);return{...be("object"),nullable:t,props:i,...o?{additional:Ge(o,n)}:e.additionalProperties===!1?{additional:!1}:{}}}function kg(e,t=new fn){let n=q(e),s=q(n?.["x-palbase-room"]);if(!s||!n)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=q(i);o&&r.push({pattern:a,params:$g(a),events:Xl(o.events,n,t),messages:Xl(o.messages,n,t)})}return r.sort((a,i)=>a.pattern<i.pattern?-1:a.pattern>i.pattern?1:0),r}function $g(e){let t=[];for(let n of e.split(":"))n.startsWith("{")&&n.endsWith("}")&&n.length>2&&t.push(n.slice(1,-1));return t}function Xl(e,t,n){let s=q(e);if(!s)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=q(i);if(!o)continue;let l=Ag(o,t)??o;r.push({name:a,schema:Ge(l,n)})}return r.sort((a,i)=>a.name<i.name?-1:a.name>i.name?1:0),r}function Ag(e,t){let n=je(e.$ref);if(!n.startsWith("#/"))return;let s=t;for(let r of n.slice(2).split("/")){let a=r.replace(/~1/g,"/").replace(/~0/g,"~"),i=q(s);if(!i||!Object.hasOwn(i,a))return;s=i[a]}return q(s)}function is(e){return e.split(".").filter(t=>t!=="")}function Ce(e){return rs(e,!0)}function os(e){return is(e).map(Ce).join("")}function rs(e,t){let n=[],s="";for(let a of e)/^[A-Za-z0-9]$/.test(a)?s+=a:s.length>0&&(n.push(s),s="");if(s.length>0&&n.push(s),n.length===0)return t?"Op":"op";let r="";if(n.forEach((a,i)=>{r+=i===0&&!t?a.charAt(0).toLowerCase()+a.slice(1):a.charAt(0).toUpperCase()+a.slice(1)}),r.charAt(0)>="0"&&r.charAt(0)<="9"&&(r=`_${r}`),!es.test(r))throw new Error(`${JSON.stringify(e)} does not make an identifier`);return r}function ep(e,t){if(!ns(t.client))throw new Error(`${JSON.stringify(t.client)} cannot name a backend: it is an identifier the client does not keep (RESERVED_CLIENT_NAMES)`);try{zl(t.project)}catch($){throw new Error(`the project's address ${JSON.stringify(t.project)}: ${$ instanceof Error?$.message:String($)}`)}if(!Bl(t.machine))throw new Error(`${JSON.stringify(t.machine)} is not a machine's name: a lowercase letter, then lowercase letters, digits and -`);try{Jl(t.url)}catch($){throw new Error(`the machine's address ${JSON.stringify(t.url)}: ${$ instanceof Error?$.message:String($)}`)}let n=new fn,s=hg(e,n),r=kg(e,n),a=Pg(e),i=[],o=[],l=new Set;for(let $ of s){let[K]=is($.operationId);if(K!==void 0){if(Gl(K)){let X=K.toLowerCase();l.has(X)||(l.add(X),o.push(K));continue}i.push($)}}o.sort();let[p,u]=Rg(i),d=new $r,[f,y,v]=Eg(p,d),T=p.filter($=>!v.has($.operationId)),[A,P]=Ig(T,d),S="";for(let $ of t.header)S+=$===""?`//
558
558
  `:`// ${se($)}
559
559
  `;let C=["BackendBase"];P>0&&C.push("BackendError"),T.some($=>!$.upload)&&C.push("CallOptions"),T.some($=>$.upload)&&C.push("UploadOptions"),S+=`import type { ${C.join(", ")} } from 'palbase/web';
560
560
  `,S+=`import { __registerBackend } from 'palbase/web/palbe';
@@ -568,37 +568,37 @@ public typealias JSONValue = JsonElement
568
568
  `,S+=qg(T),r.length>0){S+=`
569
569
  // \u2500\u2500 Rooms (@Room) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
570
570
 
571
- `;for(let $ of r)S+=zg($,p)}return a!==void 0&&(S+=Fg(a)),S}var $r=class{made=new Map(["__registerBackend","BackendBase","BackendError","CallOptions","UploadOptions","Backend","Roles","Role","Permissions","Permission"].map(t=>[t,"the module itself"]));claim(t,n){let s=this.made.get(t);if(s!==void 0)throw new Error(`${s} and ${n} both make the name ${t} \u2014 rename one in the backend and push again`);return this.made.set(t,n),t}},ss=e=>`operation ${JSON.stringify(e.operationId)}`,td=(e,t)=>`${ss(e)}'s error ${JSON.stringify(t.name)}`,kr=e=>`room ${JSON.stringify(e.pattern)}`;function Pg(e){let t=q(e)?.["x-palbase-roles"];if(t===void 0)return;let n=q(t);if(!n||!Array.isArray(n.roles))throw new Error("this contract's `x-palbase-roles` carries no roles list, so what the stack defines cannot be read from it. Refresh the contract; if it comes back the same, the stack is serving a malformed document.");return t}function Rg(e){let t=[],n=[],s=new as;for(let r of e){let a=is(r.operationId),i=a[a.length-1];if(i===void 0)continue;let o=!1;for(let d of a.slice(1))if(ts(d)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (reserved segment "${se(d)}")`),o=!0;break}if(o)continue;if(r.input&&r.query){n.push(`// codegen: skipped operation "${se(r.operationId)}" (both body and query declared \u2014 unsupported)`);continue}if(r.sse&&r.query){n.push(`// codegen: skipped operation "${se(r.operationId)}" (streaming with query parameters \u2014 unsupported)`);continue}if(r.upload&&(r.method!=="POST"||r.sse)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (an upload that is not one POST answered once \u2014 unsupported)`);continue}if(r.query){let d=Tg(r.query);if(d!==void 0){n.push(`// codegen: skipped operation "${se(r.operationId)}" (non-primitive query parameter "${se(d)}")`);continue}}let l=s;for(let d of a.slice(0,-1))if(l=l.childNode(d),!l)break;if(!l?.addMethod(i,r)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (key collides with existing namespace/method)`);continue}t.push(r)}return[t,n]}function Tg(e){for(let t of e.props)switch(t.schema.kind){case"string":case"number":case"integer":case"boolean":case"enum":break;default:return t.name}}function Eg(e,t){let n=new Set,s=new Map,r=[],a=[];for(let o of e){let l=os(o.operationId),d=s.get(l);if(d!==void 0){r.push(`// codegen: skipped type "${se(l)}" (collides with operation "${se(d)}")`),n.add(o.operationId);continue}s.set(l,o.operationId),o.input&&a.push(...Kt(t.claim(`${l}Request`,ss(o)),o.input)),o.query&&a.push(...Kt(t.claim(`${l}Query`,ss(o)),o.query)),o.output&&a.push(...Cg(l,o.output,p=>t.claim(p,ss(o))));let u=Ar(o);for(let p of u){if(!p.data)continue;let f=t.claim(`${l}${Ce(p.name)}Data`,td(o,p));a.push(`/** ${se(p.code)} (${p.status}): ${se(p.description)} */`),a.push(...Kt(f,p.data))}}if(a.length===0)return["",r,n];let i=`// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
571
+ `;for(let $ of r)S+=zg($,d)}return a!==void 0&&(S+=Fg(a)),S}var $r=class{made=new Map(["__registerBackend","BackendBase","BackendError","CallOptions","UploadOptions","Backend","Roles","Role","Permissions","Permission"].map(t=>[t,"the module itself"]));claim(t,n){let s=this.made.get(t);if(s!==void 0)throw new Error(`${s} and ${n} both make the name ${t} \u2014 rename one in the backend and push again`);return this.made.set(t,n),t}},ss=e=>`operation ${JSON.stringify(e.operationId)}`,tp=(e,t)=>`${ss(e)}'s error ${JSON.stringify(t.name)}`,kr=e=>`room ${JSON.stringify(e.pattern)}`;function Pg(e){let t=q(e)?.["x-palbase-roles"];if(t===void 0)return;let n=q(t);if(!n||!Array.isArray(n.roles))throw new Error("this contract's `x-palbase-roles` carries no roles list, so what the stack defines cannot be read from it. Refresh the contract; if it comes back the same, the stack is serving a malformed document.");return t}function Rg(e){let t=[],n=[],s=new as;for(let r of e){let a=is(r.operationId),i=a[a.length-1];if(i===void 0)continue;let o=!1;for(let p of a.slice(1))if(ts(p)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (reserved segment "${se(p)}")`),o=!0;break}if(o)continue;if(r.input&&r.query){n.push(`// codegen: skipped operation "${se(r.operationId)}" (both body and query declared \u2014 unsupported)`);continue}if(r.sse&&r.query){n.push(`// codegen: skipped operation "${se(r.operationId)}" (streaming with query parameters \u2014 unsupported)`);continue}if(r.upload&&(r.method!=="POST"||r.sse)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (an upload that is not one POST answered once \u2014 unsupported)`);continue}if(r.query){let p=Tg(r.query);if(p!==void 0){n.push(`// codegen: skipped operation "${se(r.operationId)}" (non-primitive query parameter "${se(p)}")`);continue}}let l=s;for(let p of a.slice(0,-1))if(l=l.childNode(p),!l)break;if(!l?.addMethod(i,r)){n.push(`// codegen: skipped operation "${se(r.operationId)}" (key collides with existing namespace/method)`);continue}t.push(r)}return[t,n]}function Tg(e){for(let t of e.props)switch(t.schema.kind){case"string":case"number":case"integer":case"boolean":case"enum":break;default:return t.name}}function Eg(e,t){let n=new Set,s=new Map,r=[],a=[];for(let o of e){let l=os(o.operationId),p=s.get(l);if(p!==void 0){r.push(`// codegen: skipped type "${se(l)}" (collides with operation "${se(p)}")`),n.add(o.operationId);continue}s.set(l,o.operationId),o.input&&a.push(...Kt(t.claim(`${l}Request`,ss(o)),o.input)),o.query&&a.push(...Kt(t.claim(`${l}Query`,ss(o)),o.query)),o.output&&a.push(...Cg(l,o.output,d=>t.claim(d,ss(o))));let u=Ar(o);for(let d of u){if(!d.data)continue;let f=t.claim(`${l}${Ce(d.name)}Data`,tp(o,d));a.push(`/** ${se(d.code)} (${d.status}): ${se(d.description)} */`),a.push(...Kt(f,d.data))}}if(a.length===0)return["",r,n];let i=`// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
572
572
 
573
573
  `;for(let o of a)i+=`${o}
574
- `;return[i,r,n]}function Kt(e,t){return t.kind==="object"&&!t.nullable&&t.props.length>0&&!t.additional?nd(e,t):[`export type ${e} = ${vt(t)};`,""]}function Cg(e,t,n){return t.kind==="array"&&!t.empty&&t.elem&&t.elem.kind==="object"&&!t.elem.nullable&&t.elem.props.length>0&&!t.elem.additional?[...nd(n(`${e}ResponseItem`),t.elem),`export type ${n(`${e}Response`)} = ${e}ResponseItem[]${t.nullable?" | null":""};`,""]:Kt(n(`${e}Response`),t)}function nd(e,t){let n=[`export interface ${e} {`];for(let s of t.props){if(s.name==="__proto__"){n.push(' // codegen: skipped property "__proto__"');continue}let r=s.required?"":"?";n.push(` ${Be(s.name)}${r}: ${vt(s.schema)};`)}return n.push("}",""),n}function vt(e){switch(e.kind){case"string":return e.nullable?"string | null":"string";case"number":case"integer":return e.nullable?"number | null":"number";case"boolean":return e.nullable?"boolean | null":"boolean";case"null":return"null";case"literal":{if(typeof e.literal=="number"&&!Number.isFinite(e.literal))throw new Error(`the literal ${e.literal} is not a finite number`);let t=typeof e.literal=="string"?re(e.literal):JSON.stringify(e.literal);return e.nullable?`${t} | null`:t}case"union":{let t=(e.members??[]).map(vt);return e.nullable&&t.push("null"),t.join(" | ")}case"enum":{let t=e.enumVals.map(re).join(" | ");return e.nullable?`(${t}) | null`:t}case"array":{if(e.empty)return e.nullable?"[] | null":"[]";if(!e.elem)return e.nullable?"unknown[] | null":"unknown[]";let t=vt(e.elem);(t.includes(" | ")||t.startsWith("("))&&(t=`(${t})`);let n=`${t}[]`;return e.nullable?`${n} | null`:n}case"object":{let t=jg(e);return e.nullable?`${t} | null`:t}default:return e.nullable?"unknown | null":"unknown"}}function jg(e){let t=[];for(let n of e.props){if(n.name==="__proto__")continue;let s=n.required?"":"?";t.push(`${Be(n.name)}${s}: ${vt(n.schema)}`)}if(e.additional){let n=`Record<string, ${vt(e.additional)}>`;return t.length===0?n:`({ ${t.join("; ")} } & ${n})`}return t.length===0?e.additional===!1?"Record<string, never>":"Record<string, unknown>":`{ ${t.join("; ")} }`}var Og=new Set(["not_configured","network_error","decode_error","validation_error","invalid_endpoint_name","unsupported_get_input","missing_path_param","unexpected_argument","invalid_query_value","reserved_namespace","invalid_namespace_tree","aborted","http_error","invalid_path_parameter","invalid_json","empty_stream","stream_failed","invalid_frame","invalid_path","missing_credential","undeclared_answer","undeclared_redirect","pow_unsolved","invalid_file"]),Dg=/^http_\d+$/;function Ar(e){if(e.errors.length===0)return[];let t=a=>{throw new Error(`${e.operationId}: ${a} \u2014 rename the error in the backend and push again`)},n=[...e.errors].sort((a,i)=>a.code<i.code?-1:a.code>i.code?1:0),s=new Set,r=new Map;for(let a of n){a.code==="__proto__"&&t('declares the error code "__proto__", which no lookup can hold'),(Og.has(a.code)||Dg.test(a.code))&&t(`declares the error "${a.code}", a code the web client produces itself \u2014 the two could not be told apart`),s.has(a.code)&&t(`declares the error code "${a.code}" twice`);let i=Ce(a.name),o=r.get(i);o!==void 0&&t(`declares errors "${o}" and "${a.name}", which both become the class ${i}`),s.add(a.code),r.set(i,a.name)}return n}function Ig(e,t){let n=0,s=[];for(let a of e){let i=os(a.operationId),o=Ar(a);for(let l of o){let d=t.claim(`${i}${Ce(l.name)}Error`,td(a,l)),u=l.data!==void 0;s.push(`export class ${d} extends Error {`),s.push(` readonly name = ${re(d)};`),s.push(` readonly code = ${re(l.code)};`),s.push(` readonly status = ${l.status};`),u&&s.push(` readonly data: ${i}${Ce(l.name)}Data;`),s.push(" readonly cause: BackendError;"),s.push(" constructor(cause: BackendError) {"),s.push(" super(cause.message);"),s.push(" this.cause = cause;"),u&&s.push(` this.data = cause.data as ${i}${Ce(l.name)}Data;`),s.push(" }"),s.push("}"),s.push(""),n++}}if(n===0)return["",0];let r=`// \u2500\u2500 Typed errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
574
+ `;return[i,r,n]}function Kt(e,t){return t.kind==="object"&&!t.nullable&&t.props.length>0&&!t.additional?np(e,t):[`export type ${e} = ${vt(t)};`,""]}function Cg(e,t,n){return t.kind==="array"&&!t.empty&&t.elem&&t.elem.kind==="object"&&!t.elem.nullable&&t.elem.props.length>0&&!t.elem.additional?[...np(n(`${e}ResponseItem`),t.elem),`export type ${n(`${e}Response`)} = ${e}ResponseItem[]${t.nullable?" | null":""};`,""]:Kt(n(`${e}Response`),t)}function np(e,t){let n=[`export interface ${e} {`];for(let s of t.props){if(s.name==="__proto__"){n.push(' // codegen: skipped property "__proto__"');continue}let r=s.required?"":"?";n.push(` ${Be(s.name)}${r}: ${vt(s.schema)};`)}return n.push("}",""),n}function vt(e){switch(e.kind){case"string":return e.nullable?"string | null":"string";case"number":case"integer":return e.nullable?"number | null":"number";case"boolean":return e.nullable?"boolean | null":"boolean";case"null":return"null";case"literal":{if(typeof e.literal=="number"&&!Number.isFinite(e.literal))throw new Error(`the literal ${e.literal} is not a finite number`);let t=typeof e.literal=="string"?re(e.literal):JSON.stringify(e.literal);return e.nullable?`${t} | null`:t}case"union":{let t=(e.members??[]).map(vt);return e.nullable&&t.push("null"),t.join(" | ")}case"enum":{let t=e.enumVals.map(re).join(" | ");return e.nullable?`(${t}) | null`:t}case"array":{if(e.empty)return e.nullable?"[] | null":"[]";if(!e.elem)return e.nullable?"unknown[] | null":"unknown[]";let t=vt(e.elem);(t.includes(" | ")||t.startsWith("("))&&(t=`(${t})`);let n=`${t}[]`;return e.nullable?`${n} | null`:n}case"object":{let t=jg(e);return e.nullable?`${t} | null`:t}default:return e.nullable?"unknown | null":"unknown"}}function jg(e){let t=[];for(let n of e.props){if(n.name==="__proto__")continue;let s=n.required?"":"?";t.push(`${Be(n.name)}${s}: ${vt(n.schema)}`)}if(e.additional){let n=`Record<string, ${vt(e.additional)}>`;return t.length===0?n:`({ ${t.join("; ")} } & ${n})`}return t.length===0?e.additional===!1?"Record<string, never>":"Record<string, unknown>":`{ ${t.join("; ")} }`}var Og=new Set(["not_configured","network_error","decode_error","validation_error","invalid_endpoint_name","unsupported_get_input","missing_path_param","unexpected_argument","invalid_query_value","reserved_namespace","invalid_namespace_tree","aborted","http_error","invalid_path_parameter","invalid_json","empty_stream","stream_failed","invalid_frame","invalid_path","missing_credential","undeclared_answer","undeclared_redirect","pow_unsolved","invalid_file"]),Dg=/^http_\d+$/;function Ar(e){if(e.errors.length===0)return[];let t=a=>{throw new Error(`${e.operationId}: ${a} \u2014 rename the error in the backend and push again`)},n=[...e.errors].sort((a,i)=>a.code<i.code?-1:a.code>i.code?1:0),s=new Set,r=new Map;for(let a of n){a.code==="__proto__"&&t('declares the error code "__proto__", which no lookup can hold'),(Og.has(a.code)||Dg.test(a.code))&&t(`declares the error "${a.code}", a code the web client produces itself \u2014 the two could not be told apart`),s.has(a.code)&&t(`declares the error code "${a.code}" twice`);let i=Ce(a.name),o=r.get(i);o!==void 0&&t(`declares errors "${o}" and "${a.name}", which both become the class ${i}`),s.add(a.code),r.set(i,a.name)}return n}function Ig(e,t){let n=0,s=[];for(let a of e){let i=os(a.operationId),o=Ar(a);for(let l of o){let p=t.claim(`${i}${Ce(l.name)}Error`,tp(a,l)),u=l.data!==void 0;s.push(`export class ${p} extends Error {`),s.push(` readonly name = ${re(p)};`),s.push(` readonly code = ${re(l.code)};`),s.push(` readonly status = ${l.status};`),u&&s.push(` readonly data: ${i}${Ce(l.name)}Data;`),s.push(" readonly cause: BackendError;"),s.push(" constructor(cause: BackendError) {"),s.push(" super(cause.message);"),s.push(" this.cause = cause;"),u&&s.push(` this.data = cause.data as ${i}${Ce(l.name)}Data;`),s.push(" }"),s.push("}"),s.push(""),n++}}if(n===0)return["",0];let r=`// \u2500\u2500 Typed errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
575
575
 
576
576
  `;for(let a of s)r+=`${a}
577
- `;return[r,n]}var as=class e{entries=[];index=new Map;childNode(t){let n=this.index.get(t);if(n)return n.child;let s=new e,r={key:t,child:s};return this.entries.push(r),this.index.set(t,r),s}addMethod(t,n){if(this.index.has(t))return!1;let s={key:t,op:n};return this.entries.push(s),this.index.set(t,s),!0}};function sd(e){let t=new as;for(let n of e){let s=is(n.operationId),r=s[s.length-1];if(r===void 0)continue;let a=t;for(let i of s.slice(0,-1))if(a=a.childNode(i),!a)break;a&&a.addMethod(r,n)}return t}function Lg(e,t){let n=`__registerBackend(${re(t.project)}, ${re(t.client)}, ${re(t.machine)}, ${re(t.url)}, `;if(e.length===0)return`${n}{});
577
+ `;return[r,n]}var as=class e{entries=[];index=new Map;childNode(t){let n=this.index.get(t);if(n)return n.child;let s=new e,r={key:t,child:s};return this.entries.push(r),this.index.set(t,r),s}addMethod(t,n){if(this.index.has(t))return!1;let s={key:t,op:n};return this.entries.push(s),this.index.set(t,s),!0}};function sp(e){let t=new as;for(let n of e){let s=is(n.operationId),r=s[s.length-1];if(r===void 0)continue;let a=t;for(let i of s.slice(0,-1))if(a=a.childNode(i),!a)break;a&&a.addMethod(r,n)}return t}function Lg(e,t){let n=`__registerBackend(${re(t.project)}, ${re(t.client)}, ${re(t.machine)}, ${re(t.url)}, `;if(e.length===0)return`${n}{});
578
578
  `;let s=`${n}{
579
- `;return s+=rd(sd(e),2),s+=`});
580
- `,s}function rd(e,t){let n=" ".repeat(t),s="";for(let r of e.entries){if(r.child){s+=`${n}${Be(r.key)}: {
581
- `,s+=rd(r.child,t+2),s+=`${n}},
582
- `;continue}r.op&&(s+=Ng(r.op,r.key,t))}return s}function Ng(e,t,n){let s=" ".repeat(n),r=Be(t),a=Ar(e),i=a.length>0,o=e.pathParams.length>0,l=e.input!==void 0,d=e.query!==void 0,u="";!l&&!d?u="none":d&&(u="query");let p="";if(!i){let y=[`method: ${re(e.method)}`,`path: ${re(e.path)}`];return o&&y.push(`pathParams: [${hn(e.pathParams)}]`),u!==""&&y.push(`input: ${re(u)}`),e.upload&&y.push("upload: true"),e.sse&&y.push("sse: true"),e.idempotent&&!e.upload&&y.push("idempotent: true"),p+=`${s}${r}: { ${y.join(", ")} },
583
- `,p}let f=" ".repeat(n+2);return p+=`${s}${r}: {
584
- `,p+=`${f}method: ${re(e.method)},
585
- `,p+=`${f}path: ${re(e.path)},
586
- `,o&&(p+=`${f}pathParams: [${hn(e.pathParams)}],
587
- `),u!==""&&(p+=`${f}input: ${re(u)},
588
- `),e.upload&&(p+=`${f}upload: true,
589
- `),e.sse&&(p+=`${f}sse: true,
590
- `),e.idempotent&&!e.upload&&(p+=`${f}idempotent: true,
591
- `),p+=`${f}errors: { `,a.forEach((y,v)=>{v>0&&(p+=", ");let T=`${os(e.operationId)}${Ce(y.name)}Error`;p+=`${Be(y.code)}: (e) => new ${T}(e)`}),p+=` },
592
- `,p+=`${s}},
593
- `,p}function hn(e){return e.map(re).join(", ")}function qg(e){if(e.length===0)return`export interface Backend extends BackendBase {}
579
+ `;return s+=rp(sp(e),2),s+=`});
580
+ `,s}function rp(e,t){let n=" ".repeat(t),s="";for(let r of e.entries){if(r.child){s+=`${n}${Be(r.key)}: {
581
+ `,s+=rp(r.child,t+2),s+=`${n}},
582
+ `;continue}r.op&&(s+=Ng(r.op,r.key,t))}return s}function Ng(e,t,n){let s=" ".repeat(n),r=Be(t),a=Ar(e),i=a.length>0,o=e.pathParams.length>0,l=e.input!==void 0,p=e.query!==void 0,u="";!l&&!p?u="none":p&&(u="query");let d="";if(!i){let y=[`method: ${re(e.method)}`,`path: ${re(e.path)}`];return o&&y.push(`pathParams: [${hn(e.pathParams)}]`),u!==""&&y.push(`input: ${re(u)}`),e.upload&&y.push("upload: true"),e.sse&&y.push("sse: true"),e.idempotent&&!e.upload&&y.push("idempotent: true"),d+=`${s}${r}: { ${y.join(", ")} },
583
+ `,d}let f=" ".repeat(n+2);return d+=`${s}${r}: {
584
+ `,d+=`${f}method: ${re(e.method)},
585
+ `,d+=`${f}path: ${re(e.path)},
586
+ `,o&&(d+=`${f}pathParams: [${hn(e.pathParams)}],
587
+ `),u!==""&&(d+=`${f}input: ${re(u)},
588
+ `),e.upload&&(d+=`${f}upload: true,
589
+ `),e.sse&&(d+=`${f}sse: true,
590
+ `),e.idempotent&&!e.upload&&(d+=`${f}idempotent: true,
591
+ `),d+=`${f}errors: { `,a.forEach((y,v)=>{v>0&&(d+=", ");let T=`${os(e.operationId)}${Ce(y.name)}Error`;d+=`${Be(y.code)}: (e) => new ${T}(e)`}),d+=` },
592
+ `,d+=`${s}},
593
+ `,d}function hn(e){return e.map(re).join(", ")}function qg(e){if(e.length===0)return`export interface Backend extends BackendBase {}
594
594
  `;let t=`export interface Backend extends BackendBase {
595
- `;return t+=ad(sd(e),2),t+=`}
596
- `,t}function ad(e,t){let n=" ".repeat(t),s="";for(let r of e.entries){if(r.child){s+=`${n}${Be(r.key)}: {
597
- `,s+=ad(r.child,t+2),s+=`${n}};
598
- `;continue}r.op&&(s+=`${n}${Gg(r.key)}${Mg(r.op)};
599
- `)}return s}function Mg(e){let t=os(e.operationId),n=[],s=new Set;for(let a of e.pathParams)n.push(`${xg(a,s)}: string`);e.input?n.push(`input: ${t}Request`):e.query&&n.push(`query: ${t}Query`),e.upload?n.push("file: Blob","options?: UploadOptions"):n.push(Ug(e.headers));let r=e.sse?`Promise<AsyncIterable<${e.output?`${t}Response`:"unknown"}>>`:e.output?`Promise<${t}Response>`:"Promise<void>";return`(${n.join(", ")}): ${r}`}function Ug(e){if(!e||e.props.length===0)return"options?: CallOptions";let t=e.props.some(r=>r.required),s=`{ ${e.props.map(r=>{let a=r.required?"":"?";return`${Be(r.name)}${a}: ${vt(r.schema)}`}).join("; ")} }`;return t?`options: CallOptions & { headers: ${s} }`:`options?: CallOptions & { headers?: ${s} }`}function xg(e,t){let n=rs(e,!1);(Kg.has(n)||n==="input"||n==="query"||n==="options")&&(n+="_");let s=n;for(let r=2;t.has(s);r++)s=n+r;return t.add(s),s}var Kg=new Set("await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield implements interface let package private protected public static arguments eval".split(" "));function Be(e){return es.test(e)?e:re(e)}function Gg(e){return e==="new"?re(e):Be(e)}var Bg=/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ud800-\udfff]/;function id(e,t){switch(e){case"\\":return"\\\\";case`
600
- `:return"\\n";case"\r":return"\\r";case"'":return t?"\\'":e}return e.length===1&&Bg.test(e)?`\\u${e.charCodeAt(0).toString(16).padStart(4,"0")}`:e}function re(e){let t="'";for(let n of e)t+=id(n,!0);return`${t}'`}function se(e){let t="";for(let n of e)t+=n==="@"?"\\u0040":n==="#"?"\\u0023":id(n,!1);return t.replaceAll("*/","*\\/")}function Jg(e){return`${Ce(e)}Room`}function zg(e,t){let n=Jg(e.pattern),s=[];for(let a of e.events)s.push(...Kt(t.claim(`${n}${Ce(a.name)}Event`,`${kr(e)}'s event ${JSON.stringify(a.name)}`),a.schema));for(let a of e.messages)s.push(...Kt(t.claim(`${n}${Ce(a.name)}Message`,`${kr(e)}'s message ${JSON.stringify(a.name)}`),a.schema));for(let a of[`${n}Event`,`${n}Params`,n])t.claim(a,kr(e));if(e.events.length>0){s.push(`export type ${n}Event =`);for(let a of e.events)s.push(` | { event: ${re(a.name)}; payload: ${n}${Ce(a.name)}Event }`);s[s.length-1]+=";"}else s.push(`export type ${n}Event = never;`);s.push("");let r=e.params.map(a=>`${Be(a)}: string`).join("; ");return s.push(`export interface ${n}Params { ${r} }`),s.push(`export const ${n} = {`),s.push(` pattern: ${re(e.pattern)},`),s.push(` params: [${hn(e.params)}] as const,`),s.push(` events: [${hn(e.events.map(a=>a.name))}] as const,`),s.push(` messages: [${hn(e.messages.map(a=>a.name))}] as const,`),s.push(` send: {} as { ${e.messages.map(a=>`${Be(a.name)}: ${n}${Ce(a.name)}Message`).join("; ")} },`),s.push(` event: {} as ${n}Event,`),s.push("} as const;"),s.push(""),s.join(`
601
- `)}function Wg(e){if(typeof e!="object"||e===null||!("roles"in e)||!Array.isArray(e.roles))throw new Error("roles artifact has no roles list");return e.roles.map((t,n)=>{if(typeof t!="object"||t===null)throw new Error(`roles artifact entry ${n} is not a role`);if(!("name"in t)||typeof t.name!="string"||t.name==="")throw new Error(`roles artifact entry ${n} has no name`);let s=t.name;if(!("isDefault"in t)||typeof t.isDefault!="boolean")throw new Error(`role "${s}" does not say whether it is the default`);if(!("permissions"in t)||!Array.isArray(t.permissions))throw new Error(`role "${s}" does not carry a list of permission names`);let r=[];for(let i of t.permissions){if(typeof i!="string")throw new Error(`role "${s}" lists a permission that is not a name`);r.push(i)}let a={name:s,isDefault:t.isDefault,permissions:r};return"description"in t&&typeof t.description=="string"&&t.description!==""&&(a.description=t.description),a})}function Fg(e){let t=Wg(e).sort((d,u)=>d.name<u.name?-1:d.name>u.name?1:0),n=[...new Set(t.flatMap(d=>d.permissions))].sort(),s=[],r=new Map;for(let d of t){let u=rs(d.name,!1),p=r.get(u);if(p!==void 0){s.push(` // codegen: skipped duplicate role "${se(d.name)}" \u2014 collides with "${se(p)}" (both map to \`${se(u)}\`)`);continue}r.set(u,d.name),d.description!==void 0&&s.push(` /** ${se(d.description)} */`),d.isDefault&&s.push(" /** Assigned to every new user at signup. */"),s.push(` ${u}: ${re(d.name)},`)}let a=[],i=new Map;for(let d of n){let u=rs(d,!1),p=i.get(u);if(p!==void 0){a.push(` // codegen: skipped duplicate permission "${se(d)}" \u2014 collides with "${se(p)}" (both map to \`${se(u)}\`)`);continue}i.set(u,d),a.push(` ${u}: ${re(d)},`)}let o="";if(r.size>0&&(o+=`export const Roles = {
595
+ `;return t+=ap(sp(e),2),t+=`}
596
+ `,t}function ap(e,t){let n=" ".repeat(t),s="";for(let r of e.entries){if(r.child){s+=`${n}${Be(r.key)}: {
597
+ `,s+=ap(r.child,t+2),s+=`${n}};
598
+ `;continue}r.op&&(s+=`${n}${Gg(r.key)}${xg(r.op)};
599
+ `)}return s}function xg(e){let t=os(e.operationId),n=[],s=new Set;for(let a of e.pathParams)n.push(`${Ug(a,s)}: string`);e.input?n.push(`input: ${t}Request`):e.query&&n.push(`query: ${t}Query`),e.upload?n.push("file: Blob","options?: UploadOptions"):n.push(Mg(e.headers));let r=e.sse?`Promise<AsyncIterable<${e.output?`${t}Response`:"unknown"}>>`:e.output?`Promise<${t}Response>`:"Promise<void>";return`(${n.join(", ")}): ${r}`}function Mg(e){if(!e||e.props.length===0)return"options?: CallOptions";let t=e.props.some(r=>r.required),s=`{ ${e.props.map(r=>{let a=r.required?"":"?";return`${Be(r.name)}${a}: ${vt(r.schema)}`}).join("; ")} }`;return t?`options: CallOptions & { headers: ${s} }`:`options?: CallOptions & { headers?: ${s} }`}function Ug(e,t){let n=rs(e,!1);(Kg.has(n)||n==="input"||n==="query"||n==="options")&&(n+="_");let s=n;for(let r=2;t.has(s);r++)s=n+r;return t.add(s),s}var Kg=new Set("await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield implements interface let package private protected public static arguments eval".split(" "));function Be(e){return es.test(e)?e:re(e)}function Gg(e){return e==="new"?re(e):Be(e)}var Bg=/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ud800-\udfff]/;function ip(e,t){switch(e){case"\\":return"\\\\";case`
600
+ `:return"\\n";case"\r":return"\\r";case"'":return t?"\\'":e}return e.length===1&&Bg.test(e)?`\\u${e.charCodeAt(0).toString(16).padStart(4,"0")}`:e}function re(e){let t="'";for(let n of e)t+=ip(n,!0);return`${t}'`}function se(e){let t="";for(let n of e)t+=n==="@"?"\\u0040":n==="#"?"\\u0023":ip(n,!1);return t.replaceAll("*/","*\\/")}function Jg(e){return`${Ce(e)}Room`}function zg(e,t){let n=Jg(e.pattern),s=[];for(let a of e.events)s.push(...Kt(t.claim(`${n}${Ce(a.name)}Event`,`${kr(e)}'s event ${JSON.stringify(a.name)}`),a.schema));for(let a of e.messages)s.push(...Kt(t.claim(`${n}${Ce(a.name)}Message`,`${kr(e)}'s message ${JSON.stringify(a.name)}`),a.schema));for(let a of[`${n}Event`,`${n}Params`,n])t.claim(a,kr(e));if(e.events.length>0){s.push(`export type ${n}Event =`);for(let a of e.events)s.push(` | { event: ${re(a.name)}; payload: ${n}${Ce(a.name)}Event }`);s[s.length-1]+=";"}else s.push(`export type ${n}Event = never;`);s.push("");let r=e.params.map(a=>`${Be(a)}: string`).join("; ");return s.push(`export interface ${n}Params { ${r} }`),s.push(`export const ${n} = {`),s.push(` pattern: ${re(e.pattern)},`),s.push(` params: [${hn(e.params)}] as const,`),s.push(` events: [${hn(e.events.map(a=>a.name))}] as const,`),s.push(` messages: [${hn(e.messages.map(a=>a.name))}] as const,`),s.push(` send: {} as { ${e.messages.map(a=>`${Be(a.name)}: ${n}${Ce(a.name)}Message`).join("; ")} },`),s.push(` event: {} as ${n}Event,`),s.push("} as const;"),s.push(""),s.join(`
601
+ `)}function Wg(e){if(typeof e!="object"||e===null||!("roles"in e)||!Array.isArray(e.roles))throw new Error("roles artifact has no roles list");return e.roles.map((t,n)=>{if(typeof t!="object"||t===null)throw new Error(`roles artifact entry ${n} is not a role`);if(!("name"in t)||typeof t.name!="string"||t.name==="")throw new Error(`roles artifact entry ${n} has no name`);let s=t.name;if(!("isDefault"in t)||typeof t.isDefault!="boolean")throw new Error(`role "${s}" does not say whether it is the default`);if(!("permissions"in t)||!Array.isArray(t.permissions))throw new Error(`role "${s}" does not carry a list of permission names`);let r=[];for(let i of t.permissions){if(typeof i!="string")throw new Error(`role "${s}" lists a permission that is not a name`);r.push(i)}let a={name:s,isDefault:t.isDefault,permissions:r};return"description"in t&&typeof t.description=="string"&&t.description!==""&&(a.description=t.description),a})}function Fg(e){let t=Wg(e).sort((p,u)=>p.name<u.name?-1:p.name>u.name?1:0),n=[...new Set(t.flatMap(p=>p.permissions))].sort(),s=[],r=new Map;for(let p of t){let u=rs(p.name,!1),d=r.get(u);if(d!==void 0){s.push(` // codegen: skipped duplicate role "${se(p.name)}" \u2014 collides with "${se(d)}" (both map to \`${se(u)}\`)`);continue}r.set(u,p.name),p.description!==void 0&&s.push(` /** ${se(p.description)} */`),p.isDefault&&s.push(" /** Assigned to every new user at signup. */"),s.push(` ${u}: ${re(p.name)},`)}let a=[],i=new Map;for(let p of n){let u=rs(p,!1),d=i.get(u);if(d!==void 0){a.push(` // codegen: skipped duplicate permission "${se(p)}" \u2014 collides with "${se(d)}" (both map to \`${se(u)}\`)`);continue}i.set(u,p),a.push(` ${u}: ${re(p)},`)}let o="";if(r.size>0&&(o+=`export const Roles = {
602
602
  `,o+=`${s.join(`
603
603
  `)}
604
604
  `,o+=`} as const;
@@ -622,22 +622,22 @@ public typealias JSONValue = JsonElement
622
622
  `,l+=`// than silently losing access.
623
623
  `,l+=`
624
624
  `,l+o}var Vg=/^[a-z][A-Za-z0-9]{0,62}$/,Hg=new Set(["break","case","catch","class","const","continue","debugger","default","delete","do","else","enum","export","extends","false","finally","for","function","if","import","in","instanceof","new","null","return","super","switch","this","throw","true","try","typeof","var","void","while","with","yield","let","static","implements","interface","package","private","protected","public","await","async","associatedtype","deinit","extension","fileprivate","func","init","inout","internal","operator","precedencegroup","protocol","rethrows","struct","subscript","typealias","defer","fallthrough","guard","repeat","where","as","is","nil","self","throws","some","any","open","fun","object","typeof","val","when","interface","package","throw","try"]);function Pr(e){return e.replace(/-([a-z0-9])/g,(t,n)=>n.toUpperCase())}function Xg(e){if(!Vg.test(e))return`${JSON.stringify(e)} is not a name of one: a letter a\u2013z, then letters and digits (shopApi), at most 63`;if(Sr.includes(e))return`${e} is the client's own (pb.${e}) on one of the platforms \u2014 the backend is named another way`;if(Hg.has(e))return`${e} is a word TypeScript, Swift or Kotlin keeps`;if(!ns(e))return`${e} is a name every object has (pb.${e}) \u2014 the backend is named another way`}function ls(e,t){let n=new Map,s=new Map;for(let r of[...new Set(e)].sort()){let a=Object.hasOwn(t,r)?t[r]:void 0,i=a??Pr(r),o=Xg(i);if(o)throw new Error(a!==void 0?`--as ${r}=${i}: ${o}`:`${r}'s name in the apps, ${i}: ${o} \u2014 name it (palbase apps:sync --as ${r}=<name>)`);let l=s.get(i);if(l!==void 0)throw new Error(`${l} and ${r} would both be pb.${i}: name one of them (palbase apps:sync --as ${r}=<name>)`);s.set(i,r),n.set(r,i)}return n}var B="palbase",bn="// Code generated by palbase apps:sync",Gt=e=>`${JSON.stringify(e,null,2)}
625
- `,ah={environment:/^[A-Za-z][A-Za-z0-9-]{0,38}$/,app:/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/,machine:/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,digest:/^[0-9a-f]{64}$/};function yn(e,t){if(typeof t!="string"||!ah[e].test(t))throw new m("unexpected_answer",`the project answered ${e} ${JSON.stringify(t)}, which is not one`);return t}function Rr(e){return String(e).replace(/[\u0000-\u001f\u007f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g," ")}function ih(e){return e==="web"?"ts":e==="ios"?"swift":e==="android"?"kotlin":null}var ld=e=>e.charAt(0).toUpperCase()+e.slice(1);function oh(e,t,n=[]){let s={url:t.url,publishableKey:t.publishable_key,environment:t.environment.slug,app:e.slug},r=[...n].sort((a,i)=>a.client.localeCompare(i.client));return[`${bn} \u2014 DO NOT EDIT.`,`// The settings of ${e.slug} (${Rr(e.name)}) in ${t.environment.slug} (${Rr(t.environment.name)}): what it is built with. Digest: ${Rr(t.digest)}`,"import { createClient, type ClientOptions, type PalbaseClient } from 'palbase/web';",...r.flatMap(a=>[`import './palbe/${a.machine}';`,`import type { Backend as ${ld(a.client)}Backend } from './palbe/${a.machine}';`]),...r.length===1?[`export * from './palbe/${r[0]?.machine}';`]:[],"",`export const settings = ${JSON.stringify(s,null,2)} as const;`,"",`/** The app's client: its project's modules (pb.auth, \u2026) and its backends${r.map(a=>`, pb.${a.client}`).join("")}. */`,`export type Client = PalbaseClient${r.length>0?` & { ${r.map(a=>`${a.client}: ${ld(a.client)}Backend`).join("; ")} }`:""};`,"","let client: Client | undefined;","","/**"," * The app's client, made once with these settings \u2014 and with options, when"," * connect(options) is called before anything uses the client (pb)."," */","export function connect(options: Omit<ClientOptions, 'url' | 'publishableKey'> = {}): Client {"," if (client) {"," if (Object.keys(options).length > 0) throw new Error('palbase: connect(options) comes before the client is first used \u2014 call it as the app starts');"," return client;"," }"," client = createClient({ ...options, url: settings.url, publishableKey: settings.publishableKey }) as Client;"," return client;","}","","/** The app's client, made when it is first used: pb.<backend>.<controller>.<method>(), pb.auth, \u2026 */","export const pb: Client = new Proxy({} as Client, {"," get(_, key) {"," const c = connect();"," const value: unknown = Reflect.get(c, key, c);"," return typeof value === 'function' ? value.bind(c) : value;"," },"," has: (_, key) => Reflect.has(connect(), key),","});",""].join(`
625
+ `,ah={environment:/^[A-Za-z][A-Za-z0-9-]{0,38}$/,app:/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/,machine:/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/,digest:/^[0-9a-f]{64}$/};function yn(e,t){if(typeof t!="string"||!ah[e].test(t))throw new m("unexpected_answer",`the project answered ${e} ${JSON.stringify(t)}, which is not one`);return t}function Rr(e){return String(e).replace(/[\u0000-\u001f\u007f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g," ")}function ih(e){return e==="web"?"ts":e==="ios"?"swift":e==="android"?"kotlin":null}var lp=e=>e.charAt(0).toUpperCase()+e.slice(1);function oh(e,t,n=[]){let s={url:t.url,publishableKey:t.publishable_key,environment:t.environment.slug,app:e.slug},r=[...n].sort((a,i)=>a.client.localeCompare(i.client));return[`${bn} \u2014 DO NOT EDIT.`,`// The settings of ${e.slug} (${Rr(e.name)}) in ${t.environment.slug} (${Rr(t.environment.name)}): what it is built with. Digest: ${Rr(t.digest)}`,"import { createClient, type ClientOptions, type PalbaseClient } from 'palbase/web';",...r.flatMap(a=>[`import './palbe/${a.machine}';`,`import type { Backend as ${lp(a.client)}Backend } from './palbe/${a.machine}';`]),...r.length===1?[`export * from './palbe/${r[0]?.machine}';`]:[],"",`export const settings = ${JSON.stringify(s,null,2)} as const;`,"",`/** The app's client: its project's modules (pb.auth, \u2026) and its backends${r.map(a=>`, pb.${a.client}`).join("")}. */`,`export type Client = PalbaseClient${r.length>0?` & { ${r.map(a=>`${a.client}: ${lp(a.client)}Backend`).join("; ")} }`:""};`,"","let client: Client | undefined;","","/**"," * The app's client, made once with these settings \u2014 and with options, when"," * connect(options) is called before anything uses the client (pb)."," */","export function connect(options: Omit<ClientOptions, 'url' | 'publishableKey'> = {}): Client {"," if (client) {"," if (Object.keys(options).length > 0) throw new Error('palbase: connect(options) comes before the client is first used \u2014 call it as the app starts');"," return client;"," }"," client = createClient({ ...options, url: settings.url, publishableKey: settings.publishableKey }) as Client;"," return client;","}","","/** The app's client, made when it is first used: pb.<backend>.<controller>.<method>(), pb.auth, \u2026 */","export const pb: Client = new Proxy({} as Client, {"," get(_, key) {"," const c = connect();"," const value: unknown = Reflect.get(c, key, c);"," return typeof value === 'function' ? value.bind(c) : value;"," },"," has: (_, key) => Reflect.has(connect(), key),","});",""].join(`
626
626
  `)}async function lh(e,t){let n=await eh(Oe(sh(),"palbase-contract-")),s=Oe(n,"openapi.json");try{let r=await e.io.run("bun",["--no-env-file","run","src/main.ts"],{cwd:t,env:{PALBASE_CONTRACT_OUT:s},envOnly:!0}),a;try{a=await Er(s,"utf8")}catch{throw new m("no_contract",`the backend in ${t} wrote no contract (bun run src/main.ts exited ${r.code})`,{hint:`${(r.stderr||r.stdout).trim().split(`
627
- `).slice(-3).join(" ").slice(0,300)||"is it a palbase/backend app, started from src/main.ts?"}`})}return JSON.parse(a)}finally{await pd(n,{recursive:!0,force:!0})}}function dh(e,t){if(typeof e!="string"||e==="")return;let n=e.indexOf("=");return n>0&&/^[a-z][a-z0-9-]{0,62}$/.test(e.slice(0,n))?{machine:e.slice(0,n),dir:od(t,e.slice(n+1))}:{dir:od(t,e)}}async function ph(e,t){let n=await dt(e);if(t&&t.project.id!==n.id)throw new m("another_project",`this checkout's ${B}/ is ${t.project.name}'s, not ${n.name}'s`,{hint:`run it for ${t.project.name} (-P), or in that project's own checkout`});let s=await $e(e,n.id),r=[...new Set([...t?.apps.map(k=>k.id)??[],...e.args])];if(r.length===0)throw g("name the app(s) this checkout builds: palbase apps:sync <app>",`the project's apps: ${s.map(k=>k.slug).join(", ")||"none \u2014 palbase apps:create"}`);let a=[];for(let k of r){let V=Ne(s,k);if(yn("app",V.slug),V.deleted_at)throw new m("app_deleted",`${V.name} is deleted (restorable until ${V.purge_after})`,{hint:`palbase apps:restore ${V.slug} \u2014 the sync writes nothing meanwhile`});a.some(O=>O.id===V.id)||a.push(V)}let i=new Map,o=[],l=new Map,d=new Map,u=new Map,p=dh(e.flags.backend,e.io.cwd),f=new Map,y=new Map,v=new Map,T=new Map;for(let k of a){let V=ih(k.platform);k.environments.length===0&&o.push(`${k.name} is in no environment yet: palbase apps:env:set ${k.slug} -E <env> \u2026`);for(let O of k.environments){if(O.suspended)continue;let z=await e.studio.get(`/environments/${O.ref}/apps/${k.id}/config`),Z=yn("environment",z.environment.slug),ie=[...l.keys()].find(pe=>pe!==Z&&pe.toLowerCase()===Z.toLowerCase());if(ie!==void 0)throw new m("environments_collide",`${ie} and ${Z} differ only in case: their files under ${B}/environments/ would be one directory's on macOS and Windows`,{hint:"rename one of the environments"});l.set(Z,z.environment),d.set(Z,z.url),i.set(`${B}/environments/${Z}/${k.slug}.json`,Gt(z)),k.platform==="ios"&&T.set(k,(T.get(k)??new Map).set(Z,Gt(z))),k.platform==="web"&&(y.set(k,(y.get(k)??new Map).set(Z,z)),v.set(k.slug,(v.get(k.slug)??new Set).add(Z))),V&&f.set(Z,(f.get(Z)??new Set).add(V));for(let pe of z.backends){if(yn("machine",pe.machine),pe.contract_digest!==void 0&&yn("digest",pe.contract_digest),!pe.contract_digest&&!(p&&(p.machine??pe.machine)===pe.machine)){o.push(`${pe.machine} in ${Z} has no contract kept yet \u2014 it is read once its build's version alone serves (a palbase/backend app, or one given --openapi-path): no Palbe client for it`);continue}let $t=u.get(Z)??new Map;$t.set(pe.machine,{url:pe.url,digest:pe.contract_digest??""}),u.set(Z,$t)}}}let A=new Set([...u.values()].flatMap(k=>[...k.keys()]));if(p&&p.machine===void 0){if(A.size!==1)throw g(`--backend ${String(e.flags.backend)}: name the machine it is (--backend <machine>=<dir>)`,`this checkout's apps reach ${[...A].join(", ")||"no backend"}`);p.machine=[...A][0]}if(p&&!A.has(p.machine))throw g(`--backend: ${p.machine} is not a backend this checkout's apps reach`,`they reach ${[...A].join(", ")||"none"}`);let P=p?await lh(e,p.dir):void 0;p&&o.push(`${p.machine}'s Palbe is made from the backend in ${p.dir}, not from what its machine serves: sync again without --backend once it is deployed`);let S={...t?.clients??{}};for(let k of e.flags.as??[]){let V=k.indexOf("="),O=V>0?k.slice(0,V):"";if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(O))throw g(`--as ${k}: it is <machine>=<name>`,"palbase apps:sync --as shop-api=shop");if(!A.has(O))throw g(`--as ${k}: ${O} is not a backend this checkout's apps reach`,`they reach ${[...A].join(", ")||"none"}`);let z=k.slice(V+1);z===""||z===Pr(O)?delete S[O]:S[O]=z}let C;try{C=ls(A,S)}catch(k){throw new m("invalid_backend_name",k.message,{hint:"a name is a letter a\u2013z, then letters and digits; not one of the client's own (auth, flags, storage, \u2026) or a word a language keeps"})}for(let k of Object.keys(S))A.has(k)||delete S[k];let $=new Map,K=new Map,X=(k,V,O)=>[`${bn.slice(3)} \u2014 DO NOT EDIT.`,`${k}'s client in ${V}, from its contract (contracts/${k}.openapi.json, sha256 ${O||"of this checkout"}): palbase apps:sync makes it again`];for(let[k,V]of[...u].sort(([O],[z])=>O.localeCompare(z))){let O=l.get(k)?.ref,z=f.get(k)??new Set,Z=new Map([["swift",new Map],["kotlin",new Map]]);for(let[ie,pe]of[...V].sort(([$t],[At])=>$t.localeCompare(At))){let $t=pe.url,At=C.get(ie),Ps=P!==void 0&&ie===p?.machine,en=Ps?P:await e.studio.get(`/environments/${O}/machines/${encodeURIComponent(ie)}/openapi`),Jp=Ps?Yg("sha256").update(Gt(en)).digest("hex"):pe.digest;i.set(`${B}/environments/${k}/contracts/${ie}.openapi.json`,Gt(en));let Rs=X(ie,k,Jp),Ts=(ut,zp)=>{try{return zp()}catch(Wp){throw new m("unusable_contract",`${ie}'s contract in ${k} makes no ${ut} client: ${Wp.message}`,{hint:`fix the backend, deploy it, and sync again${Ps?"":` (palbase machines:builds ${ie} says what its build wrote)`}`})}};if(z.has("ts")&&i.set(`${B}/environments/${k}/palbe/${ie}.ts`,Ts("TypeScript",()=>ed(en,{project:d.get(k),client:At,machine:ie,url:$t,header:Rs}))),z.has("swift")){let ut=Ts("Swift",()=>jl(en,{client:At,machine:ie,header:Rs}));dd(Z.get("swift"),uh(ut),ie,k,"Swift"),K.set(k,[...K.get(k)??[],ut])}if(z.has("kotlin")){let ut=Ts("Kotlin",()=>ml(en,{client:At,machine:ie,header:Rs}));dd(Z.get("kotlin"),ch(ut),ie,k,"Kotlin"),i.set(`${B}/environments/${k}/palbe/${ie}.kt`,ut)}$.set(k,[...$.get(k)??[],{machine:ie,client:At}])}}for(let[k,V]of y)for(let[O,z]of V)i.set(`${B}/environments/${O}/${k.slug}.ts`,oh(k,z,$.get(O)??[]));for(let[k,V]of T){let O={};for(let[z,Z]of[...V].sort(([ie],[pe])=>ie.localeCompare(pe)))O[z]={swift:Xo({environment:z,document:Z,backends:K.get(z)??[]})};i.set(`${B}/${k.slug}.palbase`,Gt({schema:1,app:k.slug,environments:O}))}let L=[...new Set([...v.values()].flatMap(k=>[...k]))].sort(),F=e.flags.types??t?.types_environment;if(F!==void 0&&yn("environment",F),F!==void 0&&L.length>0&&!L.includes(F))throw g(`--types ${F}: this checkout's web apps are set in ${L.join(", ")}`);if(F===void 0&&L.length>0&&(F=L.find(k=>!l.get(k)?.production)??L[0]),F!==void 0&&L.length>0){let k=O=>`${bn} \u2014 DO NOT EDIT. The editor's types are ${F}'s; a build imports its mode's environment (palbase() in vite.config).
627
+ `).slice(-3).join(" ").slice(0,300)||"is it a palbase/backend app, started from src/main.ts?"}`})}return JSON.parse(a)}finally{await dp(n,{recursive:!0,force:!0})}}function ph(e,t){if(typeof e!="string"||e==="")return;let n=e.indexOf("=");return n>0&&/^[a-z][a-z0-9-]{0,62}$/.test(e.slice(0,n))?{machine:e.slice(0,n),dir:op(t,e.slice(n+1))}:{dir:op(t,e)}}async function dh(e,t){let n=await pt(e);if(t&&t.project.id!==n.id)throw new m("another_project",`this checkout's ${B}/ is ${t.project.name}'s, not ${n.name}'s`,{hint:`run it for ${t.project.name} (-P), or in that project's own checkout`});let s=await $e(e,n.id),r=[...new Set([...t?.apps.map(k=>k.id)??[],...e.args])];if(r.length===0)throw g("name the app(s) this checkout builds: palbase apps:sync <app>",`the project's apps: ${s.map(k=>k.slug).join(", ")||"none \u2014 palbase apps:create"}`);let a=[];for(let k of r){let V=Ne(s,k);if(yn("app",V.slug),V.deleted_at)throw new m("app_deleted",`${V.name} is deleted (restorable until ${V.purge_after})`,{hint:`palbase apps:restore ${V.slug} \u2014 the sync writes nothing meanwhile`});a.some(O=>O.id===V.id)||a.push(V)}let i=new Map,o=[],l=new Map,p=new Map,u=new Map,d=ph(e.flags.backend,e.io.cwd),f=new Map,y=new Map,v=new Map,T=new Map;for(let k of a){let V=ih(k.platform);k.environments.length===0&&o.push(`${k.name} is in no environment yet: palbase apps:env:set ${k.slug} -E <env> \u2026`);for(let O of k.environments){if(O.suspended)continue;let z=await e.studio.get(`/environments/${O.ref}/apps/${k.id}/config`),Z=yn("environment",z.environment.slug),ie=[...l.keys()].find(de=>de!==Z&&de.toLowerCase()===Z.toLowerCase());if(ie!==void 0)throw new m("environments_collide",`${ie} and ${Z} differ only in case: their files under ${B}/environments/ would be one directory's on macOS and Windows`,{hint:"rename one of the environments"});l.set(Z,z.environment),p.set(Z,z.url),i.set(`${B}/environments/${Z}/${k.slug}.json`,Gt(z)),k.platform==="ios"&&T.set(k,(T.get(k)??new Map).set(Z,Gt(z))),k.platform==="web"&&(y.set(k,(y.get(k)??new Map).set(Z,z)),v.set(k.slug,(v.get(k.slug)??new Set).add(Z))),V&&f.set(Z,(f.get(Z)??new Set).add(V));for(let de of z.backends){if(yn("machine",de.machine),de.contract_digest!==void 0&&yn("digest",de.contract_digest),!de.contract_digest&&!(d&&(d.machine??de.machine)===de.machine)){o.push(`${de.machine} in ${Z} has no contract kept yet \u2014 it is read once its build's version alone serves (a palbase/backend app, or one given --openapi-path): no Palbe client for it`);continue}let $t=u.get(Z)??new Map;$t.set(de.machine,{url:de.url,digest:de.contract_digest??""}),u.set(Z,$t)}}}let A=new Set([...u.values()].flatMap(k=>[...k.keys()]));if(d&&d.machine===void 0){if(A.size!==1)throw g(`--backend ${String(e.flags.backend)}: name the machine it is (--backend <machine>=<dir>)`,`this checkout's apps reach ${[...A].join(", ")||"no backend"}`);d.machine=[...A][0]}if(d&&!A.has(d.machine))throw g(`--backend: ${d.machine} is not a backend this checkout's apps reach`,`they reach ${[...A].join(", ")||"none"}`);let P=d?await lh(e,d.dir):void 0;d&&o.push(`${d.machine}'s Palbe is made from the backend in ${d.dir}, not from what its machine serves: sync again without --backend once it is deployed`);let S={...t?.clients??{}};for(let k of e.flags.as??[]){let V=k.indexOf("="),O=V>0?k.slice(0,V):"";if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(O))throw g(`--as ${k}: it is <machine>=<name>`,"palbase apps:sync --as shop-api=shop");if(!A.has(O))throw g(`--as ${k}: ${O} is not a backend this checkout's apps reach`,`they reach ${[...A].join(", ")||"none"}`);let z=k.slice(V+1);z===""||z===Pr(O)?delete S[O]:S[O]=z}let C;try{C=ls(A,S)}catch(k){throw new m("invalid_backend_name",k.message,{hint:"a name is a letter a\u2013z, then letters and digits; not one of the client's own (auth, flags, storage, \u2026) or a word a language keeps"})}for(let k of Object.keys(S))A.has(k)||delete S[k];let $=new Map,K=new Map,X=(k,V,O)=>[`${bn.slice(3)} \u2014 DO NOT EDIT.`,`${k}'s client in ${V}, from its contract (contracts/${k}.openapi.json, sha256 ${O||"of this checkout"}): palbase apps:sync makes it again`];for(let[k,V]of[...u].sort(([O],[z])=>O.localeCompare(z))){let O=l.get(k)?.ref,z=f.get(k)??new Set,Z=new Map([["swift",new Map],["kotlin",new Map]]);for(let[ie,de]of[...V].sort(([$t],[At])=>$t.localeCompare(At))){let $t=de.url,At=C.get(ie),Ps=P!==void 0&&ie===d?.machine,en=Ps?P:await e.studio.get(`/environments/${O}/machines/${encodeURIComponent(ie)}/openapi`),Jd=Ps?Yg("sha256").update(Gt(en)).digest("hex"):de.digest;i.set(`${B}/environments/${k}/contracts/${ie}.openapi.json`,Gt(en));let Rs=X(ie,k,Jd),Ts=(ut,zd)=>{try{return zd()}catch(Wd){throw new m("unusable_contract",`${ie}'s contract in ${k} makes no ${ut} client: ${Wd.message}`,{hint:`fix the backend, deploy it, and sync again${Ps?"":` (palbase machines:builds ${ie} says what its build wrote)`}`})}};if(z.has("ts")&&i.set(`${B}/environments/${k}/palbe/${ie}.ts`,Ts("TypeScript",()=>ep(en,{project:p.get(k),client:At,machine:ie,url:$t,header:Rs}))),z.has("swift")){let ut=Ts("Swift",()=>jl(en,{client:At,machine:ie,header:Rs}));pp(Z.get("swift"),uh(ut),ie,k,"Swift"),K.set(k,[...K.get(k)??[],ut])}if(z.has("kotlin")){let ut=Ts("Kotlin",()=>ml(en,{client:At,machine:ie,header:Rs}));pp(Z.get("kotlin"),ch(ut),ie,k,"Kotlin"),i.set(`${B}/environments/${k}/palbe/${ie}.kt`,ut)}$.set(k,[...$.get(k)??[],{machine:ie,client:At}])}}for(let[k,V]of y)for(let[O,z]of V)i.set(`${B}/environments/${O}/${k.slug}.ts`,oh(k,z,$.get(O)??[]));for(let[k,V]of T){let O={};for(let[z,Z]of[...V].sort(([ie],[de])=>ie.localeCompare(de)))O[z]={swift:Xo({environment:z,document:Z,backends:K.get(z)??[]})};i.set(`${B}/${k.slug}.palbase`,Gt({schema:1,app:k.slug,environments:O}))}let L=[...new Set([...v.values()].flatMap(k=>[...k]))].sort(),F=e.flags.types??t?.types_environment;if(F!==void 0&&yn("environment",F),F!==void 0&&L.length>0&&!L.includes(F))throw g(`--types ${F}: this checkout's web apps are set in ${L.join(", ")}`);if(F===void 0&&L.length>0&&(F=L.find(k=>!l.get(k)?.production)??L[0]),F!==void 0&&L.length>0){let k=O=>`${bn} \u2014 DO NOT EDIT. The editor's types are ${F}'s; a build imports its mode's environment (palbase() in vite.config).
628
628
  ${O}`,V=(O,z)=>`${bn} \u2014 DO NOT EDIT. ${O}'s, which a build made for ${O} type-checks the app against (palbase() in vite.config).
629
629
  ${z}`;for(let[O,z]of v){z.has(F)?i.set(`${B}/${O}.ts`,k(`export * from './environments/${F}/${O}';
630
630
  `)):o.push(`${O} is not in ${F}: the editor has no types for it (palbase apps:sync --types <env>)`);for(let Z of z)i.set(`${B}/${O}.${Z}.ts`,V(Z,`export * from './environments/${Z}/${O}';
631
631
  `))}for(let{machine:O}of $.get(F)??[])i.set(`${B}/palbe/${O}.ts`,k(`export * from '../environments/${F}/palbe/${O}';
632
632
  `));for(let O of L)for(let{machine:z}of $.get(O)??[])i.set(`${B}/palbe/${z}.${O}.ts`,V(O,`export * from '../environments/${O}/palbe/${z}';
633
- `))}let kt={schema:1,project:{id:n.id,name:n.name},apps:a.map(k=>({id:k.id,slug:k.slug,name:k.name,platform:k.platform})),...F!==void 0&&L.length>0?{types_environment:F}:{},...Object.keys(S).length>0?{clients:Object.fromEntries(Object.entries(S).sort(([k],[V])=>k.localeCompare(V)))}:{}};return i.set(`${B}/project.json`,Gt(kt)),{files:i,notes:o,project:kt}}function uh(e){return[...e.matchAll(/^public (?:nonisolated )?(?:struct|enum|typealias|final class|class|protocol) ([A-Za-z_][A-Za-z0-9_]*)/gm)].map(t=>t[1])}function ch(e){return[...e.matchAll(/^(?:(?:data|sealed|enum|value|internal|private|public) )*(?:class|object|interface|typealias) ([A-Za-z_][A-Za-z0-9_]*)|^val ([A-Za-z_][A-Za-z0-9_.]*\.[A-Za-z_][A-Za-z0-9_]*):/gm)].map(t=>t[1]??t[2])}function dd(e,t,n,s,r){for(let a of t){let i=e.get(a);if(i!==void 0&&i!==n)throw new m("backends_collide",`${i} and ${n} both declare ${a} in ${s}'s ${r} client, which one target cannot hold`,{hint:"rename the controller or route in one of them (it names the type), or reach them from different apps"});e.set(a,n)}}async function mh(e){let t=new Map,n=async s=>{let r;try{r=await Tr(Oe(e,s),{withFileTypes:!0})}catch(a){if(a.code==="ENOENT")return;throw a}for(let a of r){let i=`${s}/${a.name}`;if(a.isDirectory()){(s!==B||a.name==="environments"||a.name==="palbe")&&await n(i);continue}if(!a.isFile()||a.name.endsWith(".storekit"))continue;let o=await Er(Oe(e,i),"utf8");(s!==B||a.name==="project.json"||a.name.endsWith(".palbase")||o.startsWith(bn))&&t.set(i,o)}};return await n(B),t}async function Cr(e){try{return JSON.parse(await Er(Oe(e,B,"project.json"),"utf8"))}catch(t){if(t.code==="ENOENT")return null;throw new m("unreadable_project_file",`${B}/project.json is not what palbase apps:sync writes: ${t.message}`)}}async function ud(e){let t=e.io.cwd,n=e.flags.check===!0,s=await Cr(t),r=await ph(e,s),a=await mh(t),i=[],o=[],l=[];for(let[p,f]of r.files){let y=a.get(p);y===void 0?i.push(p):y!==f&&o.push(p)}for(let p of a.keys())r.files.has(p)||l.push(p);let d=i.length+o.length+l.length>0;if(!n){for(let p of[...i,...o]){let f=await Bt(t,p);await Qg(rh(f),{recursive:!0}),await Bt(t,p),await nh(f,r.files.get(p))}for(let p of l)await pd(await Bt(t,p));await cd(t,Oe(t,B))}let u={project:r.project.project.name,apps:r.project.apps.map(p=>p.slug),added:i,changed:o,removed:l,unchanged:r.files.size-i.length-o.length,check:n,differs:d};for(let p of r.notes)e.out.note(p);return e.out.result(u,()=>{let p=(f,y)=>y.forEach(v=>e.out.line(` ${f} ${v}`));if(!d){e.out.line(`${B}/ is what ${r.project.project.name} gives now (${r.files.size} files)`);return}e.out.line(n?`${B}/ differs from what ${r.project.project.name} gives now:`:`${B}/ written for ${r.project.apps.map(f=>f.slug).join(", ")}:`),p(n?"missing":"added",i),p(n?"differs":"changed",o),p(n?"not given":"removed",l),n&&e.out.note("palbase apps:sync brings it up to date")}),n&&d?1:void 0}async function Bt(e,t){let n=Oe(e,B),s=Oe(e,...t.split("/")),r=ps(n,s);if(r===""||r.startsWith(`..${ds}`)||r===".."||r.split(ds).some(i=>i===".."||i===""))throw new m("outside_palbase",`${t} is not under ${B}/`);let a=e;for(let i of[B,...r.split(ds)]){a=Oe(a,i);try{if((await Zg(a)).isSymbolicLink())throw new m("linked_path",`${ps(e,a)} is a link: palbase writes only into ${B}/ itself`,{hint:"remove the link, and run it again"})}catch(o){if(o.code==="ENOENT")break;throw o}}return s}async function cd(e,t){let n;try{n=await Tr(t,{withFileTypes:!0})}catch{return}for(let s of n)s.isDirectory()&&await cd(e,Oe(t,s.name));ps(Oe(e,B),t)===""||ps(Oe(e,B),t).startsWith(`..${ds}`)||(await Tr(t)).length===0&&await th(t)}async function dt(e){let t=await Fe(e);return{id:t[0].project_id,name:t[0].project_name,org:t[0].org_name}}async function $e(e,t){return(await e.studio.get(`/projects/${t}/apps`)).apps}function Ne(e,t){let n=e.find(s=>s.slug===t||s.id===t);if(!n)throw new m("no_such_app",`the project has no app ${t}`,{hint:`its apps: ${e.map(s=>s.slug).join(", ")||"none \u2014 palbase apps:create"}`});return n}function us(e){let t=e.ios?[e.ios.bundle,e.ios.app_clip&&`App Clip ${e.ios.app_clip}`,e.ios.watch&&`watch ${e.ios.watch}`].filter(Boolean).join(", "):e.android?`${e.android.package} (${e.android.fingerprints.map(n=>n.label).join(", ")||"no certificate"})`:e.web?.origins.join(", ")??"";return e.suspended?`${t} \u2014 suspended`:t}var md={name:"apps:list",summary:"the project's apps, and what each is in every environment it is set in",scope:"environment",async run(e){let t=await dt(e),n=await $e(e,t.id);e.out.result({apps:n},()=>{if(n.length===0)return e.out.note(`${t.name} has no app yet: palbase apps:create ios|android|web <name>`);e.out.table(n,[{header:"slug",value:s=>s.slug},{header:"platform",value:s=>s.platform},{header:"name",value:s=>s.deleted_at?`${s.name} (deleted; restorable until ${s.purge_after?.slice(0,10)})`:s.name},{header:"environments",value:s=>s.environments.map(r=>`${r.slug}: ${us(r)}`).join("; ")||"\u2014"}])})}},gd={name:"apps:create",summary:"register an app of the project (an iOS app names its Apple team); then set it in each environment (apps:env:set)",usage:"ios|android|web <name> [--slug <slug>] [--team-id <id>] [--app-id-prefix <prefix>]",details:["An app is one product on one platform: its widgets, notification service and other extensions are not apps of their own \u2014 Apple gives them bundles under the app's, and they share its sign-in.","The slug is the app's file in a checkout: palbase/environments/<env>/<slug>.json."],args:{min:2,max:2},flags:{slug:{type:"string",value:"slug",description:"its name in a checkout (default: from the name)"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team: 10 characters, Apple Developer \u203A Membership"},"app-id-prefix":{type:"string",value:"prefix",description:"an iOS app's App ID prefix when it is not the team's (an app made before 2011, or moved from another team)"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="ios"&&t!=="android"&&t!=="web")throw g("the platform is ios, android or web");let s=await dt(e),r=await e.studio.post(`/projects/${s.id}/apps`,{platform:t,name:n,...e.flags.slug?{slug:e.flags.slug}:{},...e.flags["team-id"]?{team_id:e.flags["team-id"]}:{},...e.flags["app-id-prefix"]?{app_id_prefix:e.flags["app-id-prefix"]}:{}});e.out.result(r,()=>{e.out.line(`${s.name} \u203A ${r.name} (${r.platform}, ${r.slug})`),e.out.note(`set it in each environment: palbase apps:env:set ${r.slug} -E <env> ${t==="ios"?"--bundle <bundle id>":t==="android"?"--package <application id> --sha256 <label>=<fingerprint>":"--origin <https://\u2026>"}`)})}},hd={name:"apps:get",summary:"an app of the project, with what it is and its key in every environment it is set in",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await dt(e),n=Ne(await $e(e,t.id),e.args[0]);e.out.result(n,()=>{if(e.out.line(`${n.name} \u2014 ${n.platform}, ${n.slug}${n.team_id?`, team ${n.team_id}${n.app_id_prefix?` (App ID prefix ${n.app_id_prefix})`:""}`:""}`),n.deleted_at&&e.out.note(`deleted; restorable until ${n.purge_after} (palbase apps:restore ${n.slug})`),n.environments.length===0)return e.out.note(`in no environment yet: palbase apps:env:set ${n.slug} -E <env> \u2026`);e.out.table(n.environments,[{header:"environment",value:s=>s.slug},{header:"is",value:s=>us(s)},{header:"publishable key",value:s=>s.key?.key??"\u2014"}])})}},fd={name:"apps:update",summary:"rename an app, or change an iOS app's Apple team: every environment it is in is told",usage:'<app> [--name <name>] [--team-id <id>] [--app-id-prefix <prefix>|""]',args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team"},"app-id-prefix":{type:"string",value:"prefix",description:`its App ID prefix; "" for the team's`}},scope:"environment",async run(e){let t=await dt(e),n=Ne(await $e(e,t.id),e.args[0]),s={};if(e.flags.name!==void 0&&(s.name=e.flags.name),e.flags["team-id"]!==void 0&&(s.team_id=e.flags["team-id"]),e.flags["app-id-prefix"]!==void 0&&(s.app_id_prefix=e.flags["app-id-prefix"]===""?null:e.flags["app-id-prefix"]),Object.keys(s).length===0)throw g("nothing to change: --name, --team-id or --app-id-prefix");let r=await e.studio.request("PATCH",`/projects/${t.id}/apps/${n.id}`,s);e.out.result(r,()=>e.out.line(`${r.name} (${r.slug}) \u2014 told to ${r.environments.length} environment(s)`))}},yd={name:"apps:delete",summary:"delete an app: suspended in every environment for 30 days \u2014 its builds stop reaching the project \u2014 then removed for good",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await dt(e),n=Ne(await $e(e,t.id),e.args[0]);if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed",`deleting ${n.name} stops every build of it reaching the project`,{hint:"confirm with -y"});let r=(await e.io.prompt(`Every build of ${n.name} stops reaching ${t.name} at once (restorable for 30 days). Delete it? [y/N] `)).trim().toLowerCase();if(r!=="y"&&r!=="yes")throw new m("cancelled","nothing was changed")}let s=await e.studio.request("DELETE",`/projects/${t.id}/apps/${n.id}`);e.out.result(s,()=>e.out.line(`${s.name} is deleted; palbase apps:restore ${s.slug} brings it back as it was until ${s.purge_after}`))}},bd={name:"apps:restore",summary:"bring a deleted app back as it was, with its keys, in every environment",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await dt(e),n=Ne(await $e(e,t.id),e.args[0]),s=await e.studio.post(`/projects/${t.id}/apps/${n.id}/restore`);e.out.result(s,()=>e.out.line(`${s.name} is back in ${s.environments.length} environment(s)`))}},gh=/^([a-z][a-z0-9_-]{0,31})=(.+)$/,wd={name:"apps:env:set",summary:"what an app is in the environment: its bundle (iOS), package and signing certificates (Android) or origins (web)",usage:"<app> [--bundle <id> [--app-clip <id>] [--watch <id>]] [--package <id> --sha256 <label>=<fingerprint>\u2026] [--origin <url>\u2026 --redirect <url>\u2026]",details:["iOS: the app's bundle in this environment; its App Clip and watch app are the bundle followed by one more part. Its extensions are not set: they share the app's.","Android: its application ID and the SHA-256 of each certificate it is signed with \u2014 Play Console \u203A App signing (Google's app signing key, not the upload key), ./gradlew signingReport for a debug key. Label each: debug, play_signing, upload\u2026","Web: the origins its pages are served from (https; http only on this computer) and the addresses a sign-in may return to.","The platform publishes it to Apple and Google for the environment: a device sees a change in a day or so (Apple caches its file; Android 15 re-checks within a week)."],args:{min:1,max:1},flags:{bundle:{type:"string",value:"id",description:"an iOS app's bundle identifier here"},"app-clip":{type:"string",value:"id",description:"its App Clip's bundle (the bundle followed by one more part)"},watch:{type:"string",value:"id",description:"its watch app's bundle (the bundle followed by one more part)"},package:{type:"string",value:"id",description:"an Android app's application ID here"},sha256:{type:"string",multiple:!0,value:"label=fingerprint",description:"a signing certificate's SHA-256, labelled (play_signing=AB:CD:\u2026); every one the app is signed with"},origin:{type:"string",multiple:!0,value:"url",description:"a web app's origin"},redirect:{type:"string",multiple:!0,value:"url",description:"an address a sign-in may return to"}},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]),s=i=>(e.flags[i]??[]).flatMap(o=>o.split(",")).map(o=>o.trim()).filter(Boolean),r;if(n.platform==="ios"){if(!e.flags.bundle)throw g(`${n.name} is an iOS app: give its bundle here (--bundle)`);r={ios:{bundle:e.flags.bundle,...e.flags["app-clip"]?{app_clip:e.flags["app-clip"]}:{},...e.flags.watch?{watch:e.flags.watch}:{}}}}else if(n.platform==="android"){if(!e.flags.package)throw g(`${n.name} is an Android app: give its package here (--package)`);let i=(e.flags.sha256??[]).map(o=>{let l=gh.exec(o.trim());if(!l)throw g(`--sha256 ${JSON.stringify(o)}: label=fingerprint, like play_signing=AB:CD:\u2026`);return{label:l[1],sha256:l[2]}});r={android:{package:e.flags.package,fingerprints:i}}}else{let i=s("origin");if(i.length===0)throw g(`${n.name} is a web app: give its origins here (--origin)`);let o=s("redirect");r={web:{origins:i,...o.length?{redirects:o}:{}}}}await b(e,t,`set ${n.name} (${n.slug}) here: ${us(r)}`);let a=await e.studio.request("PUT",`/environments/${t.env_ref}/apps/${n.id}`,r);for(let i of a.warnings)e.out.warn(i);e.out.result(a,()=>{e.out.line(`${n.name} in ${t.env_name}: ${us(a)}`),a.key&&e.out.line(`its publishable key here: ${a.key.key}`),e.out.note("palbase apps:sync writes its settings into this checkout")})}},vd={name:"apps:env:unset",summary:"take an app out of the environment: its key there revoked, Apple's and Google's files no longer naming it",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]);await b(e,t,`take ${n.name} out of here: builds of it stop reaching ${t.env_name}`),await e.studio.request("DELETE",`/environments/${t.env_ref}/apps/${n.id}`),e.out.result({app:n.slug,environment:t.env_slug,removed:!0},()=>e.out.line(`${n.name} is out of ${t.env_name}`))}},_d={name:"apps:sdkconfig",summary:"an app's settings in the environment, as palbase apps:sync writes them: its address, its publishable key, what its modules tell it",usage:"<app> [--format json|env]",details:["The publishable key is public: it ships in the app. A secret key never does."],args:{min:1,max:1},flags:{format:{type:"string",value:"format",description:"json (default) or env"}},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]),s=e.flags.format??"json";if(s!=="json"&&s!=="env")throw g("--format is json or env");let r=await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`);e.out.result(r,()=>{s==="env"?(e.out.line(`PALBASE_URL=${r.url}`),e.out.line(`PALBASE_PUBLISHABLE_KEY=${r.publishable_key}`)):e.out.line(JSON.stringify(r,null,2))})}},Sd={name:"apps:sync",summary:"write each environment's settings and backend clients (Palbe) of this checkout's apps under palbase/ \u2014 nothing else is touched",usage:"[<app>\u2026] [--check] [--types <env>] [--backend [<machine>=]<dir>] [--as <machine>=<name>]\u2026",args:{min:0,max:20},details:["The apps named join this checkout's (palbase/project.json); with none named, its apps are synced.","palbase/environments/<env>/: each app's settings (<app>.json), each backend's contract (contracts/<machine>.openapi.json) and Palbe client made from it \u2014 TypeScript for a web app, Swift for an iOS app (in palbase/<app>.palbase), Kotlin for an Android app. A web app imports pb from palbase/<app>.ts: one environment's for the editor (--types), the build's mode's for the build (palbase() in vite.config), which type-checks the app against that environment.","--check writes nothing: it exits 1 naming what differs from the project now \u2014 for CI.","--backend makes a backend's Palbe from its checkout on this computer (its palbase/backend app writes its contract and exits; nothing is connected to), to try an endpoint before any machine serves it.","A backend is pb.<name> in the apps \u2014 pb.shopApi.rooms.create() for the machine shop-api \u2014 unless --as names it (--as shop-api=shop): the name is the checkout's, the same in every environment, and none of the client's own (auth, flags, storage, \u2026)."],flags:{check:{type:"boolean",description:"write nothing; exit 1 when the checkout is not what the project gives now"},types:{type:"string",value:"env",description:"the environment the editor's types are of (remembered in palbase/project.json)"},backend:{type:"string",value:"[machine=]dir",description:"a backend's checkout here: its Palbe made from the contract it writes, not from its machine's"},as:{type:"string",multiple:!0,value:"machine=name",description:"a backend's name in the apps (pb.<name>): its machine's camelCased unless named; the same in every environment (remembered in palbase/project.json)"}},scope:"environment",async run(e){return ud(e)}};import{readdir as cs,readFile as ms}from"node:fs/promises";import{dirname as hh,extname as fh,join as qe,relative as jr,sep as yh}from"node:path";var bh=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".swift",".kt"]),wh=new Set(["node_modules","dist","build",".build",".git","Pods","DerivedData","xcuserdata",".gradle","palbase"]),vh=/^[^\u0000-\u001f\u007f"\\]+$/,kd=/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/,_h=["call","stream","upload","join"],Sh=new Set(["auth","flags","realtime","analytics","calls","messaging","perf","storage","notify","configure"]);function kh(e){let t=new Set,n=e?.paths??{};for(let s of Object.values(n))for(let r of Object.values(s??{})){let i=(typeof r?.operationId=="string"?r.operationId:"").split(".")[0];/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&!Sh.has(i)&&t.add(i)}return t}async function $h(e,t){let n=[],s=async r=>{let a;try{a=await cs(r,{withFileTypes:!0})}catch(i){if(i.code==="ENOENT")return;throw i}for(let i of a){if(i.isSymbolicLink())continue;let o=qe(r,i.name);i.isDirectory()?!wh.has(i.name)&&!i.name.startsWith(".")&&await s(o):i.isFile()&&bh.has(fh(i.name))&&vh.test(jr(e,o))&&n.push(jr(e,o))}};for(let r of t)await s(qe(e,r));return[...new Set(n)].sort()}async function $d(e,t,n,s,r=new Map){if(s!==void 0&&!kd.test(s))throw new m("unexpected_answer",`${JSON.stringify(s)} is not a web app's slug`);for(let[p,f]of n){if(!/^[a-z][A-Za-z0-9]{0,62}$/.test(p))throw new m("unexpected_answer",`${JSON.stringify(p)} is not a backend's name in an app`);for(let y of f)if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(y))throw new m("unexpected_answer",`${JSON.stringify(y)} is not a backend's top-level name`)}let a=new Map;for(let[p,f]of n)for(let y of f){let v=a.get(y);if(v!==void 0)throw new m("ambiguous_name",`pb.${y} is ${v}'s and ${p}'s: a v2 call of it cannot be told which \u2014 move those calls by hand (pb.${v}.${y} or pb.${p}.${y})`);a.set(y,p)}if(n.size===1)for(let p of _h)a.set(p,[...n.keys()][0]);let i=[...a.keys()].sort((p,f)=>f.length-p.length).map(p=>p.replace(/\$/g,"\\$")).join("|"),o=i?new RegExp(`(?<![\\w.$])pb(\\s*\\.\\s*)(${i})(?![\\w$])`,"g"):void 0;for(let p of r.keys())if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(p))throw new m("unexpected_answer",`${JSON.stringify(p)} is not a machine's name`);let l=[],d=[],u=0;for(let p of await $h(e,t)){let f=await ms(qe(e,p),"utf8"),y=o?f.replace(o,(v,T,A)=>(u++,`pb${T}${a.get(A)}.${A}`)):f;if(s&&/\.(?:[cm]?[jt]sx?)$/.test(p)){let v=(...A)=>{let P=jr(hh(qe(e,p)),qe(e,"palbase",...A)).split(yh).join("/");return P.startsWith(".")?P:`./${P}`},T=v(s);y=y.replace(/import\s*\{\s*pb\s*\}\s*from\s*(['"])@palbase\/web\1/g,(A,P)=>`import { pb } from ${P}${T}${P}`),y=y.replace(/(\b(?:import|export)\s+(?:type\s+)?)(\{[^}]*\}|\*(?:\s+as\s+[A-Za-z_$][\w$]*)?|[A-Za-z_$][\w$]*)(\s*from\s*)(['"])((?:\.\.?\/)[^'"\n]*?palbe\.gen(?:\.[jt]s)?)\4/g,(A,P,S,C,$,K)=>{if(n.size<=1)return`${P}${S}${C}${$}${T}${$}`;let X=S.startsWith("{")?S.slice(1,-1).split(",").map(F=>F.trim().replace(/^type\s+/,"").split(/\s+as\s+/)[0]).filter(F=>F!==""):[],L=[...r].filter(([,F])=>X.length>0&&X.every(kt=>F.has(kt))).map(([F])=>F);return L.length!==1?(d.push(`${p}: its import from ${K} is no one backend's \u2014 ${X.length===0?"it takes the module whole":`${X.join(", ")} ${L.length===0?"are not all one backend's":`are ${L.join("'s and ")}'s`}`}: import them from palbase/palbe/<machine>`),A):`${P}${S}${C}${$}${v("palbe",L[0])}${$}`})}y!==f&&l.push({path:p,before:f,after:y})}return{changes:l,moved:u,left:d}}function Ad(e){let t=[];for(let n of e){let s=n.before.endsWith(`
633
+ `))}let kt={schema:1,project:{id:n.id,name:n.name},apps:a.map(k=>({id:k.id,slug:k.slug,name:k.name,platform:k.platform})),...F!==void 0&&L.length>0?{types_environment:F}:{},...Object.keys(S).length>0?{clients:Object.fromEntries(Object.entries(S).sort(([k],[V])=>k.localeCompare(V)))}:{}};return i.set(`${B}/project.json`,Gt(kt)),{files:i,notes:o,project:kt}}function uh(e){return[...e.matchAll(/^public (?:nonisolated )?(?:struct|enum|typealias|final class|class|protocol) ([A-Za-z_][A-Za-z0-9_]*)/gm)].map(t=>t[1])}function ch(e){return[...e.matchAll(/^(?:(?:data|sealed|enum|value|internal|private|public) )*(?:class|object|interface|typealias) ([A-Za-z_][A-Za-z0-9_]*)|^val ([A-Za-z_][A-Za-z0-9_.]*\.[A-Za-z_][A-Za-z0-9_]*):/gm)].map(t=>t[1]??t[2])}function pp(e,t,n,s,r){for(let a of t){let i=e.get(a);if(i!==void 0&&i!==n)throw new m("backends_collide",`${i} and ${n} both declare ${a} in ${s}'s ${r} client, which one target cannot hold`,{hint:"rename the controller or route in one of them (it names the type), or reach them from different apps"});e.set(a,n)}}async function mh(e){let t=new Map,n=async s=>{let r;try{r=await Tr(Oe(e,s),{withFileTypes:!0})}catch(a){if(a.code==="ENOENT")return;throw a}for(let a of r){let i=`${s}/${a.name}`;if(a.isDirectory()){(s!==B||a.name==="environments"||a.name==="palbe")&&await n(i);continue}if(!a.isFile()||a.name.endsWith(".storekit"))continue;let o=await Er(Oe(e,i),"utf8");(s!==B||a.name==="project.json"||a.name.endsWith(".palbase")||o.startsWith(bn))&&t.set(i,o)}};return await n(B),t}async function Cr(e){try{return JSON.parse(await Er(Oe(e,B,"project.json"),"utf8"))}catch(t){if(t.code==="ENOENT")return null;throw new m("unreadable_project_file",`${B}/project.json is not what palbase apps:sync writes: ${t.message}`)}}async function up(e){let t=e.io.cwd,n=e.flags.check===!0,s=await Cr(t),r=await dh(e,s),a=await mh(t),i=[],o=[],l=[];for(let[d,f]of r.files){let y=a.get(d);y===void 0?i.push(d):y!==f&&o.push(d)}for(let d of a.keys())r.files.has(d)||l.push(d);let p=i.length+o.length+l.length>0;if(!n){for(let d of[...i,...o]){let f=await Bt(t,d);await Qg(rh(f),{recursive:!0}),await Bt(t,d),await nh(f,r.files.get(d))}for(let d of l)await dp(await Bt(t,d));await cp(t,Oe(t,B))}let u={project:r.project.project.name,apps:r.project.apps.map(d=>d.slug),added:i,changed:o,removed:l,unchanged:r.files.size-i.length-o.length,check:n,differs:p};for(let d of r.notes)e.out.note(d);return e.out.result(u,()=>{let d=(f,y)=>y.forEach(v=>e.out.line(` ${f} ${v}`));if(!p){e.out.line(`${B}/ is what ${r.project.project.name} gives now (${r.files.size} files)`);return}e.out.line(n?`${B}/ differs from what ${r.project.project.name} gives now:`:`${B}/ written for ${r.project.apps.map(f=>f.slug).join(", ")}:`),d(n?"missing":"added",i),d(n?"differs":"changed",o),d(n?"not given":"removed",l),n&&e.out.note("palbase apps:sync brings it up to date")}),n&&p?1:void 0}async function Bt(e,t){let n=Oe(e,B),s=Oe(e,...t.split("/")),r=ds(n,s);if(r===""||r.startsWith(`..${ps}`)||r===".."||r.split(ps).some(i=>i===".."||i===""))throw new m("outside_palbase",`${t} is not under ${B}/`);let a=e;for(let i of[B,...r.split(ps)]){a=Oe(a,i);try{if((await Zg(a)).isSymbolicLink())throw new m("linked_path",`${ds(e,a)} is a link: palbase writes only into ${B}/ itself`,{hint:"remove the link, and run it again"})}catch(o){if(o.code==="ENOENT")break;throw o}}return s}async function cp(e,t){let n;try{n=await Tr(t,{withFileTypes:!0})}catch{return}for(let s of n)s.isDirectory()&&await cp(e,Oe(t,s.name));ds(Oe(e,B),t)===""||ds(Oe(e,B),t).startsWith(`..${ps}`)||(await Tr(t)).length===0&&await th(t)}async function pt(e){let t=await Fe(e);return{id:t[0].project_id,name:t[0].project_name,org:t[0].org_name}}async function $e(e,t){return(await e.studio.get(`/projects/${t}/apps`)).apps}function Ne(e,t){let n=e.find(s=>s.slug===t||s.id===t);if(!n)throw new m("no_such_app",`the project has no app ${t}`,{hint:`its apps: ${e.map(s=>s.slug).join(", ")||"none \u2014 palbase apps:create"}`});return n}function us(e){let t=e.ios?[e.ios.bundle,e.ios.app_clip&&`App Clip ${e.ios.app_clip}`,e.ios.watch&&`watch ${e.ios.watch}`].filter(Boolean).join(", "):e.android?`${e.android.package} (${e.android.fingerprints.map(n=>n.label).join(", ")||"no certificate"})`:e.web?.origins.join(", ")??"";return e.suspended?`${t} \u2014 suspended`:t}var mp={name:"apps:list",summary:"the project's apps, and what each is in every environment it is set in",scope:"environment",async run(e){let t=await pt(e),n=await $e(e,t.id);e.out.result({apps:n},()=>{if(n.length===0)return e.out.note(`${t.name} has no app yet: palbase apps:create ios|android|web <name>`);e.out.table(n,[{header:"slug",value:s=>s.slug},{header:"platform",value:s=>s.platform},{header:"name",value:s=>s.deleted_at?`${s.name} (deleted; restorable until ${s.purge_after?.slice(0,10)})`:s.name},{header:"environments",value:s=>s.environments.map(r=>`${r.slug}: ${us(r)}`).join("; ")||"\u2014"}])})}},gp={name:"apps:create",summary:"register an app of the project (an iOS app names its Apple team); then set it in each environment (apps:env:set)",usage:"ios|android|web <name> [--slug <slug>] [--team-id <id>] [--app-id-prefix <prefix>]",details:["An app is one product on one platform: its widgets, notification service and other extensions are not apps of their own \u2014 Apple gives them bundles under the app's, and they share its sign-in.","The slug is the app's file in a checkout: palbase/environments/<env>/<slug>.json."],args:{min:2,max:2},flags:{slug:{type:"string",value:"slug",description:"its name in a checkout (default: from the name)"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team: 10 characters, Apple Developer \u203A Membership"},"app-id-prefix":{type:"string",value:"prefix",description:"an iOS app's App ID prefix when it is not the team's (an app made before 2011, or moved from another team)"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="ios"&&t!=="android"&&t!=="web")throw g("the platform is ios, android or web");let s=await pt(e),r=await e.studio.post(`/projects/${s.id}/apps`,{platform:t,name:n,...e.flags.slug?{slug:e.flags.slug}:{},...e.flags["team-id"]?{team_id:e.flags["team-id"]}:{},...e.flags["app-id-prefix"]?{app_id_prefix:e.flags["app-id-prefix"]}:{}});e.out.result(r,()=>{e.out.line(`${s.name} \u203A ${r.name} (${r.platform}, ${r.slug})`),e.out.note(`set it in each environment: palbase apps:env:set ${r.slug} -E <env> ${t==="ios"?"--bundle <bundle id>":t==="android"?"--package <application id> --sha256 <label>=<fingerprint>":"--origin <https://\u2026>"}`)})}},hp={name:"apps:get",summary:"an app of the project, with what it is and its key in every environment it is set in",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await pt(e),n=Ne(await $e(e,t.id),e.args[0]);e.out.result(n,()=>{if(e.out.line(`${n.name} \u2014 ${n.platform}, ${n.slug}${n.team_id?`, team ${n.team_id}${n.app_id_prefix?` (App ID prefix ${n.app_id_prefix})`:""}`:""}`),n.deleted_at&&e.out.note(`deleted; restorable until ${n.purge_after} (palbase apps:restore ${n.slug})`),n.environments.length===0)return e.out.note(`in no environment yet: palbase apps:env:set ${n.slug} -E <env> \u2026`);e.out.table(n.environments,[{header:"environment",value:s=>s.slug},{header:"is",value:s=>us(s)},{header:"publishable key",value:s=>s.key?.key??"\u2014"}])})}},fp={name:"apps:update",summary:"rename an app, or change an iOS app's Apple team: every environment it is in is told",usage:'<app> [--name <name>] [--team-id <id>] [--app-id-prefix <prefix>|""]',args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"what it is called"},"team-id":{type:"string",value:"id",description:"an iOS app's Apple team"},"app-id-prefix":{type:"string",value:"prefix",description:`its App ID prefix; "" for the team's`}},scope:"environment",async run(e){let t=await pt(e),n=Ne(await $e(e,t.id),e.args[0]),s={};if(e.flags.name!==void 0&&(s.name=e.flags.name),e.flags["team-id"]!==void 0&&(s.team_id=e.flags["team-id"]),e.flags["app-id-prefix"]!==void 0&&(s.app_id_prefix=e.flags["app-id-prefix"]===""?null:e.flags["app-id-prefix"]),Object.keys(s).length===0)throw g("nothing to change: --name, --team-id or --app-id-prefix");let r=await e.studio.request("PATCH",`/projects/${t.id}/apps/${n.id}`,s);e.out.result(r,()=>e.out.line(`${r.name} (${r.slug}) \u2014 told to ${r.environments.length} environment(s)`))}},yp={name:"apps:delete",summary:"delete an app: suspended in every environment for 30 days \u2014 its builds stop reaching the project \u2014 then removed for good",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await pt(e),n=Ne(await $e(e,t.id),e.args[0]);if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed",`deleting ${n.name} stops every build of it reaching the project`,{hint:"confirm with -y"});let r=(await e.io.prompt(`Every build of ${n.name} stops reaching ${t.name} at once (restorable for 30 days). Delete it? [y/N] `)).trim().toLowerCase();if(r!=="y"&&r!=="yes")throw new m("cancelled","nothing was changed")}let s=await e.studio.request("DELETE",`/projects/${t.id}/apps/${n.id}`);e.out.result(s,()=>e.out.line(`${s.name} is deleted; palbase apps:restore ${s.slug} brings it back as it was until ${s.purge_after}`))}},bp={name:"apps:restore",summary:"bring a deleted app back as it was, with its keys, in every environment",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await pt(e),n=Ne(await $e(e,t.id),e.args[0]),s=await e.studio.post(`/projects/${t.id}/apps/${n.id}/restore`);e.out.result(s,()=>e.out.line(`${s.name} is back in ${s.environments.length} environment(s)`))}},gh=/^([a-z][a-z0-9_-]{0,31})=(.+)$/,wp={name:"apps:env:set",summary:"what an app is in the environment: its bundle (iOS), package and signing certificates (Android) or origins (web)",usage:"<app> [--bundle <id> [--app-clip <id>] [--watch <id>]] [--package <id> --sha256 <label>=<fingerprint>\u2026] [--origin <url>\u2026 --redirect <url>\u2026]",details:["iOS: the app's bundle in this environment; its App Clip and watch app are the bundle followed by one more part. Its extensions are not set: they share the app's.","Android: its application ID and the SHA-256 of each certificate it is signed with \u2014 Play Console \u203A App signing (Google's app signing key, not the upload key), ./gradlew signingReport for a debug key. Label each: debug, play_signing, upload\u2026","Web: the origins its pages are served from (https; http only on this computer) and the addresses a sign-in may return to.","The platform publishes it to Apple and Google for the environment: a device sees a change in a day or so (Apple caches its file; Android 15 re-checks within a week)."],args:{min:1,max:1},flags:{bundle:{type:"string",value:"id",description:"an iOS app's bundle identifier here"},"app-clip":{type:"string",value:"id",description:"its App Clip's bundle (the bundle followed by one more part)"},watch:{type:"string",value:"id",description:"its watch app's bundle (the bundle followed by one more part)"},package:{type:"string",value:"id",description:"an Android app's application ID here"},sha256:{type:"string",multiple:!0,value:"label=fingerprint",description:"a signing certificate's SHA-256, labelled (play_signing=AB:CD:\u2026); every one the app is signed with"},origin:{type:"string",multiple:!0,value:"url",description:"a web app's origin"},redirect:{type:"string",multiple:!0,value:"url",description:"an address a sign-in may return to"}},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]),s=i=>(e.flags[i]??[]).flatMap(o=>o.split(",")).map(o=>o.trim()).filter(Boolean),r;if(n.platform==="ios"){if(!e.flags.bundle)throw g(`${n.name} is an iOS app: give its bundle here (--bundle)`);r={ios:{bundle:e.flags.bundle,...e.flags["app-clip"]?{app_clip:e.flags["app-clip"]}:{},...e.flags.watch?{watch:e.flags.watch}:{}}}}else if(n.platform==="android"){if(!e.flags.package)throw g(`${n.name} is an Android app: give its package here (--package)`);let i=(e.flags.sha256??[]).map(o=>{let l=gh.exec(o.trim());if(!l)throw g(`--sha256 ${JSON.stringify(o)}: label=fingerprint, like play_signing=AB:CD:\u2026`);return{label:l[1],sha256:l[2]}});r={android:{package:e.flags.package,fingerprints:i}}}else{let i=s("origin");if(i.length===0)throw g(`${n.name} is a web app: give its origins here (--origin)`);let o=s("redirect");r={web:{origins:i,...o.length?{redirects:o}:{}}}}await b(e,t,`set ${n.name} (${n.slug}) here: ${us(r)}`);let a=await e.studio.request("PUT",`/environments/${t.env_ref}/apps/${n.id}`,r);for(let i of a.warnings)e.out.warn(i);e.out.result(a,()=>{e.out.line(`${n.name} in ${t.env_name}: ${us(a)}`),a.key&&e.out.line(`its publishable key here: ${a.key.key}`),e.out.note("palbase apps:sync writes its settings into this checkout")})}},vp={name:"apps:env:unset",summary:"take an app out of the environment: its key there revoked, Apple's and Google's files no longer naming it",usage:"<app>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]);await b(e,t,`take ${n.name} out of here: builds of it stop reaching ${t.env_name}`),await e.studio.request("DELETE",`/environments/${t.env_ref}/apps/${n.id}`),e.out.result({app:n.slug,environment:t.env_slug,removed:!0},()=>e.out.line(`${n.name} is out of ${t.env_name}`))}},_p={name:"apps:sdkconfig",summary:"an app's settings in the environment, as palbase apps:sync writes them: its address, its publishable key, what its modules tell it",usage:"<app> [--format json|env]",details:["The publishable key is public: it ships in the app. A secret key never does."],args:{min:1,max:1},flags:{format:{type:"string",value:"format",description:"json (default) or env"}},scope:"environment",async run(e){let t=await h(e),n=Ne(await $e(e,t.project_id),e.args[0]),s=e.flags.format??"json";if(s!=="json"&&s!=="env")throw g("--format is json or env");let r=await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`);e.out.result(r,()=>{s==="env"?(e.out.line(`PALBASE_URL=${r.url}`),e.out.line(`PALBASE_PUBLISHABLE_KEY=${r.publishable_key}`)):e.out.line(JSON.stringify(r,null,2))})}},Sp={name:"apps:sync",summary:"write each environment's settings and backend clients (Palbe) of this checkout's apps under palbase/ \u2014 nothing else is touched",usage:"[<app>\u2026] [--check] [--types <env>] [--backend [<machine>=]<dir>] [--as <machine>=<name>]\u2026",args:{min:0,max:20},details:["The apps named join this checkout's (palbase/project.json); with none named, its apps are synced.","palbase/environments/<env>/: each app's settings (<app>.json), each backend's contract (contracts/<machine>.openapi.json) and Palbe client made from it \u2014 TypeScript for a web app, Swift for an iOS app (in palbase/<app>.palbase), Kotlin for an Android app. A web app imports pb from palbase/<app>.ts: one environment's for the editor (--types), the build's mode's for the build (palbase() in vite.config), which type-checks the app against that environment.","--check writes nothing: it exits 1 naming what differs from the project now \u2014 for CI.","--backend makes a backend's Palbe from its checkout on this computer (its palbase/backend app writes its contract and exits; nothing is connected to), to try an endpoint before any machine serves it.","A backend is pb.<name> in the apps \u2014 pb.shopApi.rooms.create() for the machine shop-api \u2014 unless --as names it (--as shop-api=shop): the name is the checkout's, the same in every environment, and none of the client's own (auth, flags, storage, \u2026)."],flags:{check:{type:"boolean",description:"write nothing; exit 1 when the checkout is not what the project gives now"},types:{type:"string",value:"env",description:"the environment the editor's types are of (remembered in palbase/project.json)"},backend:{type:"string",value:"[machine=]dir",description:"a backend's checkout here: its Palbe made from the contract it writes, not from its machine's"},as:{type:"string",multiple:!0,value:"machine=name",description:"a backend's name in the apps (pb.<name>): its machine's camelCased unless named; the same in every environment (remembered in palbase/project.json)"}},scope:"environment",async run(e){return up(e)}};import{readdir as cs,readFile as ms}from"node:fs/promises";import{dirname as hh,extname as fh,join as qe,relative as jr,sep as yh}from"node:path";var bh=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".swift",".kt"]),wh=new Set(["node_modules","dist","build",".build",".git","Pods","DerivedData","xcuserdata",".gradle","palbase"]),vh=/^[^\u0000-\u001f\u007f"\\]+$/,kp=/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/,_h=["call","stream","upload","join"],Sh=new Set(["auth","flags","realtime","analytics","calls","messaging","perf","storage","notify","configure"]);function kh(e){let t=new Set,n=e?.paths??{};for(let s of Object.values(n))for(let r of Object.values(s??{})){let i=(typeof r?.operationId=="string"?r.operationId:"").split(".")[0];/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&!Sh.has(i)&&t.add(i)}return t}async function $h(e,t){let n=[],s=async r=>{let a;try{a=await cs(r,{withFileTypes:!0})}catch(i){if(i.code==="ENOENT")return;throw i}for(let i of a){if(i.isSymbolicLink())continue;let o=qe(r,i.name);i.isDirectory()?!wh.has(i.name)&&!i.name.startsWith(".")&&await s(o):i.isFile()&&bh.has(fh(i.name))&&vh.test(jr(e,o))&&n.push(jr(e,o))}};for(let r of t)await s(qe(e,r));return[...new Set(n)].sort()}async function $p(e,t,n,s,r=new Map){if(s!==void 0&&!kp.test(s))throw new m("unexpected_answer",`${JSON.stringify(s)} is not a web app's slug`);for(let[d,f]of n){if(!/^[a-z][A-Za-z0-9]{0,62}$/.test(d))throw new m("unexpected_answer",`${JSON.stringify(d)} is not a backend's name in an app`);for(let y of f)if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(y))throw new m("unexpected_answer",`${JSON.stringify(y)} is not a backend's top-level name`)}let a=new Map;for(let[d,f]of n)for(let y of f){let v=a.get(y);if(v!==void 0)throw new m("ambiguous_name",`pb.${y} is ${v}'s and ${d}'s: a v2 call of it cannot be told which \u2014 move those calls by hand (pb.${v}.${y} or pb.${d}.${y})`);a.set(y,d)}if(n.size===1)for(let d of _h)a.set(d,[...n.keys()][0]);let i=[...a.keys()].sort((d,f)=>f.length-d.length).map(d=>d.replace(/\$/g,"\\$")).join("|"),o=i?new RegExp(`(?<![\\w.$])pb(\\s*\\.\\s*)(${i})(?![\\w$])`,"g"):void 0;for(let d of r.keys())if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(d))throw new m("unexpected_answer",`${JSON.stringify(d)} is not a machine's name`);let l=[],p=[],u=0;for(let d of await $h(e,t)){let f=await ms(qe(e,d),"utf8"),y=o?f.replace(o,(v,T,A)=>(u++,`pb${T}${a.get(A)}.${A}`)):f;if(s&&/\.(?:[cm]?[jt]sx?)$/.test(d)){let v=(...A)=>{let P=jr(hh(qe(e,d)),qe(e,"palbase",...A)).split(yh).join("/");return P.startsWith(".")?P:`./${P}`},T=v(s);y=y.replace(/import\s*\{\s*pb\s*\}\s*from\s*(['"])@palbase\/web\1/g,(A,P)=>`import { pb } from ${P}${T}${P}`),y=y.replace(/(\b(?:import|export)\s+(?:type\s+)?)(\{[^}]*\}|\*(?:\s+as\s+[A-Za-z_$][\w$]*)?|[A-Za-z_$][\w$]*)(\s*from\s*)(['"])((?:\.\.?\/)[^'"\n]*?palbe\.gen(?:\.[jt]s)?)\4/g,(A,P,S,C,$,K)=>{if(n.size<=1)return`${P}${S}${C}${$}${T}${$}`;let X=S.startsWith("{")?S.slice(1,-1).split(",").map(F=>F.trim().replace(/^type\s+/,"").split(/\s+as\s+/)[0]).filter(F=>F!==""):[],L=[...r].filter(([,F])=>X.length>0&&X.every(kt=>F.has(kt))).map(([F])=>F);return L.length!==1?(p.push(`${d}: its import from ${K} is no one backend's \u2014 ${X.length===0?"it takes the module whole":`${X.join(", ")} ${L.length===0?"are not all one backend's":`are ${L.join("'s and ")}'s`}`}: import them from palbase/palbe/<machine>`),A):`${P}${S}${C}${$}${v("palbe",L[0])}${$}`})}y!==f&&l.push({path:d,before:f,after:y})}return{changes:l,moved:u,left:p}}function Ap(e){let t=[];for(let n of e){let s=n.before.endsWith(`
634
634
  `),r=(s?n.before.slice(0,-1):n.before).split(`
635
635
  `),a=(s?n.after.slice(0,-1):n.after).split(`
636
636
  `);if(r.length!==a.length||s!==n.after.endsWith(`
637
- `))throw new Error(`${n.path}: a change moved lines`);let i=d=>!s&&d===r.length-1?["\"]:[];t.push(`diff --git a/${n.path} b/${n.path}`,`--- a/${n.path}`,`+++ b/${n.path}`);let o=r.map((d,u)=>d!==a[u]),l=0;for(;l<r.length;){if(!o[l]){l++;continue}let d=Math.max(0,l-3),u=l;for(let T=l;T<r.length;T++)if(o[T])u=T;else if(T-u>6)break;let p=Math.min(r.length,u+4),f=[],y=d;for(;y<p;){if(!o[y]){f.push(` ${r[y]}`,...i(y)),y++;continue}let T=[];for(;y<p&&o[y];)T.push(y++);for(let A of T)f.push(`-${r[A]}`,...i(A));for(let A of T)f.push(`+${a[A]}`,...i(A))}let v=p-d;t.push(`@@ -${d+1},${v} +${d+1},${v} @@`,...f),l=p}}return t.length>0?`${t.join(`
637
+ `))throw new Error(`${n.path}: a change moved lines`);let i=p=>!s&&p===r.length-1?["\"]:[];t.push(`diff --git a/${n.path} b/${n.path}`,`--- a/${n.path}`,`+++ b/${n.path}`);let o=r.map((p,u)=>p!==a[u]),l=0;for(;l<r.length;){if(!o[l]){l++;continue}let p=Math.max(0,l-3),u=l;for(let T=l;T<r.length;T++)if(o[T])u=T;else if(T-u>6)break;let d=Math.min(r.length,u+4),f=[],y=p;for(;y<d;){if(!o[y]){f.push(` ${r[y]}`,...i(y)),y++;continue}let T=[];for(;y<d&&o[y];)T.push(y++);for(let A of T)f.push(`-${r[A]}`,...i(A));for(let A of T)f.push(`+${a[A]}`,...i(A))}let v=d-p;t.push(`@@ -${p+1},${v} +${p+1},${v} @@`,...f),l=d}}return t.length>0?`${t.join(`
638
638
  `)}
639
- `:""}async function Pd(e){let t;try{t=JSON.parse(await ms(qe(e,"palbase","project.json"),"utf8"))}catch(l){throw l.code==="ENOENT"?new m("not_synced","this checkout has no palbase/project.json",{hint:"palbase apps:sync <app> first: the backends and their names are read from what it writes"}):l}let n=new Map,s=[];try{s=await cs(qe(e,"palbase","environments"))}catch{}let r=new Map,a=typeof t.types_environment=="string"&&s.includes(t.types_environment)?t.types_environment:void 0;for(let l of s){if(a!==void 0&&l!==a)continue;let d=[];try{d=await cs(qe(e,"palbase","environments",l,"palbe"))}catch{continue}for(let u of d.filter(p=>p.endsWith(".ts"))){let p=await ms(qe(e,"palbase","environments",l,"palbe",u),"utf8"),f=u.slice(0,-3),y=r.get(f)??new Set;for(let v of p.matchAll(/^export (?:declare )?(?:abstract )?(?:class|interface|type|const|let|enum|function) ([A-Za-z_$][\w$]*)/gm))y.add(v[1]);r.set(f,y)}}for(let l of s){let d=[];try{d=await cs(qe(e,"palbase","environments",l,"contracts"))}catch{continue}for(let u of d){let p=u.replace(/\.openapi\.json$/,"");if(p===u)continue;let f=kh(JSON.parse(await ms(qe(e,"palbase","environments",l,"contracts",u),"utf8")));n.set(p,new Set([...n.get(p)??[],...f]))}}if(n.size===0)throw new m("no_backend","this checkout's apps reach no backend with a contract yet",{hint:"palbase apps:sync, once the backend is deployed"});let i=ls(n.keys(),t.clients??{}),o=new Map;for(let[l,d]of n)o.set(i.get(l),d);return{names:o,web:(t.apps??[]).filter(l=>l.platform==="web"&&typeof l.slug=="string"&&kd.test(l.slug)).map(l=>l.slug),modules:r}}var Rd={name:"palbe:migrate",summary:"a patch moving an app's v2 Palbe calls (pb.rooms.create) under the backend's name (pb.api.rooms.create) \u2014 printed, never written",usage:"[<dir>\u2026] [--app <web app>]",args:{min:0,max:20},details:["Reads the sources under each <dir> (the checkout by default; node_modules, build output and palbase/ left out) and prints a patch: palbase palbe:migrate > palbe.patch && git apply palbe.patch.","Which backend a name is, and its name in the app, come from what palbase apps:sync keeps under palbase/: sync first. With one backend, its own members (pb.call, pb.stream, pb.upload, pb.join) move under it too.","A web module's `import { pb } from '@palbase/web'` (v2's) takes its pb from palbase/<app> instead (--app when the checkout has several web apps), and what it imported from v2's palbe.gen \u2014 models, typed errors \u2014 from palbase/<app> too, or, when the app has several backends, from palbase/palbe/<machine> of the one that declares them."],flags:{app:{type:"string",value:"app",description:"the web app whose palbase/<app> a module's pb comes from"}},scope:"none",async run(e){let t=e.io.cwd,{names:n,web:s,modules:r}=await Pd(t),a=e.flags.app??(s.length===1?s[0]:void 0);if(e.flags.app!==void 0&&!s.includes(e.flags.app))throw g(`--app ${String(e.flags.app)} is not a web app of this checkout`,`its web apps: ${s.join(", ")||"none"}`);if(a===void 0&&s.length>1)throw g("this checkout has several web apps: name the one whose pb its modules take (--app)",`its web apps: ${s.join(", ")}`);let i=await $d(t,e.args.length>0?e.args:["."],n,a,r);e.io.stdout(Ad(i.changes));for(let o of i.left)e.out.note(o);e.out.note(i.changes.length===0?"no v2 call to move":`${i.moved} call(s) in ${i.changes.length} file(s) moved under ${[...n.keys()].map(o=>`pb.${o}`).join(", ")}: palbase palbe:migrate > palbe.patch && git apply palbe.patch`)}};var Ah=/^[A-Z][A-Z0-9_]{0,127}$/,Jt={dev:{type:"boolean",description:"the development set (what a backend on a developer's computer reads), not production"}};function zt(e){return e.flags.dev===!0?"development":"production"}function wn(e){let t=e.args[0];if(!Ah.test(t))throw g(`${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);return t}async function Or(e,t,n){let s=[],r;do{let a=await c(e,t,"GET",`/secrets/${n}?limit=1000${r?`&cursor=${encodeURIComponent(r)}`:""}`);s.push(...a.secrets),r=a.next_cursor}while(r);return s}var Ph={name:"secrets:list",summary:"the environment's secrets by name, with their version \u2014 never their values",usage:"[--dev]",flags:Jt,scope:"environment",async run(e){let t=await h(e),n=zt(e),s=await Or(e,t,n);e.out.result({set:n,secrets:s},()=>{s.length===0&&e.out.line(`No ${n} secrets.`);for(let r of s)e.out.line(`${r.name} v${r.version} ${r.updated_at}`)})}},Rh={name:"secrets:set",summary:"set a secret: its value is read from stdin, never from the command line",usage:"<NAME> [--dev] < value",details:["The environment's machines read production at their next start; a backend run with palbase run or env:pull reads development."],args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=zt(e),r=await fe(e,`the value of ${n}`);await b(e,t,`set the ${s} secret ${n}`);let a=await c(e,t,"PUT",`/secrets/${s}/${n}`,{value:r});e.out.result({set:s,name:n,version:a.version},()=>e.out.line(`Set ${n} (${s}, v${a.version}) in ${M(t)}.`))}},Th={name:"secrets:unset",summary:"take a secret away",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=zt(e);await b(e,t,`take away the ${s} secret ${n}`),await c(e,t,"DELETE",`/secrets/${s}/${n}`),e.out.result({set:s,name:n,deleted:!0},()=>e.out.line(`Took ${n} (${s}) away from ${M(t)}.`))}},Eh={name:"secrets:get",summary:"print one secret's value (an admin's)",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=await c(e,t,"GET",`/secrets/${zt(e)}/${n}`);e.out.result({name:n,version:s.version,value:s.value},()=>e.out.line(s.value))}},Ch={name:"secrets:versions",summary:"a secret's versions, newest first \u2014 never their values",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=await c(e,t,"GET",`/secrets/${zt(e)}/${n}/versions`);e.out.result(s,()=>{for(let r of s.versions)e.out.line(`v${r.version} ${r.created_at}${r.current?" (current)":""}`)})}},jh={name:"secrets:rollback",summary:"put a secret back to one of its versions (a new version with that value)",usage:"<NAME> --version <n> [--dev]",args:{min:1,max:1},flags:{...Jt,version:{type:"string",value:"n",description:"the version to go back to"}},scope:"environment",async run(e){let t=await h(e),n=wn(e),s=Number(e.flags.version);if(!Number.isInteger(s)||s<1)throw g("--version is a version number (secrets:versions lists them)");let r=zt(e);await b(e,t,`put the ${r} secret ${n} back to v${s}`);let a=await c(e,t,"POST",`/secrets/${r}/${n}/rollback`,{version:s});e.out.result({set:r,name:n,version:a.version},()=>e.out.line(`${n} (${r}) is v${s}'s value again, as v${a.version}.`))}},Oh={name:"secrets:audit",summary:"every read and write of the environment's secrets, newest first: which secret, which key did it, when \u2014 never a value",usage:"[--name <NAME>] [--dev | --prod] [--limit <n>]",details:["A backend that reads the secrets at boot (palbase run, a machine) shows here as a read, with the key it read with: palbase keys:list names the key."],flags:{name:{type:"string",value:"NAME",description:"only this secret's"},dev:{type:"boolean",description:"only the development secrets"},prod:{type:"boolean",description:"only the production secrets"},limit:{type:"string",value:"n",description:"how many (default 30)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags.dev===!0&&e.flags.prod===!0)throw g("--dev or --prod, not both");let n=Number(e.flags.limit??30);if(!Number.isInteger(n)||n<1||n>500)throw g("--limit is a whole number from 1 to 500");let s=new URLSearchParams({limit:String(n),...e.flags.name?{name:e.flags.name}:{},...e.flags.dev===!0?{environment:"development"}:e.flags.prod===!0?{environment:"production"}:{}}),r=await c(e,t,"GET",`/secrets/audit?${s}`);e.out.result(r,()=>{r.entries.length===0?e.out.line("No read or write of a secret yet."):e.out.table(r.entries,[{header:"when",value:a=>a.at.slice(0,19).replace("T"," ")},{header:"what",value:a=>a.action},{header:"set",value:a=>a.environment},{header:"secret",value:a=>a.name?`${a.name}${a.version?` v${a.version}`:""}`:"every one"},{header:"key",value:a=>a.key_id}])})}},Td=[Ph,Rh,Eh,Th,Ch,jh,Oh];var Dh=/^[A-Z][A-Z0-9_]{0,127}$/;function vn(e,t){return`/environments/${e.env_ref}/webhooks${t?`/${encodeURIComponent(t)}`:""}`}function Ed(e){return me(e.flags.events)}function gs(e){let t=e.machine?`, the machine ${e.machine}'s \u2014 kept by the platform to what its app takes`:"";return`${e.url} \u2014 ${e.events.length===0?"every event":e.events.join(", ")} (${e.id}${t})`}var Ih={name:"webhooks:list",summary:"the environment's webhook endpoints: where its events go, which (never their secrets)",scope:"environment",async run(e){let t=await h(e),{endpoints:n}=await e.studio.get(vn(t));e.out.result({endpoints:n},()=>{n.length===0&&e.out.line("No webhook endpoint yet: palbase webhooks:create <url> makes one.");for(let s of n)e.out.line(gs(s))})}},Lh={name:"webhooks:create",summary:"add a webhook endpoint; its signing secret is printed this once, or kept in a secret with --secret-to",usage:"<url> [--events <a,b>] [--secret-to <NAME> [--dev]]",details:["A machine is given its own endpoint and its secret (PALBASE_WEBHOOK_SECRET) by the platform, for the events its app takes: this is for another receiver. --secret-to keeps the secret in the environment's secrets (production; --dev: development) and prints it nowhere."],args:{min:1,max:1},flags:{events:{type:"string",value:"list",description:"the events it is sent, comma-separated (user.deleted, \u2026); none: every event"},"secret-to":{type:"string",value:"NAME",description:"keep its signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}},scope:"environment",async run(e){let t=await h(e),n=e.flags["secret-to"];if(n!==void 0&&!Dh.test(n))throw g(`--secret-to ${JSON.stringify(n)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&n===void 0)throw g("--dev goes with --secret-to <NAME>");let s=e.flags.dev===!0?"development":"production";await b(e,t,`add the webhook endpoint ${e.args[0]}${n?`, its secret kept in ${n} (${s})`:""}`);let r=await e.studio.post(vn(t),{url:e.args[0],events:Ed(e)??[]}),{secret:a,...i}=r;if(n!==void 0){let o=await c(e,t,"PUT",`/secrets/${s}/${n}`,{value:a});e.out.result({endpoint:i,secret_to:{set:s,name:n,version:o.version}},()=>e.out.line(`Added ${gs(i)}; its secret is ${n} (${s}, v${o.version}) in ${M(t)}.`));return}e.out.result(r,()=>{e.out.line(`Added ${gs(i)}.`),e.out.line(`signing secret (shown this once): ${a}`)})}},Nh={name:"webhooks:update",summary:"change a webhook endpoint's address or events (its secret stays)",usage:'<id> [--url <url>] [--events <a,b>|""]',args:{min:1,max:1},flags:{url:{type:"string",value:"url",description:"where it is delivered now"},events:{type:"string",value:"list",description:'the events it is sent now, comma-separated; "" for every event'}},scope:"environment",async run(e){let t=await h(e);if(e.flags.url===void 0&&e.flags.events===void 0)throw g("nothing to change: --url or --events");let{endpoints:n}=await e.studio.get(vn(t)),s=n.find(a=>a.id===e.args[0]);if(!s)throw g(`the environment has no webhook endpoint ${JSON.stringify(e.args[0])}`,"palbase webhooks:list shows them");await b(e,t,`change the webhook endpoint ${s.url}`);let r=await e.studio.request("PUT",vn(t,s.id),{url:e.flags.url??s.url,events:Ed(e)??s.events});e.out.result(r,()=>e.out.line(`Changed: ${gs(r)}.`))}},qh={name:"webhooks:delete",summary:"remove a webhook endpoint: its events are no longer delivered there",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`remove the webhook endpoint ${e.args[0]}`),await e.studio.request("DELETE",vn(t,e.args[0])),e.out.result({deleted:e.args[0]},()=>e.out.line(`Removed the webhook endpoint ${e.args[0]}.`))}},Cd=[Ih,Lh,Nh,qh];import{constants as hs,createReadStream as Mh}from"node:fs";import{mkdir as Uh,open as xh,readdir as Kh,realpath as jd,stat as Ld}from"node:fs/promises";import{dirname as Od,extname as Gh,join as Nd,relative as Bh,resolve as Dd,sep as fs}from"node:path";import{Readable as qd}from"node:stream";import{pipeline as Jh}from"node:stream/promises";var Md=600;function _n(e){let t=/^([a-z0-9][a-z0-9_-]{0,62}):(.*)$/.exec(e);return!t||e.startsWith("/")||e.startsWith(".")?null:{bucket:t[1],path:t[2].replace(/^\/+/,"")}}function zh(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new m("unsafe_path",`the object ${JSON.stringify(e)} has a name no request can carry`);return encodeURIComponent(e)}async function Dr(e,t,n,s){let r=[],a;do{let i=new URLSearchParams({limit:"1000",...s?{prefix:s}:{},...a?{cursor:a}:{}}),o=await c(e,t,"GET",`/storage/buckets/${encodeURIComponent(n)}/objects?${i}`);r.push(...o.objects),a=o.next_cursor}while(a);return r}async function Ud(e,t,n,s){let r;try{r=await e.io.fetch(t,{...n,redirect:"manual"})}catch(a){throw new m("network_error",`${s}: ${a instanceof Error?a.message:String(a)}`)}if(!r.ok){let a=await r.json().catch(()=>({}));throw new m(a.error??`http_${r.status}`,`${s}: ${a.error_description??a.error??`answered ${r.status}`}`)}return r}var Wh={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".heif":"image/heif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".pdf":"application/pdf",".json":"application/json",".txt":"text/plain",".csv":"text/csv",".html":"text/html",".css":"text/css",".js":"text/javascript",".xml":"application/xml",".zip":"application/zip",".mp4":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4"};function Id(e){return Wh[Gh(e).toLowerCase()]}async function Fh(e,t,n,s,r,a){let i=(await Ld(n)).size,o=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(s)}/upload-urls`,{path:r,expires_in:Md,...a?{owner_id:a}:{}});return await Ud(e,o.url,{method:"PUT",body:qd.toWeb(Mh(n)),headers:{"content-length":String(i),...Id(n)?{"content-type":Id(n)}:{}},duplex:"half"},`${n} \u2192 ${s}:${r}`),i}function Vh(e,t){let n=t.split("/");if(t===""||n.some(r=>r===""||r==="."||r===".."||r.includes("\\")||r.includes("\0")))throw new m("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=Nd(e,...n);if(!s.startsWith(e+fs))throw new m("unsafe_path",`${t} would land outside ${e}`);return s}async function Hh(e,t,n,s,r,a){let i=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(n)}/signed-urls`,{path:s,expires_in:Md}),o=await Ud(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${r}`);if(await Uh(Od(r),{recursive:!0}),a!==void 0){let[f,y]=await Promise.all([jd(a),jd(Od(r))]);if(y!==f&&!y.startsWith(f+fs))throw new m("unsafe_path",`${s} would land outside ${a} (through a link)`)}let d=(await xh(r,hs.O_WRONLY|hs.O_CREAT|hs.O_TRUNC|hs.O_NOFOLLOW)).createWriteStream();if(!o.body)return await new Promise((f,y)=>d.on("error",y).end(f)),0;let u=0,p=qd.fromWeb(o.body).on("data",f=>{u+=f.length});return await Jh(p,d),u}async function xd(e){let t=[];for(let n of await Kh(e,{withFileTypes:!0})){let s=Nd(e,n.name);n.isDirectory()?t.push(...await xd(s)):n.isFile()&&t.push(s)}return t}var Xh={name:"storage:ls",summary:"a bucket's objects, or those under a prefix: path, size, type",usage:"<bucket>[:prefix]",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0].includes(":")?e.args[0]:`${e.args[0]}:`);if(!n)throw g(`${e.args[0]} is not a bucket (bucket or bucket:prefix)`);let s=await Dr(e,t,n.bucket,n.path);e.out.result({bucket:n.bucket,objects:s},()=>{s.length===0&&e.out.line(`Nothing in ${n.bucket}:${n.path}`);for(let r of s)e.out.line(`${r.path} ${r.size} ${r.content_type??""}`)})}},Yh={name:"storage:cp",summary:"copy files between this computer and a bucket: a file, or a folder whole with -r",usage:"<from> <to> [-r] [--owner <user id>]",details:["bucket:path names an object of the environment; anything else is a path on this computer.","Up: a file to bucket:path (a path ending in / keeps the file name); with -r, a folder to bucket:prefix, its paths kept.","Down: bucket:path to a file; with -r, every object under bucket:prefix into a folder."],args:{min:2,max:2},flags:{recursive:{type:"boolean",short:"r",description:"a folder (up) or a prefix (down), whole"},owner:{type:"string",value:"user id",description:"the user uploaded objects belong to (deleted with them)"}},scope:"environment",async run(e){let t=await h(e),[n,s]=e.args,r=_n(n),a=_n(s),i=e.flags.recursive===!0,o=e.flags.owner;if(r&&a)throw g("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!r&&!a)throw g("one side is bucket:path");let l=0,d=0;if(a){let u=Dd(e.io.cwd,n),p=i?await xd(u):[u];if(!i&&(await Ld(u)).isDirectory())throw g(`${n} is a folder: copy it with -r`);await b(e,t,`upload ${p.length} file${p.length===1?"":"s"} to ${a.bucket}:${a.path} in ${M(t)}`);for(let f of p){let y=i?Bh(u,f).split(fs).join("/"):f.split(fs).pop(),v=i||a.path===""||a.path.endsWith("/")?`${a.path.replace(/\/?$/,a.path?"/":"")}${y}`:a.path;d+=await Fh(e,t,f,a.bucket,v,o),l++,p.length>1&&e.out.note(`${l}/${p.length} ${v}`)}}else if(r){let u=Dd(e.io.cwd,s),p=i?await Dr(e,t,r.bucket,r.path):[{path:r.path,size:0}];for(let f of p){let y=i?f.path.slice(r.path.length).replace(/^\/+/,""):"",v=i?Vh(u,y):u;d+=await Hh(e,t,r.bucket,f.path,v,i?u:void 0),l++,p.length>1&&e.out.note(`${l}/${p.length} ${f.path}`)}}e.out.result({copied:l,bytes:d},()=>e.out.line(`Copied ${l} file${l===1?"":"s"} (${d} bytes).`))}},Zh={name:"storage:rm",summary:"delete an object, or every object under a prefix with -r",usage:"<bucket:path> [-r]",args:{min:1,max:1},flags:{recursive:{type:"boolean",short:"r",description:"every object under the prefix"}},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0]);if(!n||!n.path&&e.flags.recursive!==!0)throw g(`${e.args[0]} is not bucket:path`);let s=e.flags.recursive===!0?(await Dr(e,t,n.bucket,n.path)).map(r=>r.path):[n.path];await b(e,t,`delete ${s.length} object${s.length===1?"":"s"} from ${n.bucket} in ${M(t)}`);for(let r of s)await c(e,t,"DELETE",`/storage/buckets/${encodeURIComponent(n.bucket)}/objects/${zh(r)}`);e.out.result({deleted:s.length},()=>e.out.line(`Deleted ${s.length} object${s.length===1?"":"s"}.`))}},Qh={name:"storage:url",summary:"a signed download URL for one object",usage:"<bucket:path> [--expires <seconds>]",args:{min:1,max:1},flags:{expires:{type:"string",value:"seconds",description:"how long it works (default 3600, at most 604800)"}},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0]);if(!n||!n.path)throw g(`${e.args[0]} is not bucket:path`);let s=e.flags.expires===void 0?3600:Number(e.flags.expires);if(!Number.isInteger(s)||s<1||s>604800)throw g("--expires is 1 to 604800 seconds");let r=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(n.bucket)}/signed-urls`,{path:n.path,expires_in:s});e.out.result(r,()=>e.out.line(r.url))}},Kd=[Xh,Yh,Zh,Qh];import{openAsBlob as ff}from"node:fs";import{readFile as yf,stat as Qd}from"node:fs/promises";import{basename as bf,resolve as ep}from"node:path";import{createHash as ef}from"node:crypto";var tf={major:5,minor:0},Jd={USD:"AFG AGO AIA ALB ARG ARM ATG AZE BEN BFA BHR BHS BLR BLZ BMU BOL BRB BRN BTN BWA CIV CMR COD COG CPV CRI CYM DMA DOM DZA ECU FJI FSM GAB GEO GHA GMB GNB GRD GTM GUY HND IRQ ISL JAM JOR KEN KGZ KHM KNA KWT LAO LBN LBR LBY LCA LKA MAC MAR MDA MDG MDV MKD MLI MMR MNG MOZ MRT MSR MUS MWI NAM NER NIC NPL NRU OMN PAN PLW PNG PRY RWA SEN SLB SLE SLV STP SUR SWZ SYC TCA TCD TJK TKM TON TTO TUN UGA UKR URY USA UZB VCT VEN VGB VUT YEM ZMB ZWE",EUR:"AUT BEL BGR BIH CYP DEU ESP EST FIN FRA GRC HRV IRL ITA LTU LUX LVA MLT MNE NLD PRT SRB SVK SVN XKS",AED:"ARE",AUD:"AUS",BRL:"BRA",CAD:"CAN",CHF:"CHE",CLP:"CHL",CNY:"CHN",COP:"COL",CZK:"CZE",DKK:"DNK",EGP:"EGY",GBP:"GBR",HKD:"HKG",HUF:"HUN",IDR:"IDN",ILS:"ISR",INR:"IND",JPY:"JPN",KRW:"KOR",KZT:"KAZ",MXN:"MEX",MYR:"MYS",NGN:"NGA",NOK:"NOR",NZD:"NZL",PEN:"PER",PHP:"PHL",PKR:"PAK",PLN:"POL",QAR:"QAT",RON:"ROU",RUB:"RUS",SAR:"SAU",SEK:"SWE",SGD:"SGP",THB:"THA",TRY:"TUR",TWD:"TWN",TZS:"TZA",VND:"VNM",ZAR:"ZAF"},Vt=new Map(Object.entries(Jd).flatMap(([e,t])=>t.split(" ").map(n=>[n,e])).sort(([e],[t])=>e<t?-1:1)),Gd={USD:"USA",EUR:"DEU"};function zd(e){let t=e.toUpperCase();if(Gd[t])return Gd[t];let n=Jd[t]?.split(" ");return n?.length===1?n[0]:void 0}var nf=new Set(["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","VND","VUV","XAF","XOF","XPF"]),sf=new Set(["BHD","JOD","KWD","OMR","TND"]);function rf(e){let t=e.toUpperCase();return nf.has(t)?0:sf.has(t)?3:2}function Wt(e,t){let n=rf(t),s=e<0?"-":"",r=String(Math.abs(Math.round(e))).padStart(n+1,"0");return n===0?`${s}${r}`:`${s}${r.slice(0,-n)}.${r.slice(-n)}`}var Wd=["ar-SA","bn-BD","ca","zh-Hans","zh-Hant","hr","cs","da","nl-NL","en-AU","en-CA","en-GB","en-US","fi","fr-FR","fr-CA","de-DE","el","gu-IN","he","hi","hu","id","it","ja","kn-IN","ko","ms","ml-IN","mr-IN","no","or-IN","pl","pt-BR","pt-PT","pa-IN","ro","ru","sk","sl-SI","es-MX","es-ES","sv","ta-IN","te-IN","th","tr","uk","ur-PK","vi"],af={en:"en-US",fr:"fr-FR",es:"es-ES",pt:"pt-PT",zh:"zh-Hans",nb:"no",nn:"no",iw:"he",in:"id"},of={"zh-cn":"zh-Hans","zh-sg":"zh-Hans","zh-tw":"zh-Hant","zh-hk":"zh-Hant","zh-mo":"zh-Hant","es-419":"es-MX","es-us":"es-MX","es-ar":"es-MX","es-co":"es-MX","es-cl":"es-MX","es-pe":"es-MX"},lf=new Map(Wd.map(e=>[e.toLowerCase(),e])),ys=new Map;for(let e of Wd)ys.has(e.split("-")[0])||ys.set(e.split("-")[0],e);for(let[e,t]of Object.entries(af))ys.set(e,t);var df={"ar-SA":"ar","de-DE":"de","fr-FR":"fr","nl-NL":"nl"};function pf(e){let t=e.trim().replaceAll("_","-").toLowerCase(),n=lf.get(t)??of[t]??ys.get(t.split("-")[0]);return n===void 0?e.trim().replaceAll("-","_"):df[n]??n.replaceAll("-","_")}function Ft(e){return ef("sha256").update(e).digest("hex").slice(0,8).toUpperCase()}var uf=["Load Products","Purchase","Verification","App Store Sync","Subscription Status","App Transaction","Manage Subscriptions Sheet","Refund Request Sheet","Offer Code Redeem Sheet"];function cf(){return{_askToBuyEnabled:!1,_billingGracePeriodEnabled:!1,_billingIssuesEnabled:!1,_disableDialogs:!1,_failTransactionsEnabled:!1,_renewalBillingIssuesEnabled:!1,_storeKitErrors:uf.map(e=>({enabled:!1,name:e})),_timeRate:0}}var Bd={consumable:"Consumable",non_consumable:"NonConsumable",non_renewing:"NonRenewingSubscription",subscription:"RecurringSubscription"},Fd={free_trial:"free",pay_as_you_go:"payAsYouGo",pay_up_front:"payUpFront"};function Vd(e,t){let n=Vt.get(t.storefront);if(n===void 0)throw new Error(`${t.storefront} is not an App Store storefront`);let s=[],r=(P,S)=>{let C=S.price,$=S.intro?.price??void 0;if(C.currency.toUpperCase()===n)return{price:C.amount,intro:$??void 0};let K=C.overrides?.[n];return K!==void 0?{price:K,intro:$==null?void 0:Math.round($*K/C.amount)}:(s.push(`${P} has no ${n} price: its ${Wt(C.amount,C.currency)} ${C.currency.toUpperCase()} is written as ${n}`),{price:C.amount,intro:$??void 0})},a=P=>{let S=new Set,C=[];for(let $ of Object.keys(P.channels.ios?.locales??{}).sort()){let K=P.channels.ios?.locales[$],X=pf($);S.has(X)||(S.add(X),C.push({description:K.description??"",displayName:K.name??"",locale:X}))}return C.sort(($,K)=>$.locale<K.locale?-1:$.locale>K.locale?1:0)},i=[],o=[],l=new Map;for(let P of e.products){let S=P.channels.ios;if(!(!S||S.plans.length===0)){if(P.type==="subscription"){let C=P.group??P.key;l.set(C,[...l.get(C)??[],...S.plans.map($=>({offering:P,plan:$}))]);continue}for(let C of S.plans){let{price:$}=r(C.store_id,C),K={displayPrice:Wt($,n),familyShareable:!1,internalID:Ft(`product:${C.store_id}`),localizations:a(P),productID:C.store_id,referenceName:C.store_id,type:Bd[P.type]};(P.type==="non_renewing"?o:i).push(K)}}}let d=[],u=0;for(let P of[...l.keys()].sort()){let S=l.get(P),C=Ft(`group:${P}`),$=Math.max(...S.map(X=>X.offering.tier??1)),K=S.flatMap(({offering:X,plan:L})=>{if(L.period===void 0)return s.push(`${L.store_id} is a subscription without a period: it is left out`),[];let{price:F,intro:kt}=r(L.store_id,L),k=L.intro?mf(L.store_id,L.intro,kt,n):void 0;return L.intro&&Fd[L.intro.mode]===void 0&&s.push(`${L.store_id}: an introductory offer ${L.intro.mode} is not one Xcode has; it is left out`),u++,[{adHocOffers:[],billingPlans:[{billingPlanType:"BILLED_UPFRONT",commitmentDisplayPrice:Wt(F,n),displayPrice:Wt(F,n),internalID:Ft(`billing:${L.store_id}`),isEnabled:!0}],codeOffers:[],displayPrice:Wt(F,n),familyShareable:!1,groupNumber:$-(X.tier??1)+1,internalID:Ft(`product:${L.store_id}`),...k?{introductoryOffer:k}:{},introductoryOffers:k?[k]:[],localizations:a(X),productID:L.store_id,recurringSubscriptionPeriod:L.period,referenceName:L.store_id,subscriptionGroupID:C,type:Bd.subscription,winbackOffers:[]}]});K.length!==0&&(K.sort((X,L)=>X.groupNumber-L.groupNumber||(X.productID<L.productID?-1:X.productID>L.productID?1:0)),d.push({id:C,localizations:[],name:P,subscriptions:K}))}let p=(P,S)=>P.productID<S.productID?-1:1;i.sort(p),o.sort(p);let f=[...i,...o,...d.flatMap(P=>P.subscriptions)],y=t.settings?._locale,v=typeof y=="string"&&f.some(P=>Hd(P).has(y))?y:gf(f),T={...t.settings??cf(),_locale:v,_storefront:t.storefront},A={appPolicies:{eula:"",policies:[{locale:v,policyText:"",policyURL:""}]},identifier:Ft(`configuration:${t.seed}`),nonRenewingSubscriptions:o,products:i,settings:T,subscriptionGroups:d,version:{...tf}};return{text:`${Ir(A,"")}
640
- `,storefront:t.storefront,currency:n,locale:v,counts:{groups:d.length,subscriptions:u,products:i.length,non_renewing:o.length},notes:[...new Set(s)].sort()}}function mf(e,t,n,s){let r=Fd[t.mode];if(r!==void 0)return{billingPlanType:"BILLED_UPFRONT",...r!=="free"&&n!==void 0?{displayPrice:Wt(n,s)}:{},internalID:Ft(`intro:${e}`),numberOfPeriods:t.mode==="pay_as_you_go"?Math.max(1,t.periods??1):1,paymentMode:r,subscriptionPeriod:t.duration}}function Hd(e){return new Set((e.localizations??[]).map(t=>t.locale))}function gf(e){let t=e.map(Hd);if(t.length===0)return"en_US";let n=[...t[0]].filter(s=>t.every(r=>r.has(s))).sort();return n.includes("en_US")?"en_US":n[0]??[...t[0]].sort()[0]??"en_US"}function hf(e,t){let n=e.toLowerCase(),s=t.toLowerCase();return n!==s?n<s?-1:1:e<t?-1:e>t?1:0}function Ir(e,t){if(e===null||typeof e!="object")return JSON.stringify(e);let n=`${t} `;if(Array.isArray(e))return e.length===0?`[
639
+ `:""}async function Pp(e){let t;try{t=JSON.parse(await ms(qe(e,"palbase","project.json"),"utf8"))}catch(l){throw l.code==="ENOENT"?new m("not_synced","this checkout has no palbase/project.json",{hint:"palbase apps:sync <app> first: the backends and their names are read from what it writes"}):l}let n=new Map,s=[];try{s=await cs(qe(e,"palbase","environments"))}catch{}let r=new Map,a=typeof t.types_environment=="string"&&s.includes(t.types_environment)?t.types_environment:void 0;for(let l of s){if(a!==void 0&&l!==a)continue;let p=[];try{p=await cs(qe(e,"palbase","environments",l,"palbe"))}catch{continue}for(let u of p.filter(d=>d.endsWith(".ts"))){let d=await ms(qe(e,"palbase","environments",l,"palbe",u),"utf8"),f=u.slice(0,-3),y=r.get(f)??new Set;for(let v of d.matchAll(/^export (?:declare )?(?:abstract )?(?:class|interface|type|const|let|enum|function) ([A-Za-z_$][\w$]*)/gm))y.add(v[1]);r.set(f,y)}}for(let l of s){let p=[];try{p=await cs(qe(e,"palbase","environments",l,"contracts"))}catch{continue}for(let u of p){let d=u.replace(/\.openapi\.json$/,"");if(d===u)continue;let f=kh(JSON.parse(await ms(qe(e,"palbase","environments",l,"contracts",u),"utf8")));n.set(d,new Set([...n.get(d)??[],...f]))}}if(n.size===0)throw new m("no_backend","this checkout's apps reach no backend with a contract yet",{hint:"palbase apps:sync, once the backend is deployed"});let i=ls(n.keys(),t.clients??{}),o=new Map;for(let[l,p]of n)o.set(i.get(l),p);return{names:o,web:(t.apps??[]).filter(l=>l.platform==="web"&&typeof l.slug=="string"&&kp.test(l.slug)).map(l=>l.slug),modules:r}}var Rp={name:"palbe:migrate",summary:"a patch moving an app's v2 Palbe calls (pb.rooms.create) under the backend's name (pb.api.rooms.create) \u2014 printed, never written",usage:"[<dir>\u2026] [--app <web app>]",args:{min:0,max:20},details:["Reads the sources under each <dir> (the checkout by default; node_modules, build output and palbase/ left out) and prints a patch: palbase palbe:migrate > palbe.patch && git apply palbe.patch.","Which backend a name is, and its name in the app, come from what palbase apps:sync keeps under palbase/: sync first. With one backend, its own members (pb.call, pb.stream, pb.upload, pb.join) move under it too.","A web module's `import { pb } from '@palbase/web'` (v2's) takes its pb from palbase/<app> instead (--app when the checkout has several web apps), and what it imported from v2's palbe.gen \u2014 models, typed errors \u2014 from palbase/<app> too, or, when the app has several backends, from palbase/palbe/<machine> of the one that declares them."],flags:{app:{type:"string",value:"app",description:"the web app whose palbase/<app> a module's pb comes from"}},scope:"none",async run(e){let t=e.io.cwd,{names:n,web:s,modules:r}=await Pp(t),a=e.flags.app??(s.length===1?s[0]:void 0);if(e.flags.app!==void 0&&!s.includes(e.flags.app))throw g(`--app ${String(e.flags.app)} is not a web app of this checkout`,`its web apps: ${s.join(", ")||"none"}`);if(a===void 0&&s.length>1)throw g("this checkout has several web apps: name the one whose pb its modules take (--app)",`its web apps: ${s.join(", ")}`);let i=await $p(t,e.args.length>0?e.args:["."],n,a,r);e.io.stdout(Ap(i.changes));for(let o of i.left)e.out.note(o);e.out.note(i.changes.length===0?"no v2 call to move":`${i.moved} call(s) in ${i.changes.length} file(s) moved under ${[...n.keys()].map(o=>`pb.${o}`).join(", ")}: palbase palbe:migrate > palbe.patch && git apply palbe.patch`)}};var Ah=/^[A-Z][A-Z0-9_]{0,127}$/,Jt={dev:{type:"boolean",description:"the development set (what a backend on a developer's computer reads), not production"}};function zt(e){return e.flags.dev===!0?"development":"production"}function wn(e){let t=e.args[0];if(!Ah.test(t))throw g(`${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);return t}async function Or(e,t,n){let s=[],r;do{let a=await c(e,t,"GET",`/secrets/${n}?limit=1000${r?`&cursor=${encodeURIComponent(r)}`:""}`);s.push(...a.secrets),r=a.next_cursor}while(r);return s}var Ph={name:"secrets:list",summary:"the environment's secrets by name, with their version \u2014 never their values",usage:"[--dev]",flags:Jt,scope:"environment",async run(e){let t=await h(e),n=zt(e),s=await Or(e,t,n);e.out.result({set:n,secrets:s},()=>{s.length===0&&e.out.line(`No ${n} secrets.`);for(let r of s)e.out.line(`${r.name} v${r.version} ${r.updated_at}`)})}},Rh={name:"secrets:set",summary:"set a secret: its value is read from stdin, never from the command line",usage:"<NAME> [--dev] < value",details:["The environment's machines read production at their next start; a backend run with palbase run or env:pull reads development."],args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=zt(e),r=await fe(e,`the value of ${n}`);await b(e,t,`set the ${s} secret ${n}`);let a=await c(e,t,"PUT",`/secrets/${s}/${n}`,{value:r});e.out.result({set:s,name:n,version:a.version},()=>e.out.line(`Set ${n} (${s}, v${a.version}) in ${x(t)}.`))}},Th={name:"secrets:unset",summary:"take a secret away",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=zt(e);await b(e,t,`take away the ${s} secret ${n}`),await c(e,t,"DELETE",`/secrets/${s}/${n}`),e.out.result({set:s,name:n,deleted:!0},()=>e.out.line(`Took ${n} (${s}) away from ${x(t)}.`))}},Eh={name:"secrets:get",summary:"print one secret's value (an admin's)",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=await c(e,t,"GET",`/secrets/${zt(e)}/${n}`);e.out.result({name:n,version:s.version,value:s.value},()=>e.out.line(s.value))}},Ch={name:"secrets:versions",summary:"a secret's versions, newest first \u2014 never their values",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:Jt,scope:"environment",async run(e){let t=await h(e),n=wn(e),s=await c(e,t,"GET",`/secrets/${zt(e)}/${n}/versions`);e.out.result(s,()=>{for(let r of s.versions)e.out.line(`v${r.version} ${r.created_at}${r.current?" (current)":""}`)})}},jh={name:"secrets:rollback",summary:"put a secret back to one of its versions (a new version with that value)",usage:"<NAME> --version <n> [--dev]",args:{min:1,max:1},flags:{...Jt,version:{type:"string",value:"n",description:"the version to go back to"}},scope:"environment",async run(e){let t=await h(e),n=wn(e),s=Number(e.flags.version);if(!Number.isInteger(s)||s<1)throw g("--version is a version number (secrets:versions lists them)");let r=zt(e);await b(e,t,`put the ${r} secret ${n} back to v${s}`);let a=await c(e,t,"POST",`/secrets/${r}/${n}/rollback`,{version:s});e.out.result({set:r,name:n,version:a.version},()=>e.out.line(`${n} (${r}) is v${s}'s value again, as v${a.version}.`))}},Oh={name:"secrets:audit",summary:"every read and write of the environment's secrets, newest first: which secret, which key did it, when \u2014 never a value",usage:"[--name <NAME>] [--dev | --prod] [--limit <n>]",details:["A backend that reads the secrets at boot (palbase run, a machine) shows here as a read, with the key it read with: palbase keys:list names the key."],flags:{name:{type:"string",value:"NAME",description:"only this secret's"},dev:{type:"boolean",description:"only the development secrets"},prod:{type:"boolean",description:"only the production secrets"},limit:{type:"string",value:"n",description:"how many (default 30)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags.dev===!0&&e.flags.prod===!0)throw g("--dev or --prod, not both");let n=Number(e.flags.limit??30);if(!Number.isInteger(n)||n<1||n>500)throw g("--limit is a whole number from 1 to 500");let s=new URLSearchParams({limit:String(n),...e.flags.name?{name:e.flags.name}:{},...e.flags.dev===!0?{environment:"development"}:e.flags.prod===!0?{environment:"production"}:{}}),r=await c(e,t,"GET",`/secrets/audit?${s}`);e.out.result(r,()=>{r.entries.length===0?e.out.line("No read or write of a secret yet."):e.out.table(r.entries,[{header:"when",value:a=>a.at.slice(0,19).replace("T"," ")},{header:"what",value:a=>a.action},{header:"set",value:a=>a.environment},{header:"secret",value:a=>a.name?`${a.name}${a.version?` v${a.version}`:""}`:"every one"},{header:"key",value:a=>a.key_id}])})}},Tp=[Ph,Rh,Eh,Th,Ch,jh,Oh];var Dh=/^[A-Z][A-Z0-9_]{0,127}$/;function vn(e,t){return`/environments/${e.env_ref}/webhooks${t?`/${encodeURIComponent(t)}`:""}`}function Ep(e){return me(e.flags.events)}function gs(e){let t=e.machine?`, the machine ${e.machine}'s \u2014 kept by the platform to what its app takes`:"";return`${e.url} \u2014 ${e.events.length===0?"every event":e.events.join(", ")} (${e.id}${t})`}var Ih={name:"webhooks:list",summary:"the environment's webhook endpoints: where its events go, which (never their secrets)",scope:"environment",async run(e){let t=await h(e),{endpoints:n}=await e.studio.get(vn(t));e.out.result({endpoints:n},()=>{n.length===0&&e.out.line("No webhook endpoint yet: palbase webhooks:create <url> makes one.");for(let s of n)e.out.line(gs(s))})}},Lh={name:"webhooks:create",summary:"add a webhook endpoint; its signing secret is printed this once, or kept in a secret with --secret-to",usage:"<url> [--events <a,b>] [--secret-to <NAME> [--dev]]",details:["A machine is given its own endpoint and its secret (PALBASE_WEBHOOK_SECRET) by the platform, for the events its app takes: this is for another receiver. --secret-to keeps the secret in the environment's secrets (production; --dev: development) and prints it nowhere."],args:{min:1,max:1},flags:{events:{type:"string",value:"list",description:"the events it is sent, comma-separated (user.deleted, \u2026); none: every event"},"secret-to":{type:"string",value:"NAME",description:"keep its signing secret in the environment's secret NAME instead of printing it"},dev:{type:"boolean",description:"with --secret-to: the development secrets, not production"}},scope:"environment",async run(e){let t=await h(e),n=e.flags["secret-to"];if(n!==void 0&&!Dh.test(n))throw g(`--secret-to ${JSON.stringify(n)} is not a secret's name: capital letters, digits and _, a letter first`);if(e.flags.dev===!0&&n===void 0)throw g("--dev goes with --secret-to <NAME>");let s=e.flags.dev===!0?"development":"production";await b(e,t,`add the webhook endpoint ${e.args[0]}${n?`, its secret kept in ${n} (${s})`:""}`);let r=await e.studio.post(vn(t),{url:e.args[0],events:Ep(e)??[]}),{secret:a,...i}=r;if(n!==void 0){let o=await c(e,t,"PUT",`/secrets/${s}/${n}`,{value:a});e.out.result({endpoint:i,secret_to:{set:s,name:n,version:o.version}},()=>e.out.line(`Added ${gs(i)}; its secret is ${n} (${s}, v${o.version}) in ${x(t)}.`));return}e.out.result(r,()=>{e.out.line(`Added ${gs(i)}.`),e.out.line(`signing secret (shown this once): ${a}`)})}},Nh={name:"webhooks:update",summary:"change a webhook endpoint's address or events (its secret stays)",usage:'<id> [--url <url>] [--events <a,b>|""]',args:{min:1,max:1},flags:{url:{type:"string",value:"url",description:"where it is delivered now"},events:{type:"string",value:"list",description:'the events it is sent now, comma-separated; "" for every event'}},scope:"environment",async run(e){let t=await h(e);if(e.flags.url===void 0&&e.flags.events===void 0)throw g("nothing to change: --url or --events");let{endpoints:n}=await e.studio.get(vn(t)),s=n.find(a=>a.id===e.args[0]);if(!s)throw g(`the environment has no webhook endpoint ${JSON.stringify(e.args[0])}`,"palbase webhooks:list shows them");await b(e,t,`change the webhook endpoint ${s.url}`);let r=await e.studio.request("PUT",vn(t,s.id),{url:e.flags.url??s.url,events:Ep(e)??s.events});e.out.result(r,()=>e.out.line(`Changed: ${gs(r)}.`))}},qh={name:"webhooks:delete",summary:"remove a webhook endpoint: its events are no longer delivered there",usage:"<id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e);await b(e,t,`remove the webhook endpoint ${e.args[0]}`),await e.studio.request("DELETE",vn(t,e.args[0])),e.out.result({deleted:e.args[0]},()=>e.out.line(`Removed the webhook endpoint ${e.args[0]}.`))}},Cp=[Ih,Lh,Nh,qh];import{constants as hs,createReadStream as xh}from"node:fs";import{mkdir as Mh,open as Uh,readdir as Kh,realpath as jp,stat as Lp}from"node:fs/promises";import{dirname as Op,extname as Gh,join as Np,relative as Bh,resolve as Dp,sep as fs}from"node:path";import{Readable as qp}from"node:stream";import{pipeline as Jh}from"node:stream/promises";var xp=600;function _n(e){let t=/^([a-z0-9][a-z0-9_-]{0,62}):(.*)$/.exec(e);return!t||e.startsWith("/")||e.startsWith(".")?null:{bucket:t[1],path:t[2].replace(/^\/+/,"")}}function zh(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new m("unsafe_path",`the object ${JSON.stringify(e)} has a name no request can carry`);return encodeURIComponent(e)}async function Dr(e,t,n,s){let r=[],a;do{let i=new URLSearchParams({limit:"1000",...s?{prefix:s}:{},...a?{cursor:a}:{}}),o=await c(e,t,"GET",`/storage/buckets/${encodeURIComponent(n)}/objects?${i}`);r.push(...o.objects),a=o.next_cursor}while(a);return r}async function Mp(e,t,n,s){let r;try{r=await e.io.fetch(t,{...n,redirect:"manual"})}catch(a){throw new m("network_error",`${s}: ${a instanceof Error?a.message:String(a)}`)}if(!r.ok){let a=await r.json().catch(()=>({}));throw new m(a.error??`http_${r.status}`,`${s}: ${a.error_description??a.error??`answered ${r.status}`}`)}return r}var Wh={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".heic":"image/heic",".heif":"image/heif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".pdf":"application/pdf",".json":"application/json",".txt":"text/plain",".csv":"text/csv",".html":"text/html",".css":"text/css",".js":"text/javascript",".xml":"application/xml",".zip":"application/zip",".mp4":"video/mp4",".mov":"video/quicktime",".webm":"video/webm",".mp3":"audio/mpeg",".wav":"audio/wav",".m4a":"audio/mp4"};function Ip(e){return Wh[Gh(e).toLowerCase()]}async function Fh(e,t,n,s,r,a){let i=(await Lp(n)).size,o=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(s)}/upload-urls`,{path:r,expires_in:xp,...a?{owner_id:a}:{}});return await Mp(e,o.url,{method:"PUT",body:qp.toWeb(xh(n)),headers:{"content-length":String(i),...Ip(n)?{"content-type":Ip(n)}:{}},duplex:"half"},`${n} \u2192 ${s}:${r}`),i}function Vh(e,t){let n=t.split("/");if(t===""||n.some(r=>r===""||r==="."||r===".."||r.includes("\\")||r.includes("\0")))throw new m("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=Np(e,...n);if(!s.startsWith(e+fs))throw new m("unsafe_path",`${t} would land outside ${e}`);return s}async function Hh(e,t,n,s,r,a){let i=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(n)}/signed-urls`,{path:s,expires_in:xp}),o=await Mp(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${r}`);if(await Mh(Op(r),{recursive:!0}),a!==void 0){let[f,y]=await Promise.all([jp(a),jp(Op(r))]);if(y!==f&&!y.startsWith(f+fs))throw new m("unsafe_path",`${s} would land outside ${a} (through a link)`)}let p=(await Uh(r,hs.O_WRONLY|hs.O_CREAT|hs.O_TRUNC|hs.O_NOFOLLOW)).createWriteStream();if(!o.body)return await new Promise((f,y)=>p.on("error",y).end(f)),0;let u=0,d=qp.fromWeb(o.body).on("data",f=>{u+=f.length});return await Jh(d,p),u}async function Up(e){let t=[];for(let n of await Kh(e,{withFileTypes:!0})){let s=Np(e,n.name);n.isDirectory()?t.push(...await Up(s)):n.isFile()&&t.push(s)}return t}var Xh={name:"storage:ls",summary:"a bucket's objects, or those under a prefix: path, size, type",usage:"<bucket>[:prefix]",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0].includes(":")?e.args[0]:`${e.args[0]}:`);if(!n)throw g(`${e.args[0]} is not a bucket (bucket or bucket:prefix)`);let s=await Dr(e,t,n.bucket,n.path);e.out.result({bucket:n.bucket,objects:s},()=>{s.length===0&&e.out.line(`Nothing in ${n.bucket}:${n.path}`);for(let r of s)e.out.line(`${r.path} ${r.size} ${r.content_type??""}`)})}},Yh={name:"storage:cp",summary:"copy files between this computer and a bucket: a file, or a folder whole with -r",usage:"<from> <to> [-r] [--owner <user id>]",details:["bucket:path names an object of the environment; anything else is a path on this computer.","Up: a file to bucket:path (a path ending in / keeps the file name); with -r, a folder to bucket:prefix, its paths kept.","Down: bucket:path to a file; with -r, every object under bucket:prefix into a folder."],args:{min:2,max:2},flags:{recursive:{type:"boolean",short:"r",description:"a folder (up) or a prefix (down), whole"},owner:{type:"string",value:"user id",description:"the user uploaded objects belong to (deleted with them)"}},scope:"environment",async run(e){let t=await h(e),[n,s]=e.args,r=_n(n),a=_n(s),i=e.flags.recursive===!0,o=e.flags.owner;if(r&&a)throw g("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!r&&!a)throw g("one side is bucket:path");let l=0,p=0;if(a){let u=Dp(e.io.cwd,n),d=i?await Up(u):[u];if(!i&&(await Lp(u)).isDirectory())throw g(`${n} is a folder: copy it with -r`);await b(e,t,`upload ${d.length} file${d.length===1?"":"s"} to ${a.bucket}:${a.path} in ${x(t)}`);for(let f of d){let y=i?Bh(u,f).split(fs).join("/"):f.split(fs).pop(),v=i||a.path===""||a.path.endsWith("/")?`${a.path.replace(/\/?$/,a.path?"/":"")}${y}`:a.path;p+=await Fh(e,t,f,a.bucket,v,o),l++,d.length>1&&e.out.note(`${l}/${d.length} ${v}`)}}else if(r){let u=Dp(e.io.cwd,s),d=i?await Dr(e,t,r.bucket,r.path):[{path:r.path,size:0}];for(let f of d){let y=i?f.path.slice(r.path.length).replace(/^\/+/,""):"",v=i?Vh(u,y):u;p+=await Hh(e,t,r.bucket,f.path,v,i?u:void 0),l++,d.length>1&&e.out.note(`${l}/${d.length} ${f.path}`)}}e.out.result({copied:l,bytes:p},()=>e.out.line(`Copied ${l} file${l===1?"":"s"} (${p} bytes).`))}},Zh={name:"storage:rm",summary:"delete an object, or every object under a prefix with -r",usage:"<bucket:path> [-r]",args:{min:1,max:1},flags:{recursive:{type:"boolean",short:"r",description:"every object under the prefix"}},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0]);if(!n||!n.path&&e.flags.recursive!==!0)throw g(`${e.args[0]} is not bucket:path`);let s=e.flags.recursive===!0?(await Dr(e,t,n.bucket,n.path)).map(r=>r.path):[n.path];await b(e,t,`delete ${s.length} object${s.length===1?"":"s"} from ${n.bucket} in ${x(t)}`);for(let r of s)await c(e,t,"DELETE",`/storage/buckets/${encodeURIComponent(n.bucket)}/objects/${zh(r)}`);e.out.result({deleted:s.length},()=>e.out.line(`Deleted ${s.length} object${s.length===1?"":"s"}.`))}},Qh={name:"storage:url",summary:"a signed download URL for one object",usage:"<bucket:path> [--expires <seconds>]",args:{min:1,max:1},flags:{expires:{type:"string",value:"seconds",description:"how long it works (default 3600, at most 604800)"}},scope:"environment",async run(e){let t=await h(e),n=_n(e.args[0]);if(!n||!n.path)throw g(`${e.args[0]} is not bucket:path`);let s=e.flags.expires===void 0?3600:Number(e.flags.expires);if(!Number.isInteger(s)||s<1||s>604800)throw g("--expires is 1 to 604800 seconds");let r=await c(e,t,"POST",`/storage/buckets/${encodeURIComponent(n.bucket)}/signed-urls`,{path:n.path,expires_in:s});e.out.result(r,()=>e.out.line(r.url))}},Kp=[Xh,Yh,Zh,Qh];import{openAsBlob as ff}from"node:fs";import{readFile as yf,stat as Qp}from"node:fs/promises";import{basename as bf,resolve as ed}from"node:path";import{createHash as ef}from"node:crypto";var tf={major:5,minor:0},Jp={USD:"AFG AGO AIA ALB ARG ARM ATG AZE BEN BFA BHR BHS BLR BLZ BMU BOL BRB BRN BTN BWA CIV CMR COD COG CPV CRI CYM DMA DOM DZA ECU FJI FSM GAB GEO GHA GMB GNB GRD GTM GUY HND IRQ ISL JAM JOR KEN KGZ KHM KNA KWT LAO LBN LBR LBY LCA LKA MAC MAR MDA MDG MDV MKD MLI MMR MNG MOZ MRT MSR MUS MWI NAM NER NIC NPL NRU OMN PAN PLW PNG PRY RWA SEN SLB SLE SLV STP SUR SWZ SYC TCA TCD TJK TKM TON TTO TUN UGA UKR URY USA UZB VCT VEN VGB VUT YEM ZMB ZWE",EUR:"AUT BEL BGR BIH CYP DEU ESP EST FIN FRA GRC HRV IRL ITA LTU LUX LVA MLT MNE NLD PRT SRB SVK SVN XKS",AED:"ARE",AUD:"AUS",BRL:"BRA",CAD:"CAN",CHF:"CHE",CLP:"CHL",CNY:"CHN",COP:"COL",CZK:"CZE",DKK:"DNK",EGP:"EGY",GBP:"GBR",HKD:"HKG",HUF:"HUN",IDR:"IDN",ILS:"ISR",INR:"IND",JPY:"JPN",KRW:"KOR",KZT:"KAZ",MXN:"MEX",MYR:"MYS",NGN:"NGA",NOK:"NOR",NZD:"NZL",PEN:"PER",PHP:"PHL",PKR:"PAK",PLN:"POL",QAR:"QAT",RON:"ROU",RUB:"RUS",SAR:"SAU",SEK:"SWE",SGD:"SGP",THB:"THA",TRY:"TUR",TWD:"TWN",TZS:"TZA",VND:"VNM",ZAR:"ZAF"},Vt=new Map(Object.entries(Jp).flatMap(([e,t])=>t.split(" ").map(n=>[n,e])).sort(([e],[t])=>e<t?-1:1)),Gp={USD:"USA",EUR:"DEU"};function zp(e){let t=e.toUpperCase();if(Gp[t])return Gp[t];let n=Jp[t]?.split(" ");return n?.length===1?n[0]:void 0}var nf=new Set(["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","VND","VUV","XAF","XOF","XPF"]),sf=new Set(["BHD","JOD","KWD","OMR","TND"]);function rf(e){let t=e.toUpperCase();return nf.has(t)?0:sf.has(t)?3:2}function Wt(e,t){let n=rf(t),s=e<0?"-":"",r=String(Math.abs(Math.round(e))).padStart(n+1,"0");return n===0?`${s}${r}`:`${s}${r.slice(0,-n)}.${r.slice(-n)}`}var Wp=["ar-SA","bn-BD","ca","zh-Hans","zh-Hant","hr","cs","da","nl-NL","en-AU","en-CA","en-GB","en-US","fi","fr-FR","fr-CA","de-DE","el","gu-IN","he","hi","hu","id","it","ja","kn-IN","ko","ms","ml-IN","mr-IN","no","or-IN","pl","pt-BR","pt-PT","pa-IN","ro","ru","sk","sl-SI","es-MX","es-ES","sv","ta-IN","te-IN","th","tr","uk","ur-PK","vi"],af={en:"en-US",fr:"fr-FR",es:"es-ES",pt:"pt-PT",zh:"zh-Hans",nb:"no",nn:"no",iw:"he",in:"id"},of={"zh-cn":"zh-Hans","zh-sg":"zh-Hans","zh-tw":"zh-Hant","zh-hk":"zh-Hant","zh-mo":"zh-Hant","es-419":"es-MX","es-us":"es-MX","es-ar":"es-MX","es-co":"es-MX","es-cl":"es-MX","es-pe":"es-MX"},lf=new Map(Wp.map(e=>[e.toLowerCase(),e])),ys=new Map;for(let e of Wp)ys.has(e.split("-")[0])||ys.set(e.split("-")[0],e);for(let[e,t]of Object.entries(af))ys.set(e,t);var pf={"ar-SA":"ar","de-DE":"de","fr-FR":"fr","nl-NL":"nl"};function df(e){let t=e.trim().replaceAll("_","-").toLowerCase(),n=lf.get(t)??of[t]??ys.get(t.split("-")[0]);return n===void 0?e.trim().replaceAll("-","_"):pf[n]??n.replaceAll("-","_")}function Ft(e){return ef("sha256").update(e).digest("hex").slice(0,8).toUpperCase()}var uf=["Load Products","Purchase","Verification","App Store Sync","Subscription Status","App Transaction","Manage Subscriptions Sheet","Refund Request Sheet","Offer Code Redeem Sheet"];function cf(){return{_askToBuyEnabled:!1,_billingGracePeriodEnabled:!1,_billingIssuesEnabled:!1,_disableDialogs:!1,_failTransactionsEnabled:!1,_renewalBillingIssuesEnabled:!1,_storeKitErrors:uf.map(e=>({enabled:!1,name:e})),_timeRate:0}}var Bp={consumable:"Consumable",non_consumable:"NonConsumable",non_renewing:"NonRenewingSubscription",subscription:"RecurringSubscription"},Fp={free_trial:"free",pay_as_you_go:"payAsYouGo",pay_up_front:"payUpFront"};function Vp(e,t){let n=Vt.get(t.storefront);if(n===void 0)throw new Error(`${t.storefront} is not an App Store storefront`);let s=[],r=(P,S)=>{let C=S.price,$=S.intro?.price??void 0;if(C.currency.toUpperCase()===n)return{price:C.amount,intro:$??void 0};let K=C.overrides?.[n];return K!==void 0?{price:K,intro:$==null?void 0:Math.round($*K/C.amount)}:(s.push(`${P} has no ${n} price: its ${Wt(C.amount,C.currency)} ${C.currency.toUpperCase()} is written as ${n}`),{price:C.amount,intro:$??void 0})},a=P=>{let S=new Set,C=[];for(let $ of Object.keys(P.channels.ios?.locales??{}).sort()){let K=P.channels.ios?.locales[$],X=df($);S.has(X)||(S.add(X),C.push({description:K.description??"",displayName:K.name??"",locale:X}))}return C.sort(($,K)=>$.locale<K.locale?-1:$.locale>K.locale?1:0)},i=[],o=[],l=new Map;for(let P of e.products){let S=P.channels.ios;if(!(!S||S.plans.length===0)){if(P.type==="subscription"){let C=P.group??P.key;l.set(C,[...l.get(C)??[],...S.plans.map($=>({offering:P,plan:$}))]);continue}for(let C of S.plans){let{price:$}=r(C.store_id,C),K={displayPrice:Wt($,n),familyShareable:!1,internalID:Ft(`product:${C.store_id}`),localizations:a(P),productID:C.store_id,referenceName:C.store_id,type:Bp[P.type]};(P.type==="non_renewing"?o:i).push(K)}}}let p=[],u=0;for(let P of[...l.keys()].sort()){let S=l.get(P),C=Ft(`group:${P}`),$=Math.max(...S.map(X=>X.offering.tier??1)),K=S.flatMap(({offering:X,plan:L})=>{if(L.period===void 0)return s.push(`${L.store_id} is a subscription without a period: it is left out`),[];let{price:F,intro:kt}=r(L.store_id,L),k=L.intro?mf(L.store_id,L.intro,kt,n):void 0;return L.intro&&Fp[L.intro.mode]===void 0&&s.push(`${L.store_id}: an introductory offer ${L.intro.mode} is not one Xcode has; it is left out`),u++,[{adHocOffers:[],billingPlans:[{billingPlanType:"BILLED_UPFRONT",commitmentDisplayPrice:Wt(F,n),displayPrice:Wt(F,n),internalID:Ft(`billing:${L.store_id}`),isEnabled:!0}],codeOffers:[],displayPrice:Wt(F,n),familyShareable:!1,groupNumber:$-(X.tier??1)+1,internalID:Ft(`product:${L.store_id}`),...k?{introductoryOffer:k}:{},introductoryOffers:k?[k]:[],localizations:a(X),productID:L.store_id,recurringSubscriptionPeriod:L.period,referenceName:L.store_id,subscriptionGroupID:C,type:Bp.subscription,winbackOffers:[]}]});K.length!==0&&(K.sort((X,L)=>X.groupNumber-L.groupNumber||(X.productID<L.productID?-1:X.productID>L.productID?1:0)),p.push({id:C,localizations:[],name:P,subscriptions:K}))}let d=(P,S)=>P.productID<S.productID?-1:1;i.sort(d),o.sort(d);let f=[...i,...o,...p.flatMap(P=>P.subscriptions)],y=t.settings?._locale,v=typeof y=="string"&&f.some(P=>Hp(P).has(y))?y:gf(f),T={...t.settings??cf(),_locale:v,_storefront:t.storefront},A={appPolicies:{eula:"",policies:[{locale:v,policyText:"",policyURL:""}]},identifier:Ft(`configuration:${t.seed}`),nonRenewingSubscriptions:o,products:i,settings:T,subscriptionGroups:p,version:{...tf}};return{text:`${Ir(A,"")}
640
+ `,storefront:t.storefront,currency:n,locale:v,counts:{groups:p.length,subscriptions:u,products:i.length,non_renewing:o.length},notes:[...new Set(s)].sort()}}function mf(e,t,n,s){let r=Fp[t.mode];if(r!==void 0)return{billingPlanType:"BILLED_UPFRONT",...r!=="free"&&n!==void 0?{displayPrice:Wt(n,s)}:{},internalID:Ft(`intro:${e}`),numberOfPeriods:t.mode==="pay_as_you_go"?Math.max(1,t.periods??1):1,paymentMode:r,subscriptionPeriod:t.duration}}function Hp(e){return new Set((e.localizations??[]).map(t=>t.locale))}function gf(e){let t=e.map(Hp);if(t.length===0)return"en_US";let n=[...t[0]].filter(s=>t.every(r=>r.has(s))).sort();return n.includes("en_US")?"en_US":n[0]??[...t[0]].sort()[0]??"en_US"}function hf(e,t){let n=e.toLowerCase(),s=t.toLowerCase();return n!==s?n<s?-1:1:e<t?-1:e>t?1:0}function Ir(e,t){if(e===null||typeof e!="object")return JSON.stringify(e);let n=`${t} `;if(Array.isArray(e))return e.length===0?`[
641
641
 
642
642
  ${t}]`:`[
643
643
  ${e.map(r=>n+Ir(r,n)).join(`,
@@ -647,27 +647,27 @@ ${t}]`;let s=Object.keys(e).filter(r=>e[r]!==void 0).sort(hf);return s.length===
647
647
  ${t}}`:`{
648
648
  ${s.map(r=>`${n}${JSON.stringify(r)} : ${Ir(e[r],n)}`).join(`,
649
649
  `)}
650
- ${t}}`}var Xd=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,wf=/^AuthKey_([A-Z0-9]{10})\.p8$/,vf=8192,Yd=["testflight"],Lr=100,Zd=5,_f=5,Sf=300,kn={beta_review:"Beta App Review",export_compliance:"the export compliance answer",expire_build:"expiring the build"};function J(e){return e?e.slice(0,19).replace("T"," "):"\u2014"}function ae(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw g(`${n} is required`);return s}function Ht(e,t,n,s){let r=ct(e.cloud,t.env_ref),a;try{a=new URL(n).origin}catch{throw new m("unexpected_address",`${s} is not a URL: nothing was sent`)}if(a!==r)throw new m("unexpected_address",`${s} names ${a}, not the environment's address ${r}: nothing was sent`);return r}async function bs(e,t,n,s){let r;try{r=await e.io.fetch(t,{...n,redirect:"manual"})}catch(l){throw new m("network_error",`${s}: ${t} could not be reached: ${l instanceof Error?l.message:String(l)}`)}let a=await r.text(),i;if(a!=="")try{i=JSON.parse(a)}catch{i=a}if(r.status>=200&&r.status<300)return i;let o=i??{};throw new Y({op:s,status:r.status,code:typeof o.error=="string"?o.error:`http_${r.status}`,message:Ln(i,r.status),requestId:typeof o.request_id=="string"?o.request_id:r.headers.get("x-request-id")??void 0,retryAfter:kf(r.headers.get("retry-after"),o.retry_after,e.io.now()),body:i})}function kf(e,t,n){if(e!==null&&e.trim()!==""){let s=Number(e);if(Number.isFinite(s)&&s>=0)return s;let r=Date.parse(e);if(Number.isFinite(r))return Math.max(0,Math.ceil((r-n)/1e3))}return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0}function qr(e,t){if(!t.connected){e.out.line("No Apple account is connected."),e.out.line("Connect one: palbase stores:apple:connect --key-file AuthKey_<KEYID>.p8 --issuer-id <issuer id>");return}e.out.line(`Apple account: issuer ${t.issuer_id??"?"}, key ${t.key_id??"?"}`),e.out.line(`status: ${t.status??"not checked"}${t.checked_at?` (checked ${J(t.checked_at)})`:""}`),t.capabilities&&e.out.line(`reaches: ${t.capabilities.length>0?t.capabilities.join(", "):"nothing"}`),t.connected_at&&e.out.line(`connected ${J(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.status==="agreement"?e.out.line(`Apple waits on the account holder: ${t.action??"accept the account's agreements in App Store Connect"}, then palbase stores:apple:check`):t.action&&e.out.line(`To do: ${t.action}`)}var $f={name:"stores:apple",summary:"the environment's Apple account: whether one is connected, what Apple last said of its key, what the key reaches",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apple");e.out.result(n,()=>qr(e,n))}},Af={name:"stores:apple:connect",summary:"connect an App Store Connect API key: it goes from this computer straight to the environment, never through Studio",usage:"--key-file <AuthKey_KEYID.p8> --issuer-id <issuer id> [--key-id <KEYID>] | --print-link",details:["App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 App Store Connect API makes the key and shows the Issuer ID; stores:apple then says what its role reaches.","Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead."],flags:{"key-file":{type:"string",value:"AuthKey_KEYID.p8",description:"the .p8 file Apple gave when the key was made"},"key-id":{type:"string",value:"KEYID",description:"the key's id (default: from the file's name)"},"issuer-id":{type:"string",value:"issuer id",description:"the account's issuer id"},"print-link":{type:"boolean",description:"only print a link that takes the key in a browser (valid 30 minutes, once)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw g("--print-link or --key-file, not both");await b(e,t,"make a link that connects an Apple account");let p=await c(e,t,"POST","/stores/apple/grants",void 0,R());Ht(e,t,p.connect_url,"the connect link"),e.out.result({connect_url:p.connect_url,expires_at:p.expires_at},()=>{e.out.line(p.connect_url),e.out.note(`Open it in a browser and pick the .p8 file; it works once, until ${J(p.expires_at)}.`)});return}let n=e.flags["key-file"],s=e.flags["issuer-id"];if(n===void 0)throw g("--key-file names the .p8 file","or --print-link for a link a browser opens");if(s===void 0||s.trim()==="")throw g("--issuer-id is the account's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations)");let r=bf(n),a=e.flags["key-id"]??wf.exec(r)?.[1];if(a===void 0)throw g(`--key-id is needed: ${r} is not named AuthKey_<KEYID>.p8`);let i;try{let p=ep(e.io.cwd,n);if((await Qd(p)).size>vf)throw new m("not_a_key",`${n} is larger than a .p8 key`);i=await yf(p,"utf8")}catch(p){throw p instanceof m?p:new m("unreadable_file",`${n} could not be read: ${p instanceof Error?p.message:String(p)}`)}if(!i.includes("-----BEGIN PRIVATE KEY-----"))throw new m("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);await b(e,t,`connect the Apple account of issuer ${s} with key ${a}`);let o=await c(e,t,"POST","/stores/apple/grants",void 0,R()),l=Ht(e,t,o.connect_url,"the connect link"),d={grant:o.grant,issuer_id:s.trim(),key_id:a,private_key:i,file_name:r},u=await bs(e,`${l}/stores/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(d)},"connecting the Apple key");e.out.result(u,()=>{e.out.line(`Connected key ${a} to ${M(t)}.`),qr(e,u)})}},Pf={name:"stores:apple:check",summary:"ask Apple about the connected key again: after the account holder accepted its agreements, or its role changed",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"POST","/stores/apple/check",void 0,R());e.out.result(n,()=>qr(e,n))}},Rf={name:"stores:apple:disconnect",summary:"take the environment's Apple key away (Apple keeps its apps and builds)",scope:"environment",async run(e){let t=await h(e);await b(e,t,"disconnect the Apple account"),await c(e,t,"DELETE","/stores/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the Apple account from ${M(t)}.`))}};function Tf(e,t){t.asc_app_id===null&&e.out.line(`Apple has no App Store Connect record for ${t.bundle_id} yet: make it on the website (App Store Connect \u2192 Apps \u2192 +, bundle id ${t.bundle_id}), then link again.`)}var Ef={name:"stores:apps",summary:"the environment's apps linked to their store",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apps");e.out.result(n,()=>{n.apps.length===0?e.out.line("No app is linked yet: palbase stores:apps:link <app id>"):e.out.table(n.apps,[{header:"app",value:s=>s.app_id},{header:"name",value:s=>s.name},{header:"bundle id",value:s=>s.bundle_id},{header:"team",value:s=>s.team_id},{header:"app store connect",value:s=>s.asc_app_id??"no record yet"}])})}},Cf={name:"stores:apps:link",summary:"link an app of the project to the App Store: its bundle id is registered with Apple, its record looked up",usage:"<app id> [--name <name at Apple>]",args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"its name at Apple (default: the bundle id)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags.name!==void 0?{name:e.flags.name}:{};await b(e,t,`link the app ${n} to the App Store`);let r=await c(e,t,"POST",`/stores/apps/${_(n)}/link`,s,R());e.out.result(r,()=>{e.out.line(`Linked ${r.name} (${r.bundle_id}, team ${r.team_id}).`),Tf(e,r)})}},jf={name:"stores:feedback",summary:"an app's TestFlight feedback: testers' screenshots and crashes",usage:"<app id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=await c(e,t,"GET",`/stores/apps/${_(n)}/feedback`);e.out.result(s,()=>{s.feedback.length===0?e.out.line("No feedback yet."):e.out.table(s.feedback,[{header:"when",value:r=>J(r.created_at)},{header:"kind",value:r=>r.kind},{header:"tester",value:r=>r.email??""},{header:"comment",value:r=>r.comment??""}])})}};function Of(e,t){e.out.line(`Package ${t.id} of ${t.app_id}: build ${t.build_number}${t.version?`, version ${t.version}`:""} \u2014 ${t.status==="uploaded"?`uploaded ${J(t.uploaded_at)}`:"waiting for its build"}`),t.size!==null&&e.out.line(`.ipa: ${t.size} bytes, sha256 ${t.sha256??"?"}`),t.bundles.length>0&&e.out.line(`bundles: ${t.bundles.map(n=>n.bundle_id+(n.path?` (${n.path})`:"")).join(", ")}`),t.notes&&e.out.line(`notes: ${t.notes}`),e.out.line(`made ${J(t.created_at)} by ${t.created_by} (command ${t.command_id})`);for(let n of t.deliveries)e.out.line(`${n.channel}: ${tp(n)}`)}function tp(e){let t=[e.status];return e.processing_state&&t.push(`Apple: ${e.processing_state}`),e.groups.length>0&&t.push(`${e.groups.length} group${e.groups.length===1?"":"s"}`),e.error_code&&t.push(`${e.error_code}${e.error?` \u2014 ${e.error}`:""}`),t.join(", ")}var Df={name:"stores:packages",summary:"the packages handed to the stores, newest first",usage:"[--app <app id>] [--limit <n>]",flags:{app:{type:"string",value:"app id",description:"only this app's"},limit:{type:"string",value:"n",description:"how many (1\u2013200, default 20)"}},scope:"environment",async run(e){let t=await h(e),n=Ue(e.flags.limit,"limit")??20;if(n<1||n>200)throw g("--limit is a whole number from 1 to 200");let s=new URLSearchParams({limit:String(n),...e.flags.app?{app_id:e.flags.app}:{}}),r=await c(e,t,"GET",`/stores/packages?${s}`);e.out.result(r,()=>{r.packages.length===0?e.out.line("No package yet."):e.out.table(r.packages,[{header:"package",value:a=>a.id},{header:"app",value:a=>a.app_id},{header:"build",value:a=>String(a.build_number)},{header:"version",value:a=>a.version??""},{header:"status",value:a=>a.status},{header:"testflight",value:a=>a.deliveries.find(i=>i.channel==="testflight")?.status??""},{header:"made",value:a=>J(a.created_at)}]),r.next_cursor&&e.out.note(`More than ${n}: raise --limit to see older ones.`)})}},If={name:"stores:packages:get",summary:"one package: its build, its bundles and its deliveries",usage:"<package id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",`/stores/packages/${_(ae(e,0,"the package's id"))}`);e.out.result(n,()=>Of(e,n))}},Lf={name:"stores:packages:create",summary:"make a package of an app: its build number, and the URL and token its .ipa is uploaded with",usage:"<app id> --command-id <id> [--notes <text>] [--entitlements-file <json>]",details:["The build is made with the build number it answers (CFBundleVersion), then uploaded: palbase stores:packages:upload.","The same --command-id again answers the same package, with a new upload token while it waits for its build: a build machine sends its run's id."],args:{min:1,max:1},flags:{"command-id":{type:"string",value:"id",description:"names the request (a CI run id): sent again, the same package"},notes:{type:"string",value:"text",description:"notes kept with it (a commit, a branch)"},"entitlements-file":{type:"string",value:"json",description:'what each bundle asks for, by bundle id ({"com.acme.app": {"aps-environment": "production"}})'}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags["command-id"];if(s===void 0||s.trim()==="")throw g("--command-id names the request: sent again, it answers the same package");let r={command_id:s};if(e.flags.notes!==void 0&&(r.notes=e.flags.notes),e.flags["entitlements-file"]!==void 0){let o=he(await ve(e,e.flags["entitlements-file"],"the entitlements"),"the entitlements"),l=d=>typeof d=="object"&&d!==null&&!Array.isArray(d);if(!l(o)||!Object.values(o).every(l))throw g("the entitlements are an object of bundle id \u2192 its entitlements object");r.entitlements=o}await b(e,t,`make a package of ${n}`);let a=await we(e,t,"POST",`/stores/apps/${_(n)}/packages`,r,R()),i=a.body;e.out.result(i,()=>{if(e.out.line(`Package ${i.id} of ${i.app_id}: build number ${i.build_number}${a.status===200?" (made before by this command id)":""}.`),i.status==="uploaded"){e.out.line("Its build has arrived already.");return}e.out.line(`Build it with CFBundleVersion ${i.build_number}, then upload its .ipa:`),i.upload&&(e.out.line(` palbase stores:packages:upload ${i.id} --file <app.ipa> --token ${i.upload.token}`),e.out.line(`url: ${i.upload.url}`),e.out.line(`token: ${i.upload.token} (shown this once; it works until ${J(i.upload.expires_at)})`))})}},Nf={name:"stores:packages:upload",summary:"hand a package its build: the .ipa goes straight to the environment's address with the upload token",usage:"<package id> --file <app.ipa> --token <upload token>",details:["The token is the one stores:packages:create printed; PALBASE_UPLOAD_TOKEN stands in for --token, out of the process list.","The file goes with its Content-Length. While the module is busy with another upload it is sent again, after the wait the module names, five times at most; a package that has its build already (this token's upload arrived before) counts as uploaded."],args:{min:1,max:1},flags:{file:{type:"string",value:"app.ipa",description:"the unsigned .ipa the build machine made"},token:{type:"string",value:"upload token",description:"the package's upload token"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.file;if(s===void 0)throw g("--file names the .ipa");let r=e.flags.token??e.io.env.PALBASE_UPLOAD_TOKEN;if(r===void 0||r.trim()==="")throw g("--token is the package's upload token (stores:packages:create printed it)","or PALBASE_UPLOAD_TOKEN");let a=ep(e.io.cwd,s),i;try{let u=await Qd(a);if(!u.isFile())throw new Error("not a file");i=u.size}catch(u){throw new m("unreadable_file",`${s} could not be read: ${u instanceof Error?u.message:String(u)}`)}await b(e,t,`upload ${s} (${i} bytes) as the build of package ${n}`);let o=`${ct(e.cloud,t.env_ref)}/stores/packages/${_(n)}/artifact`,l;try{l=await ff(a)}catch(u){throw new m("unreadable_file",`${s} could not be read: ${u instanceof Error?u.message:String(u)}`)}let d=`${s} \u2192 package ${n}`;for(let u=1;;u++)try{let p=await bs(e,o,{method:"PUT",headers:{"palbase-upload-token":r,"content-type":"application/octet-stream","content-length":String(l.size),accept:"application/json"},body:l},d);e.out.result(p,()=>{e.out.line(`Package ${p.id}: build ${p.build_number}${p.version?`, version ${p.version}`:""} arrived (${p.size??l.size} bytes, sha256 ${p.sha256??"?"}).`),e.out.line(`Send it to testers: palbase stores:deliver ${p.id}`)});return}catch(p){if(!(p instanceof Y))throw p;if(p.status===409&&p.code==="package_uploaded"){e.out.result({id:n,uploaded:!0,already:!0},()=>{e.out.line(`Package ${n} has its build already: an upload with this token arrived before, and this one was not taken.`),e.out.line(`Send it to testers: palbase stores:deliver ${n}`)});return}let f=p.status===409&&p.code==="upload_in_progress"||p.status===503&&p.code==="stores_busy",y=Math.max(p.retryAfter??_f,1);if(!f||u>=Zd||y>Sf)throw p;e.out.note(`${d}: ${p.message}; trying again in ${y} s (${u+1} of ${Zd})`),await e.io.sleep(y*1e3)}}};async function qf(e,t,n,s){if(s.every(i=>Xd.test(i)))return s;let r=await c(e,t,"GET",`/stores/packages/${_(n)}`),a=(await c(e,t,"GET",`/stores/apps/${_(r.app_id)}/groups`)).groups;return s.map(i=>{if(Xd.test(i))return i;let o=a.filter(d=>d.name===i),l=o.length>0?o:a.filter(d=>d.name.toLowerCase()===i.toLowerCase());if(l.length!==1)throw new m(l.length===0?"no_such_group":"ambiguous_group",`${r.app_id} has ${l.length===0?"no group":"more than one group"} named ${JSON.stringify(i)}`,{hint:`palbase stores:groups ${r.app_id} lists them`});return l[0].id})}var Mf={name:"stores:deliver",summary:"send a package to a channel's testers: it is signed, uploaded to Apple and processed, then given to the groups",usage:"<package id> [--channel testflight] [--groups <group,\u2026>] [--what-to-test <text>] [--locale <locale>]",details:["palbase stores:deliveries <package id> follows it; an export compliance question is answered with stores:export-compliance."],args:{min:1,max:1},flags:{channel:{type:"string",value:"channel",description:"where it goes (testflight)"},groups:{type:"string",multiple:!0,value:"group,\u2026",description:"the groups it goes to once ready, by id or name"},"what-to-test":{type:"string",value:"text",description:"the note testers read"},locale:{type:"string",value:"locale",description:"the note's locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.channel??"testflight";if(!Yd.includes(s))throw g(`--channel is one of ${Yd.join(", ")}`);let r=e.flags["what-to-test"];if(e.flags.locale!==void 0&&r===void 0)throw g("--locale is the --what-to-test note's");let a={channel:s},i=me(e.flags.groups);i&&i.length>0&&(a.groups=await qf(e,t,n,i)),r!==void 0&&(a.what_to_test={text:r,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}}),await b(e,t,`send package ${n} to ${s}${a.groups?` (${a.groups.length} group${a.groups.length===1?"":"s"})`:""}`);let o=await c(e,t,"POST",`/stores/packages/${_(n)}/deliveries`,a,R());e.out.result(o,()=>{e.out.line(`Package ${n} \u2192 ${o.channel}: ${tp(o)}.`),o.status!=="ready"&&o.status!=="failed"&&e.out.line(`Follow it: palbase stores:deliveries ${n}`)})}},Uf={name:"stores:deliveries",summary:"a package's deliveries: where each is now, and what to do when one failed",usage:"<package id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=await c(e,t,"GET",`/stores/packages/${_(n)}/deliveries`);e.out.result(s,()=>{s.deliveries.length===0?e.out.line(`Package ${n} has not been sent anywhere: palbase stores:deliver ${n}`):e.out.table(s.deliveries,[{header:"channel",value:r=>r.channel},{header:"status",value:r=>r.status},{header:"apple",value:r=>r.processing_state??""},{header:"groups",value:r=>String(r.groups.length)},{header:"updated",value:r=>J(r.updated_at)},{header:"error",value:r=>r.error_code?`${r.error_code}${r.error?`: ${r.error}`:""}`:""}])})}},xf={name:"stores:what-to-test:set",summary:"change what testers read with a package's TestFlight build, once Apple holds the build",usage:"<package id> --text <text> [--locale <locale>]",details:["Apple's note on the build, and the delivery's own; before Apple holds the build the module says so (build_not_at_apple) \u2014 stores:deliver --what-to-test sets it with the delivery."],args:{min:1,max:1},flags:{text:{type:"string",value:"text",description:"the note testers read (4000 characters at most)"},locale:{type:"string",value:"locale",description:"its locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.text;if(s===void 0||s.trim()==="")throw g("--text is the note testers read");if(s.length>4e3)throw g(`the note is 4000 characters at most (${s.length} were given)`);let r={text:s,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}};await b(e,t,`change what testers read with package ${n}${r.locale?` (${r.locale})`:""}`);let a=await c(e,t,"PUT",`/stores/packages/${_(n)}/what-to-test`,r,R());e.out.result(a,()=>e.out.line(`Testers of package ${n} read${r.locale?` (${r.locale})`:""}: ${a.what_to_test??s}`))}};function np(e,t){let n=(e.args[t]??"").trim();if(n===""||n.length>40||/[\r\n]/.test(n))throw g("a group's name is 1 to 40 characters on one line");return n}var Kf={name:"stores:groups",summary:"an app's groups of testers",usage:"<app id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=await c(e,t,"GET",`/stores/apps/${_(n)}/groups`);e.out.result(s,()=>{s.groups.length===0?e.out.line(`${n} has no group: palbase stores:groups:create ${n} <name>`):e.out.table(s.groups,[{header:"group",value:r=>r.id},{header:"name",value:r=>r.name},{header:"testers",value:r=>String(r.testers)},{header:"updated",value:r=>J(r.updated_at)}])})}},Gf={name:"stores:groups:create",summary:"make a group of testers for an app (in TestFlight an internal and an external group)",usage:"<app id> <name>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=np(e,1);await b(e,t,`make the group ${JSON.stringify(s)} of ${n}`);let r=await c(e,t,"POST",`/stores/apps/${_(n)}/groups`,{name:s},R());e.out.result(r,()=>e.out.line(`Made ${r.name} (${r.id}).`))}},Bf={name:"stores:groups:rename",summary:"rename a group",usage:"<group id> <name>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=np(e,1);await b(e,t,`rename the group ${n} to ${JSON.stringify(s)}`);let r=await c(e,t,"PATCH",`/stores/groups/${_(n)}`,{name:s},R());e.out.result(r,()=>e.out.line(`${r.id} is ${r.name}.`))}},Jf={name:"stores:groups:delete",summary:"delete a group: its testers lose the builds it gave them",usage:"<group id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id");await b(e,t,`delete the group ${n}`),await c(e,t,"DELETE",`/stores/groups/${_(n)}`,void 0,R()),e.out.result({id:n,deleted:!0},()=>e.out.line(`Deleted the group ${n}.`))}},zf={name:"stores:groups:testers",summary:"a group's testers",usage:"<group id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=await c(e,t,"GET",`/stores/groups/${_(n)}/testers`);e.out.result(s,()=>{s.testers.length===0?e.out.line("No tester in the group."):e.out.table(s.testers,[{header:"email",value:r=>r.email},{header:"name",value:r=>[r.first_name,r.last_name].filter(Boolean).join(" ")},{header:"side",value:r=>r.side},{header:"added",value:r=>J(r.added_at)}])})}};function Wf(e){let t=e.trim();if(t.startsWith("[")){let n=he(t,"the testers");if(!Array.isArray(n)||!n.every(s=>typeof s=="object"&&s!==null&&typeof s.email=="string"))throw g('the testers are a JSON array of { "email": \u2026, "first_name": \u2026, "last_name": \u2026 }');return n}return t.split(/\r?\n/).map(n=>n.trim()).filter(n=>n!==""&&!n.startsWith("#")).map(n=>{let[s,r,a]=n.split(",").map(i=>i.trim());return{email:s,...r?{first_name:r}:{},...a?{last_name:a}:{}}})}var Ff={name:"stores:groups:testers:set",summary:"set who a group holds, whole: those not named leave it",usage:"<group id> (--emails <a@x,\u2026> | --file <testers.txt>)",details:[`A file names one tester a line \u2014 email, or email,First,Last \u2014 or is a JSON array; - reads it from stdin. ${Lr} at most.`],args:{min:1,max:1},flags:{emails:{type:"string",multiple:!0,value:"a@x,\u2026",description:"the testers' addresses"},file:{type:"string",value:"path",description:"the testers, one a line (- for stdin)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=me(e.flags.emails);if(s===void 0==(e.flags.file===void 0))throw g("the testers are named by --emails or --file, one of the two");let r=s?s.map(d=>({email:d})):Wf(await ve(e,e.flags.file,"the testers"));for(let d of r)if(!/^[^@\s]+@[^@\s]+$/.test(d.email))throw g(`${JSON.stringify(d.email)} is not an email address`);if(r.length>Lr)throw g(`a group holds ${Lr} testers at most (${r.length} were named)`);let a=(await c(e,t,"GET",`/stores/groups/${_(n)}/testers`)).testers,i=new Set(r.map(d=>d.email.toLowerCase())),o=a.filter(d=>!i.has(d.email.toLowerCase())).map(d=>d.email);await b(e,t,`set the testers of ${n} to ${r.length}${o.length>0?`; ${o.length} leave: ${o.join(", ")}`:""}`);let l=await c(e,t,"PUT",`/stores/groups/${_(n)}/testers`,{testers:r},R());e.out.result({...l,left:o},()=>{e.out.line(`${n} holds ${l.testers.length} tester${l.testers.length===1?"":"s"}${o.length>0?`; ${o.join(", ")} left it`:""}.`)})}},Vf={name:"stores:groups:add-build",summary:"give a group a package that TestFlight has (external testers get it after Beta App Review)",usage:"<group id> <package id>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=ae(e,1,"the package's id");await b(e,t,`give package ${s} to the group ${n}`),await c(e,t,"POST",`/stores/groups/${_(n)}/builds`,{package_id:s},R()),e.out.result({group_id:n,package_id:s},()=>e.out.line(`Gave package ${s} to ${n}.`))}},Hf={description:"description","feedback-email":"feedback_email","contact-first-name":"contact_first_name","contact-last-name":"contact_last_name","contact-phone":"contact_phone","contact-email":"contact_email","demo-account-name":"demo_account_name",notes:"notes",locale:"locale"};function Sn(e){return e===""?"(not set)":e}var Xf={name:"stores:review-info",summary:"an app's TestFlight test information as Apple holds it, in one locale, and what Beta App Review still lacks \u2014 the demo password only as set or not",usage:"<app id> [--locale <locale>]",args:{min:1,max:1},flags:{locale:{type:"string",value:"locale",description:"the locale to read (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags.locale!==void 0?`?${new URLSearchParams({locale:e.flags.locale})}`:"",r=await c(e,t,"GET",`/stores/apps/${_(n)}/testflight/review-info${s}`);e.out.result(r,()=>{let a=r.locales.filter(u=>u!==r.locale);e.out.line(`Test information of ${n} in ${r.locale}${a.length>0?` (also in ${a.join(", ")})`:""}:`);let i=[`${r.contact_first_name} ${r.contact_last_name}`.trim(),r.contact_phone,r.contact_email].filter(u=>u!=="").join(", "),o=r.demo_account_required?`required: ${Sn(r.demo_account_name)}, password ${r.demo_account_password_set?"set":"not set"}`:"not required",l=[["what to test",Sn(r.description)],["feedback email",Sn(r.feedback_email)],["review contact",Sn(i)],["demo account",o],["notes",Sn(r.notes)]],d=Math.max(...l.map(([u])=>u.length));for(let[u,p]of l)e.out.line(` ${u.padEnd(d)} ${p}`);e.out.line(r.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${r.missing.join(", ")} (palbase stores:review-info:set ${n} \u2026).`)})}},Yf={name:"stores:review-info:set",summary:"what Beta App Review needs to know of an app: only the fields given are written; it answers what is still missing",usage:"<app id> [--description <text>] [--feedback-email <a@x>] [--contact-\u2026 <\u2026>] [--demo-account-name <name> --demo-password-stdin] [--notes <text>] [--locale <locale>]",args:{min:1,max:1},flags:{description:{type:"string",value:"text",description:"what testers should test"},"feedback-email":{type:"string",value:"a@x",description:"where testers send feedback"},"contact-first-name":{type:"string",value:"name",description:"whom Apple calls: first name"},"contact-last-name":{type:"string",value:"name",description:"last name"},"contact-phone":{type:"string",value:"phone",description:"phone"},"contact-email":{type:"string",value:"a@x",description:"email"},"demo-account-name":{type:"string",value:"name",description:"a sign-in for the reviewer, when the app needs one"},"demo-password-stdin":{type:"boolean",description:"read the demo account's password from stdin (never the command line)"},notes:{type:"string",value:"text",description:"notes to the reviewer"},locale:{type:"string",value:"locale",description:"the description's and feedback address's locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s={};for(let[a,i]of Object.entries(Hf))e.flags[a]!==void 0&&(s[i]=e.flags[a]);if(e.flags["demo-password-stdin"]===!0&&(s.demo_account_password=await fe(e,"the demo account's password")),Object.keys(s).length===0)throw g("nothing to write: name at least one field","palbase help stores:review-info:set");await b(e,t,`write ${Object.keys(s).join(", ")} of ${n}'s Beta App Review information`);let r=await c(e,t,"PUT",`/stores/apps/${_(n)}/testflight/review-info`,s,R());e.out.result(r,()=>{e.out.line(r.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${r.missing.join(", ")}.`)})}};function Zf(e,t){e.out.line(`Planned ${kn[t.kind]??t.kind} of package ${t.package_id} (action ${t.id}):`),e.out.line(` ${t.summary}`),e.out.line(` hash: ${t.hash}`),e.out.line(`It runs only when confirmed, before ${J(t.expires_at)}:`),e.out.line(` palbase stores:actions:confirm ${t.id} --hash ${t.hash}`)}function Mr(e,t,n,s,r={},a){return{name:e,summary:s,usage:"<package id>",args:{min:1,max:1},scope:"environment",...r,async run(i){let o=await h(i),l=ae(i,0,"the package's id"),d=a?.(i);await b(i,o,`plan ${kn[t]} of package ${l}`);let u=await c(i,o,"POST",`/stores/packages/${_(l)}/${n}`,d,R());i.out.result(u,()=>Zf(i,u))}}}var Qf=Mr("stores:beta-review","beta_review","beta-review","plan sending a package to Beta App Review (external testers get it after Apple's review); it runs when confirmed"),ey=Mr("stores:export-compliance","export_compliance","export-compliance","plan the export compliance answer of a package's build (the question TestFlight waits on); it runs when confirmed",{usage:"<package id> --uses-non-exempt-encryption yes|no",flags:{"uses-non-exempt-encryption":{type:"string",value:"yes|no",description:"whether the app uses encryption beyond HTTPS and what iOS provides"}}},e=>{let t=Tt(e.flags["uses-non-exempt-encryption"],"uses-non-exempt-encryption");if(t===void 0)throw g("--uses-non-exempt-encryption is yes or no: whether the app uses encryption beyond HTTPS and what iOS provides");return{uses_non_exempt_encryption:t}}),ty=Mr("stores:expire","expire_build","expire","plan expiring a package's build in TestFlight (testers can no longer install it); it runs when confirmed"),ny={name:"stores:actions",summary:"the actions planned at Apple, newest first: what each does, and whether it ran",usage:"[--status planned|done|declined|failed] [--limit <n>]",flags:{status:{type:"string",value:"status",description:"only those planned, done, declined or failed"},limit:{type:"string",value:"n",description:"how many (1\u2013200, default 30)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.status;if(n!==void 0&&!["planned","done","declined","failed"].includes(n))throw g("--status is planned, done, declined or failed");let s=Ue(e.flags.limit,"limit")??30;if(s<1||s>200)throw g("--limit is a whole number from 1 to 200");let r=new URLSearchParams({limit:String(s),...n?{status:n}:{}}),a=await c(e,t,"GET",`/stores/actions?${r}`);e.out.result(a,()=>{a.actions.length===0?e.out.line(n?`No ${n} action.`:"No action yet."):e.out.table(a.actions,[{header:"action",value:i=>i.id},{header:"kind",value:i=>i.kind},{header:"package",value:i=>i.package_id},{header:"status",value:i=>i.status},{header:"planned",value:i=>J(i.created_at)},{header:"what",value:i=>i.summary}]),a.next_cursor&&e.out.note(`More than ${s}: raise --limit to see older ones.`)})}},sy={name:"stores:actions:confirm",summary:"run a planned action at Apple: it names the hash of the plan that was shown, and is asked about first",usage:"<action id> --hash <hash>",details:["What it does is shown, then asked about \u2014 in every environment, as it cannot be undone at Apple. -y confirms without asking."],args:{min:1,max:1},flags:{hash:{type:"string",value:"hash",description:"the plan's hash, as it was shown when it was planned"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the action's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw g("--hash is the plan's hash, as stores:actions or the plan printed it: a confirmation names the plan it saw");let r=await c(e,t,"GET",`/stores/actions/${_(n)}`);if(r.status!=="planned")throw new m("action_not_planned",`the action ${n} is ${r.status}: only a planned one is confirmed`);await b(e,t,`run ${kn[r.kind]??r.kind} of package ${r.package_id}: ${r.summary}`,"It is done at Apple and cannot be undone");let a=await c(e,t,"POST",`/stores/actions/${_(n)}/confirm`,{hash:s},R());e.out.result(a,()=>{e.out.line(`${kn[a.kind]??a.kind} of package ${a.package_id}: ${a.status}.`),a.status==="failed"&&Object.keys(a.result??{}).length>0&&e.out.line(`Apple answered: ${JSON.stringify(a.result)}`)})}},ry={name:"stores:actions:decline",summary:"decline a planned action: it never runs",usage:"<action id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the action's id");await b(e,t,`decline the action ${n}`);let s=await c(e,t,"POST",`/stores/actions/${_(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined ${kn[s.kind]??s.kind} of package ${s.package_id}.`))}},Nr={"3m":"MONTHLY_RENEWAL_EVERY_THREE_MINUTES","5m":"MONTHLY_RENEWAL_EVERY_FIVE_MINUTES","15m":"MONTHLY_RENEWAL_EVERY_FIFTEEN_MINUTES","30m":"MONTHLY_RENEWAL_EVERY_THIRTY_MINUTES","1h":"MONTHLY_RENEWAL_EVERY_ONE_HOUR"},sp=e=>Object.entries(Nr).find(([,t])=>t===e)?.[0]??e,ay=100;function iy(e,t){t.testers.length===0&&e.out.line("The team has no sandbox tester yet."),e.out.table(t.testers,[{header:"email",value:n=>n.email},{header:"name",value:n=>`${n.first_name} ${n.last_name}`.trim()},{header:"storefront",value:n=>n.territory},{header:"monthly renews every",value:n=>sp(n.subscription_renewal_rate)},{header:"interrupted",value:n=>n.interrupt_purchases?"yes":"no"},{header:"id",value:n=>n.id}]),t.create_in&&e.out.note(`A person makes another, Apple's API makes none: ${t.create_in}`)}async function rp(e,t,n){let s=(await c(e,t,"GET","/stores/apple/sandbox-testers")).testers,r=[];for(let a of n){let i=s.find(o=>o.id===a||o.email.toLowerCase()===a.toLowerCase());if(!i)throw new m("no_such_tester",`the team has no sandbox tester ${a}`,{hint:`its testers: ${s.map(o=>o.email).join(", ")||"none \u2014 App Store Connect \u203A Users and Access \u203A Sandbox makes one"}`});r.some(o=>o.id===i.id)||r.push(i)}return r}var oy={name:"stores:apple:sandbox-testers",summary:"the Apple team's sandbox testers: the accounts that buy in the App Store's sandbox, how fast their subscriptions renew",details:["They are the team's, every environment's alike. Only a person makes one: App Store Connect \u203A Users and Access \u203A Sandbox."],scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apple/sandbox-testers");e.out.result(n,()=>iy(e,n))}},ly={name:"stores:apple:sandbox-testers:set",summary:"change how a sandbox tester buys: how fast its monthly subscriptions renew, whether its purchases are interrupted, its storefront",usage:"<email|id> [--renewal 3m|5m|15m|30m|1h] [--interrupt on|off] [--storefront <code>]",details:["A monthly subscription renews at the rate given; a yearly one in proportion. Apple renews one 12 times at most, then lets it end.","An interrupted purchase stops for an action of the buyer's, as when a bank asks for one: what the app does then is tested."],args:{min:1,max:1},flags:{renewal:{type:"string",value:"rate",description:"every 3m, 5m, 15m, 30m or 1h"},interrupt:{type:"string",value:"on|off",description:"interrupt its purchases"},storefront:{type:"string",value:"code",description:"the storefront it buys in, three letters (USA, DEU, TUR \u2026)"}},scope:"environment",async run(e){let t=await h(e),n={},s=e.flags.renewal;if(s!==void 0){let l=Nr[s.trim().toLowerCase()];if(l===void 0)throw g(`--renewal ${s}: a monthly subscription renews every ${Object.keys(Nr).join(", ")}`);n.subscription_renewal_rate=l}let r=e.flags.interrupt;if(r!==void 0){if(r!=="on"&&r!=="off")throw g("--interrupt is on or off");n.interrupt_purchases=r==="on"}let a=e.flags.storefront?.trim().toUpperCase();if(a!==void 0){if(!Vt.has(a))throw g(`--storefront ${e.flags.storefront}: not an App Store storefront`,"three letters, its country's ISO 3166-1 code: USA, DEU, TUR \u2026");n.territory=a}if(Object.keys(n).length===0)throw g("nothing to change: --renewal, --interrupt or --storefront");let[i]=await rp(e,t,[e.args[0]]);await b(e,t,`change the sandbox tester ${i.email}`);let o=await c(e,t,"PATCH",`/stores/apple/sandbox-testers/${_(i.id)}`,n,R());e.out.result(o,()=>{e.out.line(`${o.email}: monthly subscriptions renew every ${sp(o.subscription_renewal_rate)}, purchases ${o.interrupt_purchases?"interrupted":"not interrupted"}, storefront ${o.territory}.`),n.territory!==void 0&&e.out.line("Sign out of the sandbox account on the device and in again for the storefront to change there.")})}},dy={name:"stores:apple:sandbox-testers:clear",summary:"clear sandbox testers' purchase history: each buys again as for the first time, introductory offers included",usage:"<email|id>\u2026",details:["Apple clears them after it answers, which can take minutes. Then sign out of the sandbox account on the device and in again."],args:{min:1,max:ay},scope:"environment",async run(e){let t=await h(e),n=await rp(e,t,e.args);await b(e,t,`clear what ${n.map(s=>s.email).join(", ")} bought in the sandbox`),await c(e,t,"POST","/stores/apple/sandbox-testers/clear-purchase-history",{tester_ids:n.map(s=>s.id)},R()),e.out.result({cleared:n.map(s=>s.email)},()=>{e.out.line(`Apple is clearing what ${n.map(s=>s.email).join(", ")} bought in the sandbox.`),e.out.line("Once it has (minutes), sign out of the sandbox account on the device and in again.")})}},ap=[$f,Af,Pf,Rf,oy,ly,dy,Ef,Cf,jf,Df,If,Lf,Nf,Mf,Uf,xf,Kf,Gf,Bf,Jf,zf,Ff,Vf,Xf,Yf,Qf,ey,ty,ny,sy,ry];import{X509Certificate as py}from"node:crypto";import{mkdir as uy,readFile as xr,stat as op,writeFile as lp}from"node:fs/promises";import{basename as cy,dirname as my,relative as gy,resolve as vs,sep as hy}from"node:path";var ws={stripe:"Stripe",apple:"App Store",google:"Google Play"};function _t(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw g(`${n} is required`);return s}function fy(e){return`"${e}"`}async function dp(e){let t=he(await ve(e,e.flags.file,"the catalog"),"the catalog");if(typeof t!="object"||t===null||Array.isArray(t))throw g("the catalog is a JSON object: { base_locale, products, entitlements, credits, limits }");if("catalog"in t&&"revision"in t)throw g('the file is a saved revision (catalog:get --json): give its "catalog" alone, as catalog:get --out writes it');return t}function pp(e,t){for(let n of t)e.out.note(` ${n.warning?"warning":"problem"} ${n.path}: ${n.message} (${n.code}${n.channel?`, ${n.channel}`:""})`)}function up(e,t,n){if(t instanceof Y&&t.code==="invalid_catalog"){let s=(t.body??{}).problems??[];throw s.length>0&&(e.out.note(`The catalog breaks ${s.filter(r=>!r.warning).length} rule(s):`),pp(e,s)),new m("invalid_catalog",`${n}: ${t.message}`,{hint:"fix each problem in the file (palbase purchases:catalog:validate checks it without saving)"})}throw t}function yy(e,t){if(t.revision===0){e.out.line("No catalog yet (revision 0)."),e.out.line("Write one and save it: palbase purchases:catalog:put --file catalog.json --revision 0");return}let n=t.catalog;e.out.line(`Catalog revision ${t.revision}${t.created_at?`, saved ${J(t.created_at)}`:""}${t.created_by?` by ${t.created_by}`:""}`),e.out.line(`base language: ${n.base_locale}`),e.out.table(n.products,[{header:"product",value:s=>s.key},{header:"type",value:s=>s.type},{header:"plans",value:s=>s.plans.map(r=>r.key).join(", ")},{header:"channels",value:s=>Object.keys(s.channels).sort().join(", ")},{header:"gives",value:s=>[...s.entitlements??[],...s.credits?[`${s.credits.amount} ${s.credits.currency}`]:[]].join(", ")}]),n.products.length===0&&e.out.line("No product."),n.entitlements.length>0&&e.out.line(`entitlements: ${n.entitlements.map(s=>s.key).join(", ")}`),n.credits.length>0&&e.out.line(`credits: ${n.credits.map(s=>s.key).join(", ")}`),n.limits.length>0&&e.out.line(`limits: ${n.limits.map(s=>s.key).join(", ")}`)}var by={name:"purchases:catalog:get",summary:"the environment's catalog: its newest revision, the products it sells and what they give",usage:"[--out <catalog.json>]",details:["--out writes the catalog to a file to edit; catalog:put saves the edit with the revision it was edited from, which is printed."],flags:{out:{type:"string",value:"catalog.json",description:"write the catalog (the JSON catalog:put takes) to this file"}},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/catalog"),s=e.flags.out;if(s!==void 0){await lp(vs(e.io.cwd,s),`${JSON.stringify(n.catalog,null,2)}
651
- `),e.out.result({file:s,revision:n.revision},()=>{e.out.line(`Wrote revision ${n.revision} of the catalog to ${s}.`),e.out.line(`Save an edit of it: palbase purchases:catalog:put --file ${s} --revision ${n.revision}`)});return}e.out.result(n,()=>yy(e,n))}},wy={name:"purchases:catalog:put",summary:"save a new revision of the whole catalog, on the revision it was edited from; nothing is written to a store",usage:"--file <catalog.json> --revision <n>",details:["The module checks it first: a catalog that breaks a rule is refused with every problem, and nothing is saved.","Of two people editing at once, the second is told \u2014 the catalog moved past the revision they edited \u2014 never overwritten.","The stores are brought to it by a plan: palbase purchases:catalog:plan."],flags:{file:{type:"string",value:"catalog.json",description:"the catalog, as catalog:get --out wrote it ('-' or none: stdin)"},revision:{type:"string",value:"n",description:"the revision it was edited from (0 for the first), as catalog:get printed it"}},scope:"environment",async run(e){let t=await h(e),n=Ue(e.flags.revision,"revision");if(n===void 0)throw g("--revision is the revision the catalog was edited from (0 for the first): of two edits at once, the second is told, not saved over the first");let s=await dp(e);await b(e,t,`save the catalog (${s.products?.length??0} products) over revision ${n}`);let r;try{r=await c(e,t,"PUT","/purchases/catalog",s,{"if-match":fy(n),...R()})}catch(a){if(a instanceof Y&&a.status===412)throw new m("catalog_changed",`the catalog is no longer at revision ${n}: someone saved a newer one since it was read \u2014 nothing was saved`,{hint:"read it again (palbase purchases:catalog:get --out <file>), make the edit on it, and save it with that revision"});up(e,a,"the catalog was not saved")}e.out.result(r,()=>{e.out.line(`Saved revision ${r.revision} of the catalog.`),e.out.line("Bring the stores to it: palbase purchases:catalog:plan")})}},vy={name:"purchases:catalog:validate",summary:"check a catalog without saving it: every problem, with its path and the store whose rule it breaks",usage:"--file <catalog.json>",flags:{file:{type:"string",value:"catalog.json",description:"the catalog ('-' or none: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=await dp(e),s;try{s=await c(e,t,"POST","/purchases/catalog/validate",n,R())}catch(r){up(e,r,"the catalog cannot be read")}if(e.out.result(s,()=>{s.problems.length>0&&pp(e,s.problems),e.out.line(s.valid?`The catalog can be saved${s.problems.length>0?`, with ${s.problems.length} warning(s)`:""}.`:"The catalog cannot be saved as it is.")}),!s.valid)throw new m("invalid_catalog",`the catalog breaks ${s.problems.filter(r=>!r.warning).length} rule(s)`)}};function Ur(e,t,n){e.out.line(`${t}${n.store_env?` (${n.store_env})`:""}: ${n.steps.length===0?"nothing to write":`${n.steps.length} step(s)`}`);for(let s of n.steps)e.out.line(` ${s.op} ${s.product}${s.plan?`/${s.plan}`:""}${s.store_id?` (${s.store_id})`:""}${s.irreversible?" \u2014 permanent":""}${s.note?`: ${s.note}`:""}`);n.note&&e.out.line(` ${n.note}`)}function _y(e,t){if(e.out.line(`Plan ${t.id} for revision ${t.revision}: ${t.status}`),t.stripe?Ur(e,ws.stripe,t.stripe):e.out.line(`${ws.stripe}: no account connected (palbase purchases:stripe:grant connects one)`),Ur(e,ws.apple,t.apple),Ur(e,ws.google,t.google),t.problems.length>0){e.out.line("Warnings of the revision:");for(let r of t.problems)e.out.line(` ${r.path}: ${r.message}${r.channel?` (${r.channel})`:""}`)}t.status==="failed"&&t.error&&e.out.line(`Stripe answered: ${t.error}`);let n=t.apple_result;if(n){e.out.line(`App Store Connect (${n.bundle_id}): ${n.ok?"written":`not all written${n.error?`: ${n.error}`:""}`}`);for(let r of n.steps)e.out.line(` ${r.op} ${r.store_id}${r.state?`: ${r.state}`:""}${r.error?` \u2014 ${r.error}`:""}`)}let s=t.stripe?t.stripe.hash:t.apple.hash;t.status==="planned"&&s&&(e.out.line(` hash: ${s}`),e.out.line(t.stripe?`The Stripe part is written only when confirmed${t.apple.steps.length>0?" (the App Store part goes with it, to the stores module)":""}:`:"The App Store part is written only when confirmed (the stores module writes it to App Store Connect):"),e.out.line(` palbase purchases:catalog:confirm ${t.id} --hash ${s}`))}var Sy={name:"purchases:catalog:plan",summary:"plan bringing the stores to the catalog's newest revision, store by store, before anything is written",details:["A plan is confirmed with its Stripe part's hash (purchases:catalog:confirm) \u2014 with no Stripe account connected, its App Store part's; the App Store and Google Play parts are written by the stores module once their accounts are connected."],scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"POST","/purchases/catalog/plans",void 0,R());e.out.result(n,()=>_y(e,n))}},ky={name:"purchases:catalog:confirm",summary:"write a plan \u2014 its Stripe part now, its App Store part through the stores module: it names the hash of the plan that was shown, and is asked about first",usage:"<plan id> --hash <hash>",details:["What it writes is shown, then asked about \u2014 in every environment: a price written to Stripe is never changed or removed there, a product made in App Store Connect never goes away. -y confirms without asking."],args:{min:1,max:1},flags:{hash:{type:"string",value:"hash",description:"the hash the plan printed (its Stripe part's; with no Stripe account, its App Store part's)"}},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the plan's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw g("--hash is the hash purchases:catalog:plan printed: a confirmation names the plan it saw");let r=await c(e,t,"GET",`/purchases/catalog/plans/${_(n)}`);if(r.status!=="planned")throw new m("plan_not_open",`the plan ${n} is ${r.status}: only a planned one is confirmed`,{hint:"make a new one: palbase purchases:catalog:plan"});let a=r.apple.steps.length;if(r.stripe)await b(e,t,`write ${r.stripe.steps.length} step(s) of revision ${r.revision} to Stripe${a>0?`, and ${a} to App Store Connect`:""}`,"It writes the project's Stripe account, where a price is never changed or removed");else{if(!r.apple.hash||a===0)throw new m("no_stripe_part",`the plan ${n} has no Stripe part (no Stripe account was connected when it was made) and no App Store part to confirm`);await b(e,t,`write ${a} step(s) of revision ${r.revision} to App Store Connect`,"It makes the products in App Store Connect, where a product id never goes away")}let i=await c(e,t,"POST",`/purchases/catalog/plans/${_(n)}/confirm`,{hash:s},R());if(e.out.result(i,()=>{e.out.line(`Plan ${i.id}: ${i.status}${i.applied_at?` (${J(i.applied_at)})`:""}.`),i.status==="failed"&&i.error&&e.out.line(`Stripe answered: ${i.error}`),a>0&&!i.apple_result&&e.out.line("The stores module is writing the App Store part to App Store Connect: palbase purchases:doctor says when it took it.")}),i.status==="failed")throw new m("plan_failed",`the plan ${i.id} failed: a new plan picks up where it stopped`)}},$y={name:"purchases:catalog:decline",summary:"decline a plan: its Stripe part is never written",usage:"<plan id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the plan's id");await b(e,t,`decline the plan ${n}`);let s=await c(e,t,"POST",`/purchases/catalog/plans/${_(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined the plan ${s.id}.`))}};function ip(e){return e===null?"for life":J(e)}function Ay(e,t){e.out.line(`Customer ${t.customer_id}${t.guest?" (guest)":""}: revision ${t.revision}, as of ${J(t.requested_at)}`);let n=Object.entries(t.entitlements).sort(([s],[r])=>s<r?-1:s>r?1:0);e.out.line(n.length===0?"No entitlement.":"Entitlements:"),e.out.table(n,[{header:"entitlement",value:([s])=>s},{header:"active",value:([,s])=>s.active?"yes":"no"},{header:"until",value:([,s])=>ip(s.expires_at)},{header:"store",value:([,s])=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"renews",value:([,s])=>s.will_renew?"yes":"no"},{header:"product",value:([,s])=>s.product??"\u2014"}]),t.subscriptions.length>0&&(e.out.line("Subscriptions:"),e.out.table(t.subscriptions,[{header:"product",value:s=>`${s.product??"\u2014"}${s.plan?`/${s.plan}`:""}`},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"until",value:s=>ip(s.expires_at)},{header:"renews",value:s=>s.will_renew?"yes":"no"}])),t.purchases.length>0&&(e.out.line("Purchases:"),e.out.table(t.purchases,[{header:"product",value:s=>s.product??"\u2014"},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"bought",value:s=>J(s.purchased_at)}])),t.credits.length>0&&(e.out.line("Credits:"),e.out.table(t.credits,[{header:"credit",value:s=>s.currency},{header:"balance",value:s=>String(s.balance)},{header:"available",value:s=>String(s.available)},{header:"held",value:s=>String(s.reserved)},{header:"owed",value:s=>String(s.debt)},{header:"next expiry",value:s=>J(s.next_expiry)}])),t.limits.length>0&&(e.out.line("Limits:"),e.out.table(t.limits,[{header:"limit",value:s=>s.limit},{header:"used",value:s=>`${s.used} of ${s.max}`},{header:"held",value:s=>String(s.reserved)},{header:"left",value:s=>String(s.remaining)},{header:"resets",value:s=>J(s.resets_at)}]))}var Py={name:"purchases:customer:get",summary:"what a customer has, whatever store they bought in: entitlements, subscriptions, purchases, credits and limits",usage:"<customer id>",details:["The customer is the user's id; a guest merged into an account answers as the account."],args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=await c(e,t,"GET",`/purchases/customers/${_(n)}`);e.out.result(s,()=>Ay(e,s))}};function Ry(e){let t=e.flags.until,n=e.flags.for;if(t!==void 0&&n!==void 0)throw g("--until or --for, not both");if(t!==void 0){let s=Date.parse(t);if(!Number.isFinite(s))throw g(`--until ${t}: a date or time (2026-12-31, 2026-12-31T18:00:00Z)`);if(s<=e.io.now())throw g(`--until ${t} is not in the future`);return new Date(s).toISOString()}if(n!==void 0){let s=/^([1-9][0-9]{0,4})([hdw])$/.exec(n.trim());if(!s)throw g(`--for ${n}: a number of hours, days or weeks (12h, 30d, 2w)`);let r=Number(s[1])*{h:1,d:24,w:168}[s[2]];return new Date(e.io.now()+r*36e5).toISOString()}}var Ty={name:"purchases:customer:grant",summary:"give a customer one of the catalog's entitlements, for a while or for life, saying why \u2014 a promotion, a support case, a tester",usage:"<customer id> <entitlement> --reason <why> [--for 30d | --until <date>]",details:["The reason is the record. Without --for or --until the grant is for life. It prints the grant's id: purchases:customer:revoke ends it.","A customer not seen before is made; nothing is charged, in any store."],args:{min:2,max:2},flags:{reason:{type:"string",value:"why",description:"why it is given (500 characters at most): the record"},for:{type:"string",value:"span",description:"how long: 12h, 30d, 2w"},until:{type:"string",value:"date",description:"when it ends (2026-12-31, or a time)"}},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=_t(e,1,"the entitlement"),r=e.flags.reason?.trim();if(!r)throw g("--reason says why it is given: the grant's record");if(r.length>500)throw g("--reason is 500 characters at most");let a=Ry(e);await b(e,t,`give ${n} ${s} ${a?`until ${J(a)}`:"for life"}`);let i={entitlement:s,reason:r,...a?{expires_at:a}:{}},o=await c(e,t,"POST",`/purchases/customers/${_(n)}/grants`,i,R());e.out.result(o,()=>{e.out.line(`Gave ${o.customer_id} ${o.entitlement} ${o.expires_at?`until ${J(o.expires_at)}`:"for life"} (grant ${o.id}).`),e.out.line(`End it: palbase purchases:customer:revoke ${o.customer_id} ${o.id}`)})}},Ey={name:"purchases:customer:revoke",summary:"end a granted entitlement now; what the customer bought is not touched",usage:"<customer id> <grant id>",details:["The grant's id is the one purchases:customer:grant printed. The entitlement stays on while a purchase still gives it."],args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=_t(e,1,"the grant's id");await b(e,t,`end the grant ${s} of ${n}`),await c(e,t,"DELETE",`/purchases/customers/${_(n)}/grants/${_(s)}`,void 0,R()),e.out.result({revoked:s,customer_id:n},()=>e.out.line(`Ended the grant ${s} of ${n}.`))}},Cy={name:"purchases:stripe",summary:"the environment's Stripe account: whether one is connected, what Stripe last said of its key",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/stripe");e.out.result(n,()=>{if(!n.connected){e.out.line("No Stripe account is connected."),e.out.line("Connect one: palbase purchases:stripe:grant");return}e.out.line(`Stripe account ${n.account_id??"?"} (${n.livemode?"live":"test"} mode), key ${n.key_hint??"?"}`),e.out.line(`status: ${n.status??"not checked"}${n.checked_at?` (checked ${J(n.checked_at)})`:""}`),n.connected_at&&e.out.line(`connected ${J(n.connected_at)}${n.connected_by?` by ${n.connected_by}`:""}`),n.action&&e.out.line(`To do: ${n.action}`)})}},jy={name:"purchases:stripe:grant",summary:"a link that connects a Stripe restricted key: the key goes from the browser straight to the environment, never through Studio or this computer",details:["The link works once, for 30 minutes; a project holds 10 open at most."],scope:"environment",async run(e){let t=await h(e);await b(e,t,"make a link that connects a Stripe account");let n=await c(e,t,"POST","/purchases/stripe/grants",void 0,R());Ht(e,t,n.connect_url,"the connect link"),e.out.result({connect_url:n.connect_url,expires_at:n.expires_at},()=>{e.out.line(n.connect_url),e.out.note(`Open it in a browser and paste the Stripe restricted key; it works once, until ${J(n.expires_at)}.`)})}};function Oy(e){let t=n=>/^[\w./@:-]+$/.test(n)?n:JSON.stringify(n);return`${e.global.project!==void 0?` -P ${t(e.global.project)}`:""}${e.global.env!==void 0?` -E ${t(e.global.env)}`:""}`}async function Kr(e,t){let n=(await $e(e,t.project_id)).filter(o=>o.deleted_at===null),s=n.filter(o=>o.platform==="ios"),r=e.flags.app;if(r!==void 0){let o=Ne(n,r);if(o.platform!=="ios")throw g(`${o.slug} is a ${o.platform} app: a StoreKit configuration is an iOS app's`,`the project's iOS apps: ${s.map(l=>l.slug).join(", ")||"none"}`);return o}let a=(await Cr(e.io.cwd))?.apps.filter(o=>o.platform==="ios").map(o=>o.id)??[],i=s.filter(o=>a.includes(o.id));if(i.length===1)return i[0];if(s.length===1)return s[0];throw s.length===0?new m("no_ios_app",`${t.project_name} has no iOS app`,{hint:"register it: palbase apps:create ios <name> --team-id <team id>"}):g("name the iOS app the file is for: --app <app>",`the project's iOS apps: ${s.map(o=>o.slug).join(", ")}`)}function Dy(e,t){let n=gy(e.io.cwd,vs(e.io.cwd,t)).split(hy).join("/");if(!n.startsWith(`${B}/`)||n.split("/").includes(".."))throw g(`--out ${t}: the file goes under ${B}/, as the CLI writes nothing else in the checkout`,`palbase purchases:storekit --out ${B}/<name>.storekit`);if(!n.endsWith(".storekit"))throw g(`--out ${t}: Xcode takes a StoreKit configuration by its .storekit extension`);return n}function Iy(e){let t=new Map;for(let n of e.products)for(let s of n.channels.ios?.plans??[])t.set(s.price.currency.toUpperCase(),(t.get(s.price.currency.toUpperCase())??0)+1);return[...t].sort(([n,s],[r,a])=>a-s||(n<r?-1:1))[0]?.[0]}var Ly={name:"purchases:storekit",summary:"an Xcode StoreKit configuration of the environment's iOS products, under palbase/: a build Xcode runs buys them with no App Store",usage:"[--app <app>] [--out <palbase/\u2026.storekit>] [--storefront <code>]",details:["Each plan is the App Store product its store id names, with its text in every language the catalog has and its price; a subscription is in its group, at its tier's level, with its introductory offer.","Prices are in the storefront's currency: the catalog's price, or the amount set by hand in that currency. The storefront is --storefront, else the one the file has, else the one the catalog's currency is the App Store's own price in (USD: USA, EUR: Germany, TRY: Turkey \u2026), else USA.","Made again, the file is made whole from the catalog \u2014 the same catalog, the same file \u2014 keeping the test settings Xcode's Editor menu wrote in it. Nothing outside palbase/ is written: Xcode is pointed at the file in the scheme."],flags:{app:{type:"string",value:"app",description:"the iOS app it is for (default: the one this checkout syncs, or the project's only one)"},out:{type:"string",value:"palbase/\u2026.storekit",description:"where under palbase/ (default: palbase/environments/<env>/<app>.storekit)"},storefront:{type:"string",value:"code",description:"the App Store storefront prices are in, by its three letters (USA, DEU, TUR, GBR, JPN \u2026)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.storefront?.trim().toUpperCase();if(n!==void 0&&!Vt.has(n))throw g(`--storefront ${e.flags.storefront}: not an App Store storefront`,"a storefront is three letters, its country's ISO 3166-1 code: USA, DEU, TUR, GBR, JPN \u2026");let s=await Kr(e,t);if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(t.env_slug))throw new m("unexpected_answer",`the environment's slug ${JSON.stringify(t.env_slug)} is not one`);if(!/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/.test(s.slug))throw new m("unexpected_answer",`the project answered app ${JSON.stringify(s.slug)}, which is not one`);let r=e.flags.out!==void 0?Dy(e,e.flags.out):`${B}/environments/${t.env_slug}/${s.slug}.storekit`,a=await c(e,t,"GET","/purchases/offerings?channel=ios");if(!a.products.some(S=>(S.channels.ios?.plans.length??0)>0))throw new m("nothing_on_ios",`${t.env_name}'s catalog sells nothing on iOS yet: there is no product for Xcode to sell`,{hint:"give a product channels.ios in the catalog (palbase purchases:catalog:get --out catalog.json, then purchases:catalog:put)"});let i=await Bt(e.io.cwd,r),o,l;try{o=await xr(i,"utf8");let S=JSON.parse(o);typeof S.settings=="object"&&S.settings!==null&&!Array.isArray(S.settings)&&(l=S.settings)}catch{}let d=typeof l?._storefront=="string"&&Vt.has(l._storefront)?l._storefront:void 0,u=Iy(a);n??=d??(u!==void 0?zd(u):void 0)??"USA";let p=Vd(a,{seed:`${t.env_ref}/${s.id}`,storefront:n,settings:l}),f=o!==p.text;f&&(await uy(my(i),{recursive:!0}),await Bt(e.io.cwd,r),await lp(i,p.text));for(let S of p.notes)e.out.warn(S);let y=t.env_slug===Pe,v=Oy(e),T=[`In Xcode: Product \u203A Scheme \u203A Edit Scheme\u2026 \u203A Run \u203A Options \u203A StoreKit Configuration \u2014 choose ${r} (not listed yet? add it from that menu).`,y?`${t.project_name} \u203A ${t.env_name} is the project's production environment: it never takes a purchase Xcode signs. Make the file for a test environment (-E <env>) to see a purchase reach the server.`:`Then select the file in Xcode, choose Editor \u203A Save Public Certificate, and let ${t.env_name} take the purchases Xcode signs: palbase purchases:apple:xcode-certificate <the saved .cer file>${v}`,`Run the app and buy: Xcode sells the products itself, nothing goes to the App Store. After the catalog changes: palbase purchases:storekit${e.flags.app!==void 0?` --app ${s.slug}`:""}${e.flags.out!==void 0?` --out ${r}`:""}${v}`],A=p.counts,P=[A.subscriptions>0?`${A.subscriptions} subscription${A.subscriptions===1?"":"s"} in ${A.groups} group${A.groups===1?"":"s"}`:"",A.products>0?`${A.products} in-app purchase${A.products===1?"":"s"}`:"",A.non_renewing>0?`${A.non_renewing} non-renewing subscription${A.non_renewing===1?"":"s"}`:""].filter(S=>S!=="").join(", ");e.out.result({file:r,app:s.slug,environment:t.env_slug,revision:a.revision,storefront:p.storefront,currency:p.currency,locale:p.locale,...A,written:f,next:T},()=>{e.out.line(`${f?"Wrote":"Up to date:"} ${r} \u2014 ${P}, priced in ${p.currency} (storefront ${p.storefront}), catalog revision ${a.revision}.`),e.out.line(""),T.forEach((S,C)=>e.out.line(`${C+1}. ${S}`))})}},Ny=/^SubscriptionKey_([A-Z0-9]{10})\.p8$/,qy=/^AuthKey_[A-Z0-9]{10}\.p8$/,My=8192,Uy=16384;function cp(e,t){if(!t.connected)e.out.line("No In-App Purchase key is connected: the App Store's purchases are not taken."),e.out.line("Connect one: palbase purchases:apple:connect --key-file SubscriptionKey_<KEYID>.p8");else{e.out.line(`In-App Purchase key ${t.key_id??"?"} (issuer ${t.issuer_id??"?"}) for ${t.bundle_id??"?"}${t.app_apple_id?`, Apple ID ${t.app_apple_id}`:", its Apple ID not known yet"}`),e.out.line(`status: ${t.status??"not checked"}${t.checked_at?` (checked ${J(t.checked_at)})`:""}`);let s=t.notifications;s&&e.out.line(`notifications: ${s.url} \u2014 ${s.bound?`bound ${J(s.bound_at)}`:`not bound${s.error?`: ${s.error}`:""}`}; Apple's TEST notification ${s.verified_at?`arrived ${J(s.verified_at)}`:"not arrived yet"}`),t.connected_at&&e.out.line(`connected ${J(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.action&&e.out.line(`To do: ${t.action}`)}let n=t.xcode_certificate;e.out.line(n?`Xcode's StoreKit Testing purchases: taken, signed with ${n.subject} until ${J(n.not_after)} (sha256 ${n.fingerprint.slice(0,16)}\u2026)`:"Xcode's StoreKit Testing purchases: not taken (palbase purchases:apple:xcode-certificate <file> takes them)")}var xy={name:"purchases:apple",summary:"the environment's App Store connection: the In-App Purchase key, where the App Store reports, whether Xcode's purchases are taken",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/apple");e.out.result(n,()=>cp(e,n))}};function Ky(e,t){let n=e.environments.find(s=>s.ref===t.env_ref)?.ios?.bundle;if(n===void 0)throw new m("app_not_in_environment",`${e.slug} is not set in ${t.env_name}: the key is connected for the app's bundle there`,{hint:`palbase apps:env:set ${e.slug} -E ${t.env_slug} --bundle <bundle id>`});return n}var Gy={name:"purchases:apple:connect",summary:"connect an In-App Purchase key for an iOS app: it goes from this computer straight to the environment, never through Studio",usage:"--key-file <SubscriptionKey_KEYID.p8> [--key-id <KEYID>] [--issuer-id <issuer id>] [--app <app>] | --print-link",details:["App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes the key (SubscriptionKey_<KEYID>.p8) and shows the Issuer ID \u2014 the team's, the same as its App Store Connect API keys': left out, the one stores:apple connected is taken.","The environment verifies the app's purchases and reads its subscriptions with it, and has the stores module point the App Store's notifications at the environment.","Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead."],flags:{"key-file":{type:"string",value:"SubscriptionKey_KEYID.p8",description:"the .p8 file Apple gave when the key was made"},"key-id":{type:"string",value:"KEYID",description:"the key's id (default: from the file's name)"},"issuer-id":{type:"string",value:"issuer id",description:"the team's issuer id (default: the connected App Store Connect key's)"},app:{type:"string",value:"app",description:"the iOS app (default: the one this checkout syncs, or the project's only one)"},"print-link":{type:"boolean",description:"only print a link that takes the key in a browser (valid 30 minutes, once)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw g("--print-link or --key-file, not both");await b(e,t,"make a link that connects an In-App Purchase key");let y=await c(e,t,"POST","/purchases/apple/grants",void 0,R());Ht(e,t,y.connect_url,"the connect link"),e.out.result({connect_url:y.connect_url,expires_at:y.expires_at},()=>{e.out.line(y.connect_url),e.out.note(`Open it in a browser, pick the app and the .p8 file; it works once, until ${J(y.expires_at)}.`)});return}let n=e.flags["key-file"];if(n===void 0)throw g("--key-file names the In-App Purchase key's .p8 file","or --print-link for a link a browser opens");let s=cy(n),r=e.flags["key-id"];if(r===void 0){if(qy.test(s))throw g(`${s} is an App Store Connect API key (palbase stores:apple:connect takes it): this is the In-App Purchase key, SubscriptionKey_<KEYID>.p8`,"App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes one");if(r=Ny.exec(s)?.[1],r===void 0)throw g(`--key-id is needed: ${s} is not named SubscriptionKey_<KEYID>.p8`)}let a;try{let y=vs(e.io.cwd,n);if((await op(y)).size>My)throw new m("not_a_key",`${n} is larger than a .p8 key`);a=await xr(y,"utf8")}catch(y){throw y instanceof m?y:new m("unreadable_file",`${n} could not be read: ${y instanceof Error?y.message:String(y)}`)}if(!a.includes("-----BEGIN PRIVATE KEY-----"))throw new m("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);let i=e.flags["issuer-id"]?.trim();if(i===void 0||i===""){let y=await c(e,t,"GET","/stores/apple");if(i=y.connected?y.issuer_id:void 0,i===void 0)throw g("--issuer-id is the team's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations): no App Store Connect key is connected to read it from")}let o=await Kr(e,t),l=Ky(o,t);await b(e,t,`connect the In-App Purchase key ${r} for ${l}`);let d=await c(e,t,"POST","/purchases/apple/grants",void 0,R()),u=Ht(e,t,d.connect_url,"the connect link"),p={grant:d.grant,issuer_id:i,key_id:r,private_key:a,bundle_id:l},f=await bs(e,`${u}/purchases/apple/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(p)},"connecting the In-App Purchase key");e.out.result(f,()=>{e.out.line(`Connected key ${r} for ${l} to ${M(t)}.`),cp(e,f),e.out.line("Check what else the App Store needs: palbase purchases:doctor")})}},By={name:"purchases:apple:disconnect",summary:"forget the environment's In-App Purchase key: the App Store's purchases and notifications are refused from then on (what was bought stays)",scope:"environment",async run(e){let t=await h(e);await b(e,t,"disconnect the In-App Purchase key"),await c(e,t,"DELETE","/purchases/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the In-App Purchase key from ${M(t)}.`))}};async function Jy(e,t){let n=(await $e(e,t.project_id)).find(s=>s.deleted_at===null&&s.environments.some(r=>r.ref===t.env_ref));return n===void 0?t.env_slug===Pe:(await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`)).environment.production}async function zy(e,t){let n;try{let a=vs(t,e);if((await op(a)).size>Uy)throw new m("not_a_certificate",`${e} is larger than Xcode's certificate`);n=await xr(a)}catch(a){throw a instanceof m?a:new m("unreadable_file",`${e} could not be read: ${a instanceof Error?a.message:String(a)}`)}let s;try{s=new py(n)}catch{throw new m("not_a_certificate",`${e} is not a certificate`,{hint:"the one Xcode saves: select the .storekit file, then Editor \u203A Save Public Certificate"})}let r=n.toString("latin1");return{certificate:r.includes("-----BEGIN CERTIFICATE-----")?r.trim():n.toString("base64"),x509:s}}var Wy={name:"purchases:apple:xcode-certificate",summary:"take the purchases Xcode's StoreKit Testing signs with this certificate, as test purchases \u2014 never on production; --remove stops",usage:"<certificate.cer> | --remove",details:["Xcode saves the certificate: select the .storekit file (palbase purchases:storekit makes it), then Editor \u203A Save Public Certificate.","From then on what the app reports signed with it \u2014 Xcode's own purchases, on a developer's Mac \u2014 is a test purchase of the environment, with or without an In-App Purchase key; nothing of it is asked of Apple. Set again, it replaces the one before.","The production environment takes only what the App Store signed: it is refused there."],args:{min:0,max:1},flags:{remove:{type:"boolean",description:"stop taking Xcode's purchases (those taken stay)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.remove===!0,s=e.args[0];if(n===(s!==void 0))throw g("name the certificate file, or --remove","palbase purchases:apple:xcode-certificate StoreKitTestCertificate.cer");if(n){await b(e,t,"stop taking Xcode's StoreKit Testing purchases"),await c(e,t,"DELETE","/purchases/apple/xcode-certificate",void 0,R()),e.out.result({removed:!0},()=>e.out.line(`${M(t)} no longer takes Xcode's purchases; those it took stay.`));return}let r=await zy(s,e.io.cwd);if(await Jy(e,t))throw new m("production_environment",`${t.project_name} \u203A ${t.env_name} is the project's production environment: it takes only purchases the App Store signed, never Xcode's`,{hint:"set the certificate on a test environment: palbase purchases:apple:xcode-certificate <file> -E <env>"});await b(e,t,`take the purchases Xcode signs with ${r.x509.subject.replaceAll(`
652
- `,", ")}`);let a=await c(e,t,"PUT","/purchases/apple/xcode-certificate",{certificate:r.certificate},R());e.out.result(a,()=>{e.out.line(`${M(t)} takes the purchases Xcode signs with ${a.subject}, until ${J(a.not_after)} (sha256 ${a.fingerprint.slice(0,16)}\u2026).`),e.out.line("Run the app from Xcode and buy: each purchase is a test purchase of this environment.")})}},Fy=new Set(["sandbox_access","xcode_certificate"]),Vy={name:"purchases:doctor",summary:"whether the environment sells in the App Store now, check by check \u2014 the App Store Connect side and the purchases side \u2014 with the fix of each that fails",usage:"[--app <app>]",details:["Apple is asked now: the keys, the agreements, the app's App Store Connect record, where the App Store reports, the sandbox testers, each product's state, the catalog's last confirmation; and whether Xcode's purchases are taken.","It exits 1 while a check that is not informational fails: run it again until every line is \u2713."],flags:{app:{type:"string",value:"app",description:"the iOS app (default: the environment's one linked app)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.app!==void 0?await Kr(e,t):void 0,s=n?`?${new URLSearchParams({app:n.id})}`:"",r=p=>p.then(f=>({value:f}),f=>({error:f})),[a,i]=await Promise.all([r(c(e,t,"GET",`/stores/apple/readiness${s}`)),r(c(e,t,"GET","/purchases/apple/readiness"))]),o=(p,f)=>{if(!(f instanceof Y))throw f;return{module:p,id:`${p}_unanswered`,ok:!1,informational:!1,message:`The ${p} module could not answer: ${f.message}`,fix:f.status===503||f.status===502?"palbase purchases:doctor (again, in a moment)":""}},l=[..."value"in a?a.value.checks.map(p=>({module:"stores",id:p.id,ok:p.ok,informational:!1,message:p.message,fix:p.fix})):[o("stores",a.error)],..."value"in i?i.value.checks.map(p=>({module:"purchases",id:p.id,ok:p.ok,informational:Fy.has(p.id),message:p.message,fix:p.fix})):[o("purchases",i.error)]],d=l.filter(p=>!p.ok&&!p.informational),u=d.length===0;return e.out.result({ready:u,checks:l,stores:"value"in a?a.value:null,purchases:"value"in i?i.value:null},()=>{let p=(f,y)=>{e.out.line(y);for(let v of l.filter(T=>T.module===f))e.out.line(` ${v.informational?"i":v.ok?"\u2713":"\u2717"} ${v.message}`),(!v.ok||v.informational)&&v.fix&&e.out.line(` \u2192 ${v.fix}`)};p("stores","App Store Connect (stores)"),p("purchases","In-App Purchases (purchases)"),e.out.line(""),e.out.line(u?`Ready: ${M(t)} sells in the App Store.`:`${d.length} of ${l.filter(f=>!f.informational).length} checks fail: fix each, then run palbase purchases:doctor again.`)}),u?void 0:1}},mp=[by,wy,vy,Sy,ky,$y,Py,Ty,Ey,Cy,jy,Ly,xy,Gy,By,Wy,Vy];import{readdir as Hy,readFile as Xy,stat as Yy}from"node:fs/promises";import{join as Zy,resolve as Qy}from"node:path";var eb=1e3,gp=900*1024,tb="palbase-argon2id";function nb(e,t){let n=e.trim();if(n.startsWith("{"))try{let s=JSON.parse(n);if(Array.isArray(s.users)){if(s.hash?.pepper!==void 0)throw g(`${t} holds a pepper: it goes on stdin, not in a file`);let r=s.hash&&typeof s.hash.algorithm=="string"?{algorithm:s.hash.algorithm,...typeof s.hash.recovery_code_prefix=="string"?{recovery_code_prefix:s.hash.recovery_code_prefix}:{}}:void 0;return{users:s.users,hash:r}}}catch(s){if(s instanceof Error&&s.name==="CliError")throw s}return{users:rb(e,t)}}function sb(e){let t=[],n=[],s=0;for(let r of e){let a=Buffer.byteLength(JSON.stringify(r))+1;if(a>gp)throw new m("user_too_large",`the user ${String(r.id??"")} is larger than one call takes (${a} bytes)`);(n.length===eb||s+a>gp)&&(t.push(n),n=[],s=0),n.push(r),s+=a}return n.length>0&&t.push(n),t}function rb(e,t){let n=e.trim();if(n==="")return[];if(n.startsWith("["))try{let s=JSON.parse(n);if(Array.isArray(s)&&s.every(r=>typeof r=="object"&&r!==null&&!Array.isArray(r)))return s}catch{}return n.split(`
653
- `).flatMap((s,r)=>{if(s.trim()==="")return[];try{let a=JSON.parse(s);if(typeof a!="object"||a===null||Array.isArray(a))throw new Error("not an object");return[a]}catch(a){throw g(`${t}:${r+1} is not a user (one JSON object a line): ${a instanceof Error?a.message:String(a)}`)}})}var hp={name:"auth:import",summary:"bring users over with what they sign in with: ids, addresses, password hashes, providers, second factors, passkeys, open sessions",usage:"<file|folder> [--pepper-stdin]",details:["A folder holds import bodies, one a file (users-0001.json\u2026, each without its pepper); a file holds one such body, JSON lines (a user each) or one JSON array.","Password and recovery-code hashes keep the source's pepper: give it on stdin with --pepper-stdin \u2014 never on the command line.","Users go 1000 a call. One refused does not stop the others; a user whose id or address the project has is refused, never overwritten."],args:{min:1,max:1},flags:{"pepper-stdin":{type:"boolean",description:"read the source's pepper from stdin"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s=Qy(e.io.cwd,n),r;try{r=(await Yy(s)).isDirectory()?(await Hy(s)).filter(p=>/^users-\d+\.json$/.test(p)).sort().map(p=>Zy(s,p)):[s]}catch(p){throw g(`${n} could not be read: ${p instanceof Error?p.message:String(p)}`)}let a=[],i;for(let p of r){let f=nb(await Xy(p,"utf8"),p);if(a.push(...f.users),f.hash){if(i&&JSON.stringify(i)!==JSON.stringify(f.hash))throw g(`${p} names another hash than the files before it`);i=f.hash}}if(a.length===0)throw g(`${n} holds no user`);let o=a.some(p=>p.password_hash!==void 0||Array.isArray(p.recovery_code_hashes)&&p.recovery_code_hashes.length>0),l;if(o){if(e.flags["pepper-stdin"]!==!0)throw g("these users carry password hashes: give the source's pepper with --pepper-stdin",`palbase auth:import ${n} --pepper-stdin < pepper.txt`);l={...i??{algorithm:tb},pepper:await fe(e,"the source's pepper")}}await b(e,t,`bring ${a.length} user${a.length===1?"":"s"} into ${M(t)}'s auth`);let d={imported:0,errors:[],sessions_imported:0,sessions_skipped:0},u=0;for(let p of sb(a)){let f=await c(e,t,"POST","/admin/users/import",{...l?{hash:l}:{},users:p});d.imported+=f.imported,d.sessions_imported+=f.sessions_imported,d.sessions_skipped+=f.sessions_skipped;for(let y of f.errors??[])d.errors.push({...y,index:y.index+u});u+=p.length,u<a.length&&e.out.note(`${u} of ${a.length}\u2026`)}if(e.out.result(d,()=>{e.out.line(`Imported ${d.imported} of ${a.length} users (${d.sessions_imported} open sessions; ${d.sessions_skipped} ended ones left behind).`);for(let p of d.errors.slice(0,50))e.out.line(` ${p.id??`#${p.index+1}`}: ${p.error} \u2014 ${p.message}`);d.errors.length>50&&e.out.line(` \u2026and ${d.errors.length-50} more (--json lists every one)`)}),d.errors.length>0)throw new m("import_incomplete",`${d.errors.length} user${d.errors.length===1?" was":"s were"} not imported`)}};import{writeFile as fp}from"node:fs/promises";import{resolve as yp}from"node:path";var ab=/^usr_[A-Za-z0-9-]{1,64}$/;function Xt(e){if(!ab.test(e))throw g(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function $n(e,t,n,s){if(e===void 0)return;let r=Number(e);if(!Number.isInteger(r)||r<n||r>s)throw g(`--${t} is a whole number from ${n} to ${s}`);return r}function bp(e,t){if(e===void 0)return;let n=/^(\d+)([smhd])$/.exec(String(e));if(!n)throw g(`--${t} is a span: 30m, 2h, 7d`);return Number(n[1])*{s:1,m:60,h:3600,d:86400}[n[2]]}var Xe=e=>e?e.slice(0,16).replace("T"," "):"";function Gr(e){let t=[e.email_verified===!1?"unverified":"",e.banned?`banned${e.ban_reason?` (${j(e.ban_reason)})`:""}`:"",e.is_test?"test":"",e.has_mfa?"mfa":""].filter(Boolean);return`${j(e.id)} ${j(e.email??e.phone??"")}${t.length?` [${t.join(", ")}]`:""}`}var ib={name:"auth:users:list",summary:"the environment's users: id, email, verified, last sign-in; --banned for the banned alone",usage:"[--banned] [--limit <n>]",flags:{banned:{type:"boolean",description:"only the banned"},limit:{type:"string",value:"n",description:"how many at most (default 100)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,1e4)??100,s=[],r;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{cursor:r}:{},...e.flags.banned===!0?{banned:"true"}:{}}),i=await c(e,t,"GET",`/admin/users?${a}`);s.push(...i.users),r=i.next_cursor||void 0}while(r&&s.length<n);e.out.result({users:s},()=>{s.length===0?e.out.line("No user yet."):e.out.table(s,[{header:"id",value:a=>a.id},{header:"email",value:a=>j(a.email??a.phone??"")},{header:"verified",value:a=>a.email_verified?"yes":"no"},{header:"last sign-in",value:a=>Xe(a.last_login_at)},{header:"notes",value:a=>[a.banned?"banned":"",a.is_test?"test":"",a.has_mfa?"mfa":""].filter(Boolean).join(", ")}])})}},ob={name:"auth:users:get",summary:"one user: their email and phone, how they sign in, whether verified or banned, their roles",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),[s,r]=await Promise.all([c(e,t,"GET",`/admin/users/${_(n)}`),c(e,t,"GET",`/admin/users/${_(n)}/roles`).then(a=>Array.isArray(a)?a:a.roles??[])]);e.out.result({...s,roles:r},()=>{e.out.line(Gr(s)),e.out.line(` signs in with ${s.providers?.length?j(s.providers.join(", ")):"a password"}; made ${j(Xe(s.created_at))}, last ${j(Xe(s.last_login_at))||"never"}`),e.out.line(` roles: ${r.length?r.map(j).join(", "):"none"}`)})}},lb={name:"auth:users:create",summary:"make a user with an email and a password (read from stdin, never the command line)",usage:"<email> [--verified]",args:{min:1,max:1},flags:{verified:{type:"boolean",description:"their email is taken as verified"}},scope:"environment",async run(e){let t=await h(e),n=await fe(e,"the password");await b(e,t,`make the user ${e.args[0]}`);let s=await c(e,t,"POST","/admin/users",{email:e.args[0],password:n,...e.flags.verified===!0?{email_verified:!0}:{}},R());e.out.result(s,()=>e.out.line(`Made ${Gr(s)}.`))}};function An(e,t,n,s,r,a={}){return{name:e,summary:t,usage:"<user-id>"+(a&&"reason"in a?" [--reason <text>]":""),args:{min:1,max:1},flags:a,scope:"environment",async run(i){let o=await h(i),l=Xt(i.args[0]);await b(i,o,n(l)),await s(i,o,l),i.out.result({user:l,done:e},()=>i.out.line(r(l)))}}}var db=An("auth:users:delete","delete a user: their account, sessions and sign-ins go; backends hear user.deleted",e=>`delete the user ${e}`,(e,t,n)=>c(e,t,"DELETE",`/admin/users/${_(n)}`),e=>`Deleted ${e}.`),pb=An("auth:users:ban","ban a user: their sessions end and they cannot sign in until unbanned",e=>`ban the user ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/ban`,e.flags.reason===void 0?{}:{reason:e.flags.reason}),e=>`Banned ${e}.`,{reason:{type:"string",value:"text",description:"why (kept with the ban)"}}),ub=An("auth:users:unban","let a banned user sign in again",e=>`unban the user ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/unban`,{}),e=>`${e} may sign in again.`),cb=An("auth:users:sign-out","end every session of a user's: each device signs in again",e=>`end every session of ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/sessions/revoke-all`,{}),e=>`Every session of ${e} is ended.`),mb={name:"auth:test-users:list",summary:"the environment's test users (made with its secret key; their tokens say test: true)",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/admin/test-users"),s=Array.isArray(n)?n:n.users;e.out.result({users:s},()=>{s.length===0&&e.out.line("No test user yet: palbase auth:test-users:create makes some.");for(let r of s)e.out.line(Gr(r))})}},gb={name:"auth:test-users:create",summary:"make test users for a suite: their passwords, and with --token an access token each, shown this once",usage:"[--count <n>] [--token]",details:["A test user signs in as any user does; its tokens say test: true, so a backend can tell them apart. --token gives each an access token at once, for a test that calls the backend as that user."],flags:{count:{type:"string",value:"n",description:"how many (default 1, at most 100)"},token:{type:"boolean",description:"an access token for each, too"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.count,"count",1,100)??1;await b(e,t,`make ${n} test user(s)`);let s=await c(e,t,"POST","/admin/test-users",{count:n,with_tokens:e.flags.token===!0},R());e.out.result(s,()=>{for(let r of s.users)e.out.line(`${j(r.user_id)} ${j(r.email??"")} password ${j(r.password??"")}`),r.access_token&&e.out.line(` access token: ${j(r.access_token)}`);e.out.line("Shown this once.")})}},hb=An("auth:test-users:delete","delete a test user",e=>`delete the test user ${e}`,(e,t,n)=>c(e,t,"DELETE",`/admin/test-users/${_(n)}`),e=>`Deleted the test user ${e}.`),fb={name:"flags:users:get",summary:"every flag as one user gets it now \u2014 their own values, the targeting, the defaults \u2014 as their client reads them",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=await c(e,t,"GET",`/flags/users/${_(n)}`);e.out.result({user:n,values:s.values},()=>{let r=Object.keys(s.values??{});r.length===0&&e.out.line("The environment has no flag yet.");for(let a of r.sort())e.out.line(`${j(a)} = ${JSON.stringify(s.values[a])}`)})}},yb={name:"flags:users:set",summary:"give a user a flag's value of their own, whatever its default and targeting say",usage:"<user-id> <key> <value>",args:{min:3,max:3},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=e.args[1],r=tt(e.args[2]);await b(e,t,`give ${n} the value ${JSON.stringify(r)} of ${s}`),await c(e,t,"PUT",`/flags/users/${_(n)}/${_(s)}`,{value:r}),e.out.result({user:n,key:s,value:r},()=>e.out.line(`${n} gets ${s} = ${JSON.stringify(r)}.`))}},bb={name:"flags:users:unset",summary:"take a user's own value of a flag away, or every one with no key: the flag decides for them again",usage:"<user-id> [key]",args:{min:1,max:2},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=e.args[1];await b(e,t,s?`take ${n}'s own value of ${s} away`:`take every value of ${n}'s own away`),await c(e,t,"DELETE",s?`/flags/users/${_(n)}/${_(s)}`:`/flags/users/${_(n)}`),e.out.result({user:n,key:s??null},()=>e.out.line(s?`${n} gets ${s} as the flag says.`:`${n} gets every flag as it says.`))}};function wb(e){let t=e===void 0?[]:Array.isArray(e)?e:[String(e)];if(t.length===0)return;let n={};for(let s of t){let r=s.indexOf("=");if(r<=0)throw g(`--var is name=value, not ${JSON.stringify(s)}`);n[s.slice(0,r)]=s.slice(r+1)}return n}var vb={name:"notify:send",summary:"send an email or a text through the environment's providers: written here, or a template with its variables",usage:"<email|sms> <to> [--template <slug> --var name=value\u2026] [--subject <s> --text <t> --html <h>] [--body <text>]",details:["A message is sent as a backend sends it (POST /notify/email, /notify/sms): its delivery is in palbase notify:messages."],args:{min:2,max:2},flags:{template:{type:"string",value:"slug",description:"a template of the environment (notify:templates:list)"},var:{type:"string",value:"name=value",multiple:!0,description:"a template's variable (again for each)"},locale:{type:"string",value:"locale",description:"the template's locale"},subject:{type:"string",value:"text",description:"an email's subject"},text:{type:"string",value:"text",description:"an email's text"},html:{type:"string",value:"html",description:"an email's HTML"},body:{type:"string",value:"text",description:"a text's words"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="email"&&t!=="sms")throw g("notify:send email <to> or notify:send sms <to>");let s=await h(e),r=e.flags,a=wb(r.var),i={to:n,...r.template?{template:r.template}:{},...a?{variables:a}:{},...r.locale?{locale:r.locale}:{}};if(t==="email"){if(!r.template&&(r.subject===void 0||r.text===void 0&&r.html===void 0))throw g("an email is a --template, or a --subject with --text or --html");Object.assign(i,r.subject!==void 0?{subject:r.subject}:{},r.text!==void 0?{text:r.text}:{},r.html!==void 0?{html:r.html}:{})}else{if(!r.template&&r.body===void 0)throw g("a text is a --template or a --body");r.body!==void 0&&(i.body=r.body)}await b(e,s,`send ${t==="email"?"an email":"a text"} to ${n}`);let o=await c(e,s,"POST",`/notify/${t}`,i,R());e.out.result(o,()=>e.out.line(o.skipped?`Not sent to ${j(n)}: the user does not take this category (${j(o.message_id??"")}).`:`Queued to ${j(n)} (${j(o.message_id??"")}): palbase notify:messages says what became of it.`))}},_b={name:"notify:messages",summary:"the messages the environment sent, newest first, and what became of each",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,100)??20,s=await c(e,t,"GET",`/notify/messages?limit=${n}`);e.out.result(s,()=>{s.messages.length===0?e.out.line("Nothing sent yet."):e.out.table(s.messages,[{header:"sent",value:r=>Xe(r.created_at)},{header:"channel",value:r=>r.channel},{header:"to",value:r=>j(r.recipient_hint??"")},{header:"template",value:r=>j(r.template??"")},{header:"status",value:r=>`${r.status}${r.error?` (${j(r.error)})`:""}`},{header:"id",value:r=>r.id}])})}},Sb={name:"notify:deliverability",summary:"whether each channel can send now, through which providers, and what to do when it cannot",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/notify/deliverability");e.out.result(n,()=>e.out.table(n.channels,[{header:"channel",value:s=>s.channel},{header:"sends",value:s=>s.ok?"yes":"no"},{header:"via",value:s=>j(s.via.join(" \u2192 "))||"-"},{header:"reports",value:s=>s.reports?"yes":"no"},{header:"why / what to do",value:s=>j([s.because,s.remedy].filter(Boolean).join(" \u2014 "))}]))}},kb={name:"auth:audit",summary:"who did what in the environment's auth, newest first: sign-ins, changes to users and settings, admin actions",usage:"[--event <type>] [--limit <n>]",flags:{event:{type:"string",value:"type",description:"only this kind of event (user.login, admin.user.update\u2026)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,1e4)??50,s=[],r;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{cursor:r}:{},...typeof e.flags.event=="string"?{event_type:e.flags.event}:{}}),i=await c(e,t,"GET",`/admin/audit?${a}`);s.push(...i.events),r=i.next_cursor||void 0}while(r&&s.length<n);e.out.result({events:s},()=>{s.length===0?e.out.line("Nothing recorded yet."):e.out.table(s,[{header:"when",value:a=>Xe(a.created_at)},{header:"event",value:a=>j(a.event_type)},{header:"result",value:a=>j(a.result??"")},{header:"by",value:a=>j(a.actor?.email??a.actor?.user_id??"")},{header:"from",value:a=>j(a.actor?.ip??"")},{header:"on",value:a=>j([a.target_type,a.target_id].filter(Boolean).join(" "))}])})}},$b={name:"auth:audit:verify",summary:"whether the audit trail is whole: each event chained to the one before, none changed or taken out",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/admin/audit/verify");if(!n.valid){let s=n.broken_at?` at event ${j(n.broken_at.event_id??"?")} (number ${(n.broken_at.index??0)+1} of ${n.total}, oldest first)`:"";throw new m("audit_trail_broken",`the audit trail is broken${s}: an event there was changed or taken out`)}e.out.result(n,()=>e.out.line(`The audit trail is whole: ${n.total} events, each chained to the one before.`))}},Ab={name:"auth:audit:export",summary:"the audit trail as a file: JSON lines or CSV, a span of it, one kind of event",usage:"[--format ndjson|csv] [--from <time>] [--to <time>] [--event <type>] [--out <file>]",flags:{format:{type:"string",value:"ndjson|csv",description:"JSON lines (default) or CSV"},from:{type:"string",value:"time",description:"from this time on (RFC 3339: 2026-10-01T00:00:00Z)"},to:{type:"string",value:"time",description:"up to this time"},event:{type:"string",value:"type",description:"only this kind of event"},out:{type:"string",value:"file",description:"write it here instead of to standard output"}},scope:"environment",async run(e){let t=e.flags.format??"ndjson";if(t!=="ndjson"&&t!=="csv")throw g("--format is ndjson or csv");for(let i of["from","to"]){let o=e.flags[i];if(o!==void 0&&Number.isNaN(Date.parse(String(o))))throw g(`--${i} is a time: 2026-10-01T00:00:00Z`)}let n=await h(e),s=new URLSearchParams({format:String(t)});for(let[i,o]of[["from","from"],["to","to"],["event","event_type"]])typeof e.flags[i]=="string"&&s.set(o,e.flags[i]);let r=await c(e,n,"GET",`/admin/audit/export?${s}`),a=typeof r=="string"?r:t==="ndjson"&&Array.isArray(r)?r.map(i=>JSON.stringify(i)).join(`
654
- `):JSON.stringify(r);typeof e.flags.out=="string"?(await fp(yp(e.io.cwd,e.flags.out),a.endsWith(`
650
+ ${t}}`}var Xp=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,wf=/^AuthKey_([A-Z0-9]{10})\.p8$/,vf=8192,Yp=["testflight"],Lr=100,Zp=5,_f=5,Sf=300,kn={beta_review:"Beta App Review",export_compliance:"the export compliance answer",expire_build:"expiring the build"};function J(e){return e?e.slice(0,19).replace("T"," "):"\u2014"}function ae(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw g(`${n} is required`);return s}function Ht(e,t,n,s){let r=ct(e.cloud,t.env_ref),a;try{a=new URL(n).origin}catch{throw new m("unexpected_address",`${s} is not a URL: nothing was sent`)}if(a!==r)throw new m("unexpected_address",`${s} names ${a}, not the environment's address ${r}: nothing was sent`);return r}async function bs(e,t,n,s){let r;try{r=await e.io.fetch(t,{...n,redirect:"manual"})}catch(l){throw new m("network_error",`${s}: ${t} could not be reached: ${l instanceof Error?l.message:String(l)}`)}let a=await r.text(),i;if(a!=="")try{i=JSON.parse(a)}catch{i=a}if(r.status>=200&&r.status<300)return i;let o=i??{};throw new Y({op:s,status:r.status,code:typeof o.error=="string"?o.error:`http_${r.status}`,message:Ln(i,r.status),requestId:typeof o.request_id=="string"?o.request_id:r.headers.get("x-request-id")??void 0,retryAfter:kf(r.headers.get("retry-after"),o.retry_after,e.io.now()),body:i})}function kf(e,t,n){if(e!==null&&e.trim()!==""){let s=Number(e);if(Number.isFinite(s)&&s>=0)return s;let r=Date.parse(e);if(Number.isFinite(r))return Math.max(0,Math.ceil((r-n)/1e3))}return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0}function qr(e,t){if(!t.connected){e.out.line("No Apple account is connected."),e.out.line("Connect one: palbase stores:apple:connect --key-file AuthKey_<KEYID>.p8 --issuer-id <issuer id>");return}e.out.line(`Apple account: issuer ${t.issuer_id??"?"}, key ${t.key_id??"?"}`),e.out.line(`status: ${t.status??"not checked"}${t.checked_at?` (checked ${J(t.checked_at)})`:""}`),t.capabilities&&e.out.line(`reaches: ${t.capabilities.length>0?t.capabilities.join(", "):"nothing"}`),t.connected_at&&e.out.line(`connected ${J(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.status==="agreement"?e.out.line(`Apple waits on the account holder: ${t.action??"accept the account's agreements in App Store Connect"}, then palbase stores:apple:check`):t.action&&e.out.line(`To do: ${t.action}`)}var $f={name:"stores:apple",summary:"the environment's Apple account: whether one is connected, what Apple last said of its key, what the key reaches",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apple");e.out.result(n,()=>qr(e,n))}},Af={name:"stores:apple:connect",summary:"connect an App Store Connect API key: it goes from this computer straight to the environment, never through Studio",usage:"--key-file <AuthKey_KEYID.p8> --issuer-id <issuer id> [--key-id <KEYID>] | --print-link",details:["App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 App Store Connect API makes the key and shows the Issuer ID; stores:apple then says what its role reaches.","Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead."],flags:{"key-file":{type:"string",value:"AuthKey_KEYID.p8",description:"the .p8 file Apple gave when the key was made"},"key-id":{type:"string",value:"KEYID",description:"the key's id (default: from the file's name)"},"issuer-id":{type:"string",value:"issuer id",description:"the account's issuer id"},"print-link":{type:"boolean",description:"only print a link that takes the key in a browser (valid 30 minutes, once)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw g("--print-link or --key-file, not both");await b(e,t,"make a link that connects an Apple account");let d=await c(e,t,"POST","/stores/apple/grants",void 0,R());Ht(e,t,d.connect_url,"the connect link"),e.out.result({connect_url:d.connect_url,expires_at:d.expires_at},()=>{e.out.line(d.connect_url),e.out.note(`Open it in a browser and pick the .p8 file; it works once, until ${J(d.expires_at)}.`)});return}let n=e.flags["key-file"],s=e.flags["issuer-id"];if(n===void 0)throw g("--key-file names the .p8 file","or --print-link for a link a browser opens");if(s===void 0||s.trim()==="")throw g("--issuer-id is the account's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations)");let r=bf(n),a=e.flags["key-id"]??wf.exec(r)?.[1];if(a===void 0)throw g(`--key-id is needed: ${r} is not named AuthKey_<KEYID>.p8`);let i;try{let d=ed(e.io.cwd,n);if((await Qp(d)).size>vf)throw new m("not_a_key",`${n} is larger than a .p8 key`);i=await yf(d,"utf8")}catch(d){throw d instanceof m?d:new m("unreadable_file",`${n} could not be read: ${d instanceof Error?d.message:String(d)}`)}if(!i.includes("-----BEGIN PRIVATE KEY-----"))throw new m("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);await b(e,t,`connect the Apple account of issuer ${s} with key ${a}`);let o=await c(e,t,"POST","/stores/apple/grants",void 0,R()),l=Ht(e,t,o.connect_url,"the connect link"),p={grant:o.grant,issuer_id:s.trim(),key_id:a,private_key:i,file_name:r},u=await bs(e,`${l}/stores/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(p)},"connecting the Apple key");e.out.result(u,()=>{e.out.line(`Connected key ${a} to ${x(t)}.`),qr(e,u)})}},Pf={name:"stores:apple:check",summary:"ask Apple about the connected key again: after the account holder accepted its agreements, or its role changed",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"POST","/stores/apple/check",void 0,R());e.out.result(n,()=>qr(e,n))}},Rf={name:"stores:apple:disconnect",summary:"take the environment's Apple key away (Apple keeps its apps and builds)",scope:"environment",async run(e){let t=await h(e);await b(e,t,"disconnect the Apple account"),await c(e,t,"DELETE","/stores/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the Apple account from ${x(t)}.`))}};function Tf(e,t){t.asc_app_id===null&&e.out.line(`Apple has no App Store Connect record for ${t.bundle_id} yet: make it on the website (App Store Connect \u2192 Apps \u2192 +, bundle id ${t.bundle_id}), then link again.`)}var Ef={name:"stores:apps",summary:"the environment's apps linked to their store",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apps");e.out.result(n,()=>{n.apps.length===0?e.out.line("No app is linked yet: palbase stores:apps:link <app id>"):e.out.table(n.apps,[{header:"app",value:s=>s.app_id},{header:"name",value:s=>s.name},{header:"bundle id",value:s=>s.bundle_id},{header:"team",value:s=>s.team_id},{header:"app store connect",value:s=>s.asc_app_id??"no record yet"}])})}},Cf={name:"stores:apps:link",summary:"link an app of the project to the App Store: its bundle id is registered with Apple, its record looked up",usage:"<app id> [--name <name at Apple>]",args:{min:1,max:1},flags:{name:{type:"string",value:"name",description:"its name at Apple (default: the bundle id)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags.name!==void 0?{name:e.flags.name}:{};await b(e,t,`link the app ${n} to the App Store`);let r=await c(e,t,"POST",`/stores/apps/${_(n)}/link`,s,R());e.out.result(r,()=>{e.out.line(`Linked ${r.name} (${r.bundle_id}, team ${r.team_id}).`),Tf(e,r)})}},jf={name:"stores:feedback",summary:"an app's TestFlight feedback: testers' screenshots and crashes",usage:"<app id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=await c(e,t,"GET",`/stores/apps/${_(n)}/feedback`);e.out.result(s,()=>{s.feedback.length===0?e.out.line("No feedback yet."):e.out.table(s.feedback,[{header:"when",value:r=>J(r.created_at)},{header:"kind",value:r=>r.kind},{header:"tester",value:r=>r.email??""},{header:"comment",value:r=>r.comment??""}])})}};function Of(e,t){e.out.line(`Package ${t.id} of ${t.app_id}: build ${t.build_number}${t.version?`, version ${t.version}`:""} \u2014 ${t.status==="uploaded"?`uploaded ${J(t.uploaded_at)}`:"waiting for its build"}`),t.size!==null&&e.out.line(`.ipa: ${t.size} bytes, sha256 ${t.sha256??"?"}`),t.bundles.length>0&&e.out.line(`bundles: ${t.bundles.map(n=>n.bundle_id+(n.path?` (${n.path})`:"")).join(", ")}`),t.notes&&e.out.line(`notes: ${t.notes}`),e.out.line(`made ${J(t.created_at)} by ${t.created_by} (command ${t.command_id})`);for(let n of t.deliveries)e.out.line(`${n.channel}: ${td(n)}`)}function td(e){let t=[e.status];return e.processing_state&&t.push(`Apple: ${e.processing_state}`),e.groups.length>0&&t.push(`${e.groups.length} group${e.groups.length===1?"":"s"}`),e.error_code&&t.push(`${e.error_code}${e.error?` \u2014 ${e.error}`:""}`),t.join(", ")}var Df={name:"stores:packages",summary:"the packages handed to the stores, newest first",usage:"[--app <app id>] [--limit <n>]",flags:{app:{type:"string",value:"app id",description:"only this app's"},limit:{type:"string",value:"n",description:"how many (1\u2013200, default 20)"}},scope:"environment",async run(e){let t=await h(e),n=Me(e.flags.limit,"limit")??20;if(n<1||n>200)throw g("--limit is a whole number from 1 to 200");let s=new URLSearchParams({limit:String(n),...e.flags.app?{app_id:e.flags.app}:{}}),r=await c(e,t,"GET",`/stores/packages?${s}`);e.out.result(r,()=>{r.packages.length===0?e.out.line("No package yet."):e.out.table(r.packages,[{header:"package",value:a=>a.id},{header:"app",value:a=>a.app_id},{header:"build",value:a=>String(a.build_number)},{header:"version",value:a=>a.version??""},{header:"status",value:a=>a.status},{header:"testflight",value:a=>a.deliveries.find(i=>i.channel==="testflight")?.status??""},{header:"made",value:a=>J(a.created_at)}]),r.next_cursor&&e.out.note(`More than ${n}: raise --limit to see older ones.`)})}},If={name:"stores:packages:get",summary:"one package: its build, its bundles and its deliveries",usage:"<package id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",`/stores/packages/${_(ae(e,0,"the package's id"))}`);e.out.result(n,()=>Of(e,n))}},Lf={name:"stores:packages:create",summary:"make a package of an app: its build number, and the URL and token its .ipa is uploaded with",usage:"<app id> --command-id <id> [--notes <text>] [--entitlements-file <json>]",details:["The build is made with the build number it answers (CFBundleVersion), then uploaded: palbase stores:packages:upload.","The same --command-id again answers the same package, with a new upload token while it waits for its build: a build machine sends its run's id."],args:{min:1,max:1},flags:{"command-id":{type:"string",value:"id",description:"names the request (a CI run id): sent again, the same package"},notes:{type:"string",value:"text",description:"notes kept with it (a commit, a branch)"},"entitlements-file":{type:"string",value:"json",description:'what each bundle asks for, by bundle id ({"com.acme.app": {"aps-environment": "production"}})'}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags["command-id"];if(s===void 0||s.trim()==="")throw g("--command-id names the request: sent again, it answers the same package");let r={command_id:s};if(e.flags.notes!==void 0&&(r.notes=e.flags.notes),e.flags["entitlements-file"]!==void 0){let o=he(await ve(e,e.flags["entitlements-file"],"the entitlements"),"the entitlements"),l=p=>typeof p=="object"&&p!==null&&!Array.isArray(p);if(!l(o)||!Object.values(o).every(l))throw g("the entitlements are an object of bundle id \u2192 its entitlements object");r.entitlements=o}await b(e,t,`make a package of ${n}`);let a=await we(e,t,"POST",`/stores/apps/${_(n)}/packages`,r,R()),i=a.body;e.out.result(i,()=>{if(e.out.line(`Package ${i.id} of ${i.app_id}: build number ${i.build_number}${a.status===200?" (made before by this command id)":""}.`),i.status==="uploaded"){e.out.line("Its build has arrived already.");return}e.out.line(`Build it with CFBundleVersion ${i.build_number}, then upload its .ipa:`),i.upload&&(e.out.line(` palbase stores:packages:upload ${i.id} --file <app.ipa> --token ${i.upload.token}`),e.out.line(`url: ${i.upload.url}`),e.out.line(`token: ${i.upload.token} (shown this once; it works until ${J(i.upload.expires_at)})`))})}},Nf={name:"stores:packages:upload",summary:"hand a package its build: the .ipa goes straight to the environment's address with the upload token",usage:"<package id> --file <app.ipa> --token <upload token>",details:["The token is the one stores:packages:create printed; PALBASE_UPLOAD_TOKEN stands in for --token, out of the process list.","The file goes with its Content-Length. While the module is busy with another upload it is sent again, after the wait the module names, five times at most; a package that has its build already (this token's upload arrived before) counts as uploaded."],args:{min:1,max:1},flags:{file:{type:"string",value:"app.ipa",description:"the unsigned .ipa the build machine made"},token:{type:"string",value:"upload token",description:"the package's upload token"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.file;if(s===void 0)throw g("--file names the .ipa");let r=e.flags.token??e.io.env.PALBASE_UPLOAD_TOKEN;if(r===void 0||r.trim()==="")throw g("--token is the package's upload token (stores:packages:create printed it)","or PALBASE_UPLOAD_TOKEN");let a=ed(e.io.cwd,s),i;try{let u=await Qp(a);if(!u.isFile())throw new Error("not a file");i=u.size}catch(u){throw new m("unreadable_file",`${s} could not be read: ${u instanceof Error?u.message:String(u)}`)}await b(e,t,`upload ${s} (${i} bytes) as the build of package ${n}`);let o=`${ct(e.cloud,t.env_ref)}/stores/packages/${_(n)}/artifact`,l;try{l=await ff(a)}catch(u){throw new m("unreadable_file",`${s} could not be read: ${u instanceof Error?u.message:String(u)}`)}let p=`${s} \u2192 package ${n}`;for(let u=1;;u++)try{let d=await bs(e,o,{method:"PUT",headers:{"palbase-upload-token":r,"content-type":"application/octet-stream","content-length":String(l.size),accept:"application/json"},body:l},p);e.out.result(d,()=>{e.out.line(`Package ${d.id}: build ${d.build_number}${d.version?`, version ${d.version}`:""} arrived (${d.size??l.size} bytes, sha256 ${d.sha256??"?"}).`),e.out.line(`Send it to testers: palbase stores:deliver ${d.id}`)});return}catch(d){if(!(d instanceof Y))throw d;if(d.status===409&&d.code==="package_uploaded"){e.out.result({id:n,uploaded:!0,already:!0},()=>{e.out.line(`Package ${n} has its build already: an upload with this token arrived before, and this one was not taken.`),e.out.line(`Send it to testers: palbase stores:deliver ${n}`)});return}let f=d.status===409&&d.code==="upload_in_progress"||d.status===503&&d.code==="stores_busy",y=Math.max(d.retryAfter??_f,1);if(!f||u>=Zp||y>Sf)throw d;e.out.note(`${p}: ${d.message}; trying again in ${y} s (${u+1} of ${Zp})`),await e.io.sleep(y*1e3)}}};async function qf(e,t,n,s){if(s.every(i=>Xp.test(i)))return s;let r=await c(e,t,"GET",`/stores/packages/${_(n)}`),a=(await c(e,t,"GET",`/stores/apps/${_(r.app_id)}/groups`)).groups;return s.map(i=>{if(Xp.test(i))return i;let o=a.filter(p=>p.name===i),l=o.length>0?o:a.filter(p=>p.name.toLowerCase()===i.toLowerCase());if(l.length!==1)throw new m(l.length===0?"no_such_group":"ambiguous_group",`${r.app_id} has ${l.length===0?"no group":"more than one group"} named ${JSON.stringify(i)}`,{hint:`palbase stores:groups ${r.app_id} lists them`});return l[0].id})}var xf={name:"stores:deliver",summary:"send a package to a channel's testers: it is signed, uploaded to Apple and processed, then given to the groups",usage:"<package id> [--channel testflight] [--groups <group,\u2026>] [--what-to-test <text>] [--locale <locale>]",details:["palbase stores:deliveries <package id> follows it; an export compliance question is answered with stores:export-compliance."],args:{min:1,max:1},flags:{channel:{type:"string",value:"channel",description:"where it goes (testflight)"},groups:{type:"string",multiple:!0,value:"group,\u2026",description:"the groups it goes to once ready, by id or name"},"what-to-test":{type:"string",value:"text",description:"the note testers read"},locale:{type:"string",value:"locale",description:"the note's locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.channel??"testflight";if(!Yp.includes(s))throw g(`--channel is one of ${Yp.join(", ")}`);let r=e.flags["what-to-test"];if(e.flags.locale!==void 0&&r===void 0)throw g("--locale is the --what-to-test note's");let a={channel:s},i=me(e.flags.groups);i&&i.length>0&&(a.groups=await qf(e,t,n,i)),r!==void 0&&(a.what_to_test={text:r,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}}),await b(e,t,`send package ${n} to ${s}${a.groups?` (${a.groups.length} group${a.groups.length===1?"":"s"})`:""}`);let o=await c(e,t,"POST",`/stores/packages/${_(n)}/deliveries`,a,R());e.out.result(o,()=>{e.out.line(`Package ${n} \u2192 ${o.channel}: ${td(o)}.`),o.status!=="ready"&&o.status!=="failed"&&e.out.line(`Follow it: palbase stores:deliveries ${n}`)})}},Mf={name:"stores:deliveries",summary:"a package's deliveries: where each is now, and what to do when one failed",usage:"<package id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=await c(e,t,"GET",`/stores/packages/${_(n)}/deliveries`);e.out.result(s,()=>{s.deliveries.length===0?e.out.line(`Package ${n} has not been sent anywhere: palbase stores:deliver ${n}`):e.out.table(s.deliveries,[{header:"channel",value:r=>r.channel},{header:"status",value:r=>r.status},{header:"apple",value:r=>r.processing_state??""},{header:"groups",value:r=>String(r.groups.length)},{header:"updated",value:r=>J(r.updated_at)},{header:"error",value:r=>r.error_code?`${r.error_code}${r.error?`: ${r.error}`:""}`:""}])})}},Uf={name:"stores:what-to-test:set",summary:"change what testers read with a package's TestFlight build, once Apple holds the build",usage:"<package id> --text <text> [--locale <locale>]",details:["Apple's note on the build, and the delivery's own; before Apple holds the build the module says so (build_not_at_apple) \u2014 stores:deliver --what-to-test sets it with the delivery."],args:{min:1,max:1},flags:{text:{type:"string",value:"text",description:"the note testers read (4000 characters at most)"},locale:{type:"string",value:"locale",description:"its locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the package's id"),s=e.flags.text;if(s===void 0||s.trim()==="")throw g("--text is the note testers read");if(s.length>4e3)throw g(`the note is 4000 characters at most (${s.length} were given)`);let r={text:s,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}};await b(e,t,`change what testers read with package ${n}${r.locale?` (${r.locale})`:""}`);let a=await c(e,t,"PUT",`/stores/packages/${_(n)}/what-to-test`,r,R());e.out.result(a,()=>e.out.line(`Testers of package ${n} read${r.locale?` (${r.locale})`:""}: ${a.what_to_test??s}`))}};function nd(e,t){let n=(e.args[t]??"").trim();if(n===""||n.length>40||/[\r\n]/.test(n))throw g("a group's name is 1 to 40 characters on one line");return n}var Kf={name:"stores:groups",summary:"an app's groups of testers",usage:"<app id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=await c(e,t,"GET",`/stores/apps/${_(n)}/groups`);e.out.result(s,()=>{s.groups.length===0?e.out.line(`${n} has no group: palbase stores:groups:create ${n} <name>`):e.out.table(s.groups,[{header:"group",value:r=>r.id},{header:"name",value:r=>r.name},{header:"testers",value:r=>String(r.testers)},{header:"updated",value:r=>J(r.updated_at)}])})}},Gf={name:"stores:groups:create",summary:"make a group of testers for an app (in TestFlight an internal and an external group)",usage:"<app id> <name>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=nd(e,1);await b(e,t,`make the group ${JSON.stringify(s)} of ${n}`);let r=await c(e,t,"POST",`/stores/apps/${_(n)}/groups`,{name:s},R());e.out.result(r,()=>e.out.line(`Made ${r.name} (${r.id}).`))}},Bf={name:"stores:groups:rename",summary:"rename a group",usage:"<group id> <name>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=nd(e,1);await b(e,t,`rename the group ${n} to ${JSON.stringify(s)}`);let r=await c(e,t,"PATCH",`/stores/groups/${_(n)}`,{name:s},R());e.out.result(r,()=>e.out.line(`${r.id} is ${r.name}.`))}},Jf={name:"stores:groups:delete",summary:"delete a group: its testers lose the builds it gave them",usage:"<group id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id");await b(e,t,`delete the group ${n}`),await c(e,t,"DELETE",`/stores/groups/${_(n)}`,void 0,R()),e.out.result({id:n,deleted:!0},()=>e.out.line(`Deleted the group ${n}.`))}},zf={name:"stores:groups:testers",summary:"a group's testers",usage:"<group id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=await c(e,t,"GET",`/stores/groups/${_(n)}/testers`);e.out.result(s,()=>{s.testers.length===0?e.out.line("No tester in the group."):e.out.table(s.testers,[{header:"email",value:r=>r.email},{header:"name",value:r=>[r.first_name,r.last_name].filter(Boolean).join(" ")},{header:"side",value:r=>r.side},{header:"added",value:r=>J(r.added_at)}])})}};function Wf(e){let t=e.trim();if(t.startsWith("[")){let n=he(t,"the testers");if(!Array.isArray(n)||!n.every(s=>typeof s=="object"&&s!==null&&typeof s.email=="string"))throw g('the testers are a JSON array of { "email": \u2026, "first_name": \u2026, "last_name": \u2026 }');return n}return t.split(/\r?\n/).map(n=>n.trim()).filter(n=>n!==""&&!n.startsWith("#")).map(n=>{let[s,r,a]=n.split(",").map(i=>i.trim());return{email:s,...r?{first_name:r}:{},...a?{last_name:a}:{}}})}var Ff={name:"stores:groups:testers:set",summary:"set who a group holds, whole: those not named leave it",usage:"<group id> (--emails <a@x,\u2026> | --file <testers.txt>)",details:[`A file names one tester a line \u2014 email, or email,First,Last \u2014 or is a JSON array; - reads it from stdin. ${Lr} at most.`],args:{min:1,max:1},flags:{emails:{type:"string",multiple:!0,value:"a@x,\u2026",description:"the testers' addresses"},file:{type:"string",value:"path",description:"the testers, one a line (- for stdin)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=me(e.flags.emails);if(s===void 0==(e.flags.file===void 0))throw g("the testers are named by --emails or --file, one of the two");let r=s?s.map(p=>({email:p})):Wf(await ve(e,e.flags.file,"the testers"));for(let p of r)if(!/^[^@\s]+@[^@\s]+$/.test(p.email))throw g(`${JSON.stringify(p.email)} is not an email address`);if(r.length>Lr)throw g(`a group holds ${Lr} testers at most (${r.length} were named)`);let a=(await c(e,t,"GET",`/stores/groups/${_(n)}/testers`)).testers,i=new Set(r.map(p=>p.email.toLowerCase())),o=a.filter(p=>!i.has(p.email.toLowerCase())).map(p=>p.email);await b(e,t,`set the testers of ${n} to ${r.length}${o.length>0?`; ${o.length} leave: ${o.join(", ")}`:""}`);let l=await c(e,t,"PUT",`/stores/groups/${_(n)}/testers`,{testers:r},R());e.out.result({...l,left:o},()=>{e.out.line(`${n} holds ${l.testers.length} tester${l.testers.length===1?"":"s"}${o.length>0?`; ${o.join(", ")} left it`:""}.`)})}},Vf={name:"stores:groups:add-build",summary:"give a group a package that TestFlight has (external testers get it after Beta App Review)",usage:"<group id> <package id>",args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the group's id"),s=ae(e,1,"the package's id");await b(e,t,`give package ${s} to the group ${n}`),await c(e,t,"POST",`/stores/groups/${_(n)}/builds`,{package_id:s},R()),e.out.result({group_id:n,package_id:s},()=>e.out.line(`Gave package ${s} to ${n}.`))}},Hf={description:"description","feedback-email":"feedback_email","contact-first-name":"contact_first_name","contact-last-name":"contact_last_name","contact-phone":"contact_phone","contact-email":"contact_email","demo-account-name":"demo_account_name",notes:"notes",locale:"locale"};function Sn(e){return e===""?"(not set)":e}var Xf={name:"stores:review-info",summary:"an app's TestFlight test information as Apple holds it, in one locale, and what Beta App Review still lacks \u2014 the demo password only as set or not",usage:"<app id> [--locale <locale>]",args:{min:1,max:1},flags:{locale:{type:"string",value:"locale",description:"the locale to read (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s=e.flags.locale!==void 0?`?${new URLSearchParams({locale:e.flags.locale})}`:"",r=await c(e,t,"GET",`/stores/apps/${_(n)}/testflight/review-info${s}`);e.out.result(r,()=>{let a=r.locales.filter(u=>u!==r.locale);e.out.line(`Test information of ${n} in ${r.locale}${a.length>0?` (also in ${a.join(", ")})`:""}:`);let i=[`${r.contact_first_name} ${r.contact_last_name}`.trim(),r.contact_phone,r.contact_email].filter(u=>u!=="").join(", "),o=r.demo_account_required?`required: ${Sn(r.demo_account_name)}, password ${r.demo_account_password_set?"set":"not set"}`:"not required",l=[["what to test",Sn(r.description)],["feedback email",Sn(r.feedback_email)],["review contact",Sn(i)],["demo account",o],["notes",Sn(r.notes)]],p=Math.max(...l.map(([u])=>u.length));for(let[u,d]of l)e.out.line(` ${u.padEnd(p)} ${d}`);e.out.line(r.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${r.missing.join(", ")} (palbase stores:review-info:set ${n} \u2026).`)})}},Yf={name:"stores:review-info:set",summary:"what Beta App Review needs to know of an app: only the fields given are written; it answers what is still missing",usage:"<app id> [--description <text>] [--feedback-email <a@x>] [--contact-\u2026 <\u2026>] [--demo-account-name <name> --demo-password-stdin] [--notes <text>] [--locale <locale>]",args:{min:1,max:1},flags:{description:{type:"string",value:"text",description:"what testers should test"},"feedback-email":{type:"string",value:"a@x",description:"where testers send feedback"},"contact-first-name":{type:"string",value:"name",description:"whom Apple calls: first name"},"contact-last-name":{type:"string",value:"name",description:"last name"},"contact-phone":{type:"string",value:"phone",description:"phone"},"contact-email":{type:"string",value:"a@x",description:"email"},"demo-account-name":{type:"string",value:"name",description:"a sign-in for the reviewer, when the app needs one"},"demo-password-stdin":{type:"boolean",description:"read the demo account's password from stdin (never the command line)"},notes:{type:"string",value:"text",description:"notes to the reviewer"},locale:{type:"string",value:"locale",description:"the description's and feedback address's locale (default: the app's primary one)"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the app's id"),s={};for(let[a,i]of Object.entries(Hf))e.flags[a]!==void 0&&(s[i]=e.flags[a]);if(e.flags["demo-password-stdin"]===!0&&(s.demo_account_password=await fe(e,"the demo account's password")),Object.keys(s).length===0)throw g("nothing to write: name at least one field","palbase help stores:review-info:set");await b(e,t,`write ${Object.keys(s).join(", ")} of ${n}'s Beta App Review information`);let r=await c(e,t,"PUT",`/stores/apps/${_(n)}/testflight/review-info`,s,R());e.out.result(r,()=>{e.out.line(r.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${r.missing.join(", ")}.`)})}};function Zf(e,t){e.out.line(`Planned ${kn[t.kind]??t.kind} of package ${t.package_id} (action ${t.id}):`),e.out.line(` ${t.summary}`),e.out.line(` hash: ${t.hash}`),e.out.line(`It runs only when confirmed, before ${J(t.expires_at)}:`),e.out.line(` palbase stores:actions:confirm ${t.id} --hash ${t.hash}`)}function xr(e,t,n,s,r={},a){return{name:e,summary:s,usage:"<package id>",args:{min:1,max:1},scope:"environment",...r,async run(i){let o=await h(i),l=ae(i,0,"the package's id"),p=a?.(i);await b(i,o,`plan ${kn[t]} of package ${l}`);let u=await c(i,o,"POST",`/stores/packages/${_(l)}/${n}`,p,R());i.out.result(u,()=>Zf(i,u))}}}var Qf=xr("stores:beta-review","beta_review","beta-review","plan sending a package to Beta App Review (external testers get it after Apple's review); it runs when confirmed"),ey=xr("stores:export-compliance","export_compliance","export-compliance","plan the export compliance answer of a package's build (the question TestFlight waits on); it runs when confirmed",{usage:"<package id> --uses-non-exempt-encryption yes|no",flags:{"uses-non-exempt-encryption":{type:"string",value:"yes|no",description:"whether the app uses encryption beyond HTTPS and what iOS provides"}}},e=>{let t=Tt(e.flags["uses-non-exempt-encryption"],"uses-non-exempt-encryption");if(t===void 0)throw g("--uses-non-exempt-encryption is yes or no: whether the app uses encryption beyond HTTPS and what iOS provides");return{uses_non_exempt_encryption:t}}),ty=xr("stores:expire","expire_build","expire","plan expiring a package's build in TestFlight (testers can no longer install it); it runs when confirmed"),ny={name:"stores:actions",summary:"the actions planned at Apple, newest first: what each does, and whether it ran",usage:"[--status planned|done|declined|failed] [--limit <n>]",flags:{status:{type:"string",value:"status",description:"only those planned, done, declined or failed"},limit:{type:"string",value:"n",description:"how many (1\u2013200, default 30)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.status;if(n!==void 0&&!["planned","done","declined","failed"].includes(n))throw g("--status is planned, done, declined or failed");let s=Me(e.flags.limit,"limit")??30;if(s<1||s>200)throw g("--limit is a whole number from 1 to 200");let r=new URLSearchParams({limit:String(s),...n?{status:n}:{}}),a=await c(e,t,"GET",`/stores/actions?${r}`);e.out.result(a,()=>{a.actions.length===0?e.out.line(n?`No ${n} action.`:"No action yet."):e.out.table(a.actions,[{header:"action",value:i=>i.id},{header:"kind",value:i=>i.kind},{header:"package",value:i=>i.package_id},{header:"status",value:i=>i.status},{header:"planned",value:i=>J(i.created_at)},{header:"what",value:i=>i.summary}]),a.next_cursor&&e.out.note(`More than ${s}: raise --limit to see older ones.`)})}},sy={name:"stores:actions:confirm",summary:"run a planned action at Apple: it names the hash of the plan that was shown, and is asked about first",usage:"<action id> --hash <hash>",details:["What it does is shown, then asked about \u2014 in every environment, as it cannot be undone at Apple. -y confirms without asking."],args:{min:1,max:1},flags:{hash:{type:"string",value:"hash",description:"the plan's hash, as it was shown when it was planned"}},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the action's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw g("--hash is the plan's hash, as stores:actions or the plan printed it: a confirmation names the plan it saw");let r=await c(e,t,"GET",`/stores/actions/${_(n)}`);if(r.status!=="planned")throw new m("action_not_planned",`the action ${n} is ${r.status}: only a planned one is confirmed`);await b(e,t,`run ${kn[r.kind]??r.kind} of package ${r.package_id}: ${r.summary}`,"It is done at Apple and cannot be undone");let a=await c(e,t,"POST",`/stores/actions/${_(n)}/confirm`,{hash:s},R());e.out.result(a,()=>{e.out.line(`${kn[a.kind]??a.kind} of package ${a.package_id}: ${a.status}.`),a.status==="failed"&&Object.keys(a.result??{}).length>0&&e.out.line(`Apple answered: ${JSON.stringify(a.result)}`)})}},ry={name:"stores:actions:decline",summary:"decline a planned action: it never runs",usage:"<action id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=ae(e,0,"the action's id");await b(e,t,`decline the action ${n}`);let s=await c(e,t,"POST",`/stores/actions/${_(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined ${kn[s.kind]??s.kind} of package ${s.package_id}.`))}},Nr={"3m":"MONTHLY_RENEWAL_EVERY_THREE_MINUTES","5m":"MONTHLY_RENEWAL_EVERY_FIVE_MINUTES","15m":"MONTHLY_RENEWAL_EVERY_FIFTEEN_MINUTES","30m":"MONTHLY_RENEWAL_EVERY_THIRTY_MINUTES","1h":"MONTHLY_RENEWAL_EVERY_ONE_HOUR"},sd=e=>Object.entries(Nr).find(([,t])=>t===e)?.[0]??e,ay=100;function iy(e,t){t.testers.length===0&&e.out.line("The team has no sandbox tester yet."),e.out.table(t.testers,[{header:"email",value:n=>n.email},{header:"name",value:n=>`${n.first_name} ${n.last_name}`.trim()},{header:"storefront",value:n=>n.territory},{header:"monthly renews every",value:n=>sd(n.subscription_renewal_rate)},{header:"interrupted",value:n=>n.interrupt_purchases?"yes":"no"},{header:"id",value:n=>n.id}]),t.create_in&&e.out.note(`A person makes another, Apple's API makes none: ${t.create_in}`)}async function rd(e,t,n){let s=(await c(e,t,"GET","/stores/apple/sandbox-testers")).testers,r=[];for(let a of n){let i=s.find(o=>o.id===a||o.email.toLowerCase()===a.toLowerCase());if(!i)throw new m("no_such_tester",`the team has no sandbox tester ${a}`,{hint:`its testers: ${s.map(o=>o.email).join(", ")||"none \u2014 App Store Connect \u203A Users and Access \u203A Sandbox makes one"}`});r.some(o=>o.id===i.id)||r.push(i)}return r}var oy={name:"stores:apple:sandbox-testers",summary:"the Apple team's sandbox testers: the accounts that buy in the App Store's sandbox, how fast their subscriptions renew",details:["They are the team's, every environment's alike. Only a person makes one: App Store Connect \u203A Users and Access \u203A Sandbox."],scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/stores/apple/sandbox-testers");e.out.result(n,()=>iy(e,n))}},ly={name:"stores:apple:sandbox-testers:set",summary:"change how a sandbox tester buys: how fast its monthly subscriptions renew, whether its purchases are interrupted, its storefront",usage:"<email|id> [--renewal 3m|5m|15m|30m|1h] [--interrupt on|off] [--storefront <code>]",details:["A monthly subscription renews at the rate given; a yearly one in proportion. Apple renews one 12 times at most, then lets it end.","An interrupted purchase stops for an action of the buyer's, as when a bank asks for one: what the app does then is tested."],args:{min:1,max:1},flags:{renewal:{type:"string",value:"rate",description:"every 3m, 5m, 15m, 30m or 1h"},interrupt:{type:"string",value:"on|off",description:"interrupt its purchases"},storefront:{type:"string",value:"code",description:"the storefront it buys in, three letters (USA, DEU, TUR \u2026)"}},scope:"environment",async run(e){let t=await h(e),n={},s=e.flags.renewal;if(s!==void 0){let l=Nr[s.trim().toLowerCase()];if(l===void 0)throw g(`--renewal ${s}: a monthly subscription renews every ${Object.keys(Nr).join(", ")}`);n.subscription_renewal_rate=l}let r=e.flags.interrupt;if(r!==void 0){if(r!=="on"&&r!=="off")throw g("--interrupt is on or off");n.interrupt_purchases=r==="on"}let a=e.flags.storefront?.trim().toUpperCase();if(a!==void 0){if(!Vt.has(a))throw g(`--storefront ${e.flags.storefront}: not an App Store storefront`,"three letters, its country's ISO 3166-1 code: USA, DEU, TUR \u2026");n.territory=a}if(Object.keys(n).length===0)throw g("nothing to change: --renewal, --interrupt or --storefront");let[i]=await rd(e,t,[e.args[0]]);await b(e,t,`change the sandbox tester ${i.email}`);let o=await c(e,t,"PATCH",`/stores/apple/sandbox-testers/${_(i.id)}`,n,R());e.out.result(o,()=>{e.out.line(`${o.email}: monthly subscriptions renew every ${sd(o.subscription_renewal_rate)}, purchases ${o.interrupt_purchases?"interrupted":"not interrupted"}, storefront ${o.territory}.`),n.territory!==void 0&&e.out.line("Sign out of the sandbox account on the device and in again for the storefront to change there.")})}},py={name:"stores:apple:sandbox-testers:clear",summary:"clear sandbox testers' purchase history: each buys again as for the first time, introductory offers included",usage:"<email|id>\u2026",details:["Apple clears them after it answers, which can take minutes. Then sign out of the sandbox account on the device and in again."],args:{min:1,max:ay},scope:"environment",async run(e){let t=await h(e),n=await rd(e,t,e.args);await b(e,t,`clear what ${n.map(s=>s.email).join(", ")} bought in the sandbox`),await c(e,t,"POST","/stores/apple/sandbox-testers/clear-purchase-history",{tester_ids:n.map(s=>s.id)},R()),e.out.result({cleared:n.map(s=>s.email)},()=>{e.out.line(`Apple is clearing what ${n.map(s=>s.email).join(", ")} bought in the sandbox.`),e.out.line("Once it has (minutes), sign out of the sandbox account on the device and in again.")})}},ad=[$f,Af,Pf,Rf,oy,ly,py,Ef,Cf,jf,Df,If,Lf,Nf,xf,Mf,Uf,Kf,Gf,Bf,Jf,zf,Ff,Vf,Xf,Yf,Qf,ey,ty,ny,sy,ry];import{X509Certificate as dy}from"node:crypto";import{mkdir as uy,readFile as Ur,stat as od,writeFile as ld}from"node:fs/promises";import{basename as cy,dirname as my,relative as gy,resolve as vs,sep as hy}from"node:path";var ws={stripe:"Stripe",apple:"App Store",google:"Google Play"};function _t(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw g(`${n} is required`);return s}function fy(e){return`"${e}"`}async function pd(e){let t=he(await ve(e,e.flags.file,"the catalog"),"the catalog");if(typeof t!="object"||t===null||Array.isArray(t))throw g("the catalog is a JSON object: { base_locale, products, entitlements, credits, limits }");if("catalog"in t&&"revision"in t)throw g('the file is a saved revision (catalog:get --json): give its "catalog" alone, as catalog:get --out writes it');return t}function dd(e,t){for(let n of t)e.out.note(` ${n.warning?"warning":"problem"} ${n.path}: ${n.message} (${n.code}${n.channel?`, ${n.channel}`:""})`)}function ud(e,t,n){if(t instanceof Y&&t.code==="invalid_catalog"){let s=(t.body??{}).problems??[];throw s.length>0&&(e.out.note(`The catalog breaks ${s.filter(r=>!r.warning).length} rule(s):`),dd(e,s)),new m("invalid_catalog",`${n}: ${t.message}`,{hint:"fix each problem in the file (palbase purchases:catalog:validate checks it without saving)"})}throw t}function yy(e,t){if(t.revision===0){e.out.line("No catalog yet (revision 0)."),e.out.line("Write one and save it: palbase purchases:catalog:put --file catalog.json --revision 0");return}let n=t.catalog;e.out.line(`Catalog revision ${t.revision}${t.created_at?`, saved ${J(t.created_at)}`:""}${t.created_by?` by ${t.created_by}`:""}`),e.out.line(`base language: ${n.base_locale}`),e.out.table(n.products,[{header:"product",value:s=>s.key},{header:"type",value:s=>s.type},{header:"plans",value:s=>s.plans.map(r=>r.key).join(", ")},{header:"channels",value:s=>Object.keys(s.channels).sort().join(", ")},{header:"gives",value:s=>[...s.entitlements??[],...s.credits?[`${s.credits.amount} ${s.credits.currency}`]:[]].join(", ")}]),n.products.length===0&&e.out.line("No product."),n.entitlements.length>0&&e.out.line(`entitlements: ${n.entitlements.map(s=>s.key).join(", ")}`),n.credits.length>0&&e.out.line(`credits: ${n.credits.map(s=>s.key).join(", ")}`),n.limits.length>0&&e.out.line(`limits: ${n.limits.map(s=>s.key).join(", ")}`)}var by={name:"purchases:catalog:get",summary:"the environment's catalog: its newest revision, the products it sells and what they give",usage:"[--out <catalog.json>]",details:["--out writes the catalog to a file to edit; catalog:put saves the edit with the revision it was edited from, which is printed."],flags:{out:{type:"string",value:"catalog.json",description:"write the catalog (the JSON catalog:put takes) to this file"}},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/catalog"),s=e.flags.out;if(s!==void 0){await ld(vs(e.io.cwd,s),`${JSON.stringify(n.catalog,null,2)}
651
+ `),e.out.result({file:s,revision:n.revision},()=>{e.out.line(`Wrote revision ${n.revision} of the catalog to ${s}.`),e.out.line(`Save an edit of it: palbase purchases:catalog:put --file ${s} --revision ${n.revision}`)});return}e.out.result(n,()=>yy(e,n))}},wy={name:"purchases:catalog:put",summary:"save a new revision of the whole catalog, on the revision it was edited from; nothing is written to a store",usage:"--file <catalog.json> --revision <n>",details:["The module checks it first: a catalog that breaks a rule is refused with every problem, and nothing is saved.","Of two people editing at once, the second is told \u2014 the catalog moved past the revision they edited \u2014 never overwritten.","The stores are brought to it by a plan: palbase purchases:catalog:plan."],flags:{file:{type:"string",value:"catalog.json",description:"the catalog, as catalog:get --out wrote it ('-' or none: stdin)"},revision:{type:"string",value:"n",description:"the revision it was edited from (0 for the first), as catalog:get printed it"}},scope:"environment",async run(e){let t=await h(e),n=Me(e.flags.revision,"revision");if(n===void 0)throw g("--revision is the revision the catalog was edited from (0 for the first): of two edits at once, the second is told, not saved over the first");let s=await pd(e);await b(e,t,`save the catalog (${s.products?.length??0} products) over revision ${n}`);let r;try{r=await c(e,t,"PUT","/purchases/catalog",s,{"if-match":fy(n),...R()})}catch(a){if(a instanceof Y&&a.status===412)throw new m("catalog_changed",`the catalog is no longer at revision ${n}: someone saved a newer one since it was read \u2014 nothing was saved`,{hint:"read it again (palbase purchases:catalog:get --out <file>), make the edit on it, and save it with that revision"});ud(e,a,"the catalog was not saved")}e.out.result(r,()=>{e.out.line(`Saved revision ${r.revision} of the catalog.`),e.out.line("Bring the stores to it: palbase purchases:catalog:plan")})}},vy={name:"purchases:catalog:validate",summary:"check a catalog without saving it: every problem, with its path and the store whose rule it breaks",usage:"--file <catalog.json>",flags:{file:{type:"string",value:"catalog.json",description:"the catalog ('-' or none: stdin)"}},scope:"environment",async run(e){let t=await h(e),n=await pd(e),s;try{s=await c(e,t,"POST","/purchases/catalog/validate",n,R())}catch(r){ud(e,r,"the catalog cannot be read")}if(e.out.result(s,()=>{s.problems.length>0&&dd(e,s.problems),e.out.line(s.valid?`The catalog can be saved${s.problems.length>0?`, with ${s.problems.length} warning(s)`:""}.`:"The catalog cannot be saved as it is.")}),!s.valid)throw new m("invalid_catalog",`the catalog breaks ${s.problems.filter(r=>!r.warning).length} rule(s)`)}};function Mr(e,t,n){e.out.line(`${t}${n.store_env?` (${n.store_env})`:""}: ${n.steps.length===0?"nothing to write":`${n.steps.length} step(s)`}`);for(let s of n.steps)e.out.line(` ${s.op} ${s.product}${s.plan?`/${s.plan}`:""}${s.store_id?` (${s.store_id})`:""}${s.irreversible?" \u2014 permanent":""}${s.note?`: ${s.note}`:""}`);n.note&&e.out.line(` ${n.note}`)}function _y(e,t){if(e.out.line(`Plan ${t.id} for revision ${t.revision}: ${t.status}`),t.stripe?Mr(e,ws.stripe,t.stripe):e.out.line(`${ws.stripe}: no account connected (palbase purchases:stripe:grant connects one)`),Mr(e,ws.apple,t.apple),Mr(e,ws.google,t.google),t.problems.length>0){e.out.line("Warnings of the revision:");for(let r of t.problems)e.out.line(` ${r.path}: ${r.message}${r.channel?` (${r.channel})`:""}`)}t.status==="failed"&&t.error&&e.out.line(`Stripe answered: ${t.error}`);let n=t.apple_result;if(n){e.out.line(`App Store Connect (${n.bundle_id}): ${n.ok?"written":`not all written${n.error?`: ${n.error}`:""}`}`);for(let r of n.steps)e.out.line(` ${r.op} ${r.store_id}${r.state?`: ${r.state}`:""}${r.error?` \u2014 ${r.error}`:""}`)}let s=t.stripe?t.stripe.hash:t.apple.hash;t.status==="planned"&&s&&(e.out.line(` hash: ${s}`),e.out.line(t.stripe?`The Stripe part is written only when confirmed${t.apple.steps.length>0?" (the App Store part goes with it, to the stores module)":""}:`:"The App Store part is written only when confirmed (the stores module writes it to App Store Connect):"),e.out.line(` palbase purchases:catalog:confirm ${t.id} --hash ${s}`))}var Sy={name:"purchases:catalog:plan",summary:"plan bringing the stores to the catalog's newest revision, store by store, before anything is written",details:["A plan is confirmed with its Stripe part's hash (purchases:catalog:confirm) \u2014 with no Stripe account connected, its App Store part's; the App Store and Google Play parts are written by the stores module once their accounts are connected."],scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"POST","/purchases/catalog/plans",void 0,R());e.out.result(n,()=>_y(e,n))}},ky={name:"purchases:catalog:confirm",summary:"write a plan \u2014 its Stripe part now, its App Store part through the stores module: it names the hash of the plan that was shown, and is asked about first",usage:"<plan id> --hash <hash>",details:["What it writes is shown, then asked about \u2014 in every environment: a price written to Stripe is never changed or removed there, a product made in App Store Connect never goes away. -y confirms without asking."],args:{min:1,max:1},flags:{hash:{type:"string",value:"hash",description:"the hash the plan printed (its Stripe part's; with no Stripe account, its App Store part's)"}},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the plan's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw g("--hash is the hash purchases:catalog:plan printed: a confirmation names the plan it saw");let r=await c(e,t,"GET",`/purchases/catalog/plans/${_(n)}`);if(r.status!=="planned")throw new m("plan_not_open",`the plan ${n} is ${r.status}: only a planned one is confirmed`,{hint:"make a new one: palbase purchases:catalog:plan"});let a=r.apple.steps.length;if(r.stripe)await b(e,t,`write ${r.stripe.steps.length} step(s) of revision ${r.revision} to Stripe${a>0?`, and ${a} to App Store Connect`:""}`,"It writes the project's Stripe account, where a price is never changed or removed");else{if(!r.apple.hash||a===0)throw new m("no_stripe_part",`the plan ${n} has no Stripe part (no Stripe account was connected when it was made) and no App Store part to confirm`);await b(e,t,`write ${a} step(s) of revision ${r.revision} to App Store Connect`,"It makes the products in App Store Connect, where a product id never goes away")}let i=await c(e,t,"POST",`/purchases/catalog/plans/${_(n)}/confirm`,{hash:s},R());if(e.out.result(i,()=>{e.out.line(`Plan ${i.id}: ${i.status}${i.applied_at?` (${J(i.applied_at)})`:""}.`),i.status==="failed"&&i.error&&e.out.line(`Stripe answered: ${i.error}`),a>0&&!i.apple_result&&e.out.line("The stores module is writing the App Store part to App Store Connect: palbase purchases:doctor says when it took it.")}),i.status==="failed")throw new m("plan_failed",`the plan ${i.id} failed: a new plan picks up where it stopped`)}},$y={name:"purchases:catalog:decline",summary:"decline a plan: its Stripe part is never written",usage:"<plan id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the plan's id");await b(e,t,`decline the plan ${n}`);let s=await c(e,t,"POST",`/purchases/catalog/plans/${_(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined the plan ${s.id}.`))}};function id(e){return e===null?"for life":J(e)}function Ay(e,t){e.out.line(`Customer ${t.customer_id}${t.guest?" (guest)":""}: revision ${t.revision}, as of ${J(t.requested_at)}`);let n=Object.entries(t.entitlements).sort(([s],[r])=>s<r?-1:s>r?1:0);e.out.line(n.length===0?"No entitlement.":"Entitlements:"),e.out.table(n,[{header:"entitlement",value:([s])=>s},{header:"active",value:([,s])=>s.active?"yes":"no"},{header:"until",value:([,s])=>id(s.expires_at)},{header:"store",value:([,s])=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"renews",value:([,s])=>s.will_renew?"yes":"no"},{header:"product",value:([,s])=>s.product??"\u2014"}]),t.subscriptions.length>0&&(e.out.line("Subscriptions:"),e.out.table(t.subscriptions,[{header:"product",value:s=>`${s.product??"\u2014"}${s.plan?`/${s.plan}`:""}`},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"until",value:s=>id(s.expires_at)},{header:"renews",value:s=>s.will_renew?"yes":"no"}])),t.purchases.length>0&&(e.out.line("Purchases:"),e.out.table(t.purchases,[{header:"product",value:s=>s.product??"\u2014"},{header:"store",value:s=>`${s.store}${s.is_sandbox?" (sandbox)":""}`},{header:"state",value:s=>s.state},{header:"bought",value:s=>J(s.purchased_at)}])),t.credits.length>0&&(e.out.line("Credits:"),e.out.table(t.credits,[{header:"credit",value:s=>s.currency},{header:"balance",value:s=>String(s.balance)},{header:"available",value:s=>String(s.available)},{header:"held",value:s=>String(s.reserved)},{header:"owed",value:s=>String(s.debt)},{header:"next expiry",value:s=>J(s.next_expiry)}])),t.limits.length>0&&(e.out.line("Limits:"),e.out.table(t.limits,[{header:"limit",value:s=>s.limit},{header:"used",value:s=>`${s.used} of ${s.max}`},{header:"held",value:s=>String(s.reserved)},{header:"left",value:s=>String(s.remaining)},{header:"resets",value:s=>J(s.resets_at)}]))}var Py={name:"purchases:customer:get",summary:"what a customer has, whatever store they bought in: entitlements, subscriptions, purchases, credits and limits",usage:"<customer id>",details:["The customer is the user's id; a guest merged into an account answers as the account."],args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=await c(e,t,"GET",`/purchases/customers/${_(n)}`);e.out.result(s,()=>Ay(e,s))}};function Ry(e){let t=e.flags.until,n=e.flags.for;if(t!==void 0&&n!==void 0)throw g("--until or --for, not both");if(t!==void 0){let s=Date.parse(t);if(!Number.isFinite(s))throw g(`--until ${t}: a date or time (2026-12-31, 2026-12-31T18:00:00Z)`);if(s<=e.io.now())throw g(`--until ${t} is not in the future`);return new Date(s).toISOString()}if(n!==void 0){let s=/^([1-9][0-9]{0,4})([hdw])$/.exec(n.trim());if(!s)throw g(`--for ${n}: a number of hours, days or weeks (12h, 30d, 2w)`);let r=Number(s[1])*{h:1,d:24,w:168}[s[2]];return new Date(e.io.now()+r*36e5).toISOString()}}var Ty={name:"purchases:customer:grant",summary:"give a customer one of the catalog's entitlements, for a while or for life, saying why \u2014 a promotion, a support case, a tester",usage:"<customer id> <entitlement> --reason <why> [--for 30d | --until <date>]",details:["The reason is the record. Without --for or --until the grant is for life. It prints the grant's id: purchases:customer:revoke ends it.","A customer not seen before is made; nothing is charged, in any store."],args:{min:2,max:2},flags:{reason:{type:"string",value:"why",description:"why it is given (500 characters at most): the record"},for:{type:"string",value:"span",description:"how long: 12h, 30d, 2w"},until:{type:"string",value:"date",description:"when it ends (2026-12-31, or a time)"}},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=_t(e,1,"the entitlement"),r=e.flags.reason?.trim();if(!r)throw g("--reason says why it is given: the grant's record");if(r.length>500)throw g("--reason is 500 characters at most");let a=Ry(e);await b(e,t,`give ${n} ${s} ${a?`until ${J(a)}`:"for life"}`);let i={entitlement:s,reason:r,...a?{expires_at:a}:{}},o=await c(e,t,"POST",`/purchases/customers/${_(n)}/grants`,i,R());e.out.result(o,()=>{e.out.line(`Gave ${o.customer_id} ${o.entitlement} ${o.expires_at?`until ${J(o.expires_at)}`:"for life"} (grant ${o.id}).`),e.out.line(`End it: palbase purchases:customer:revoke ${o.customer_id} ${o.id}`)})}},Ey={name:"purchases:customer:revoke",summary:"end a granted entitlement now; what the customer bought is not touched",usage:"<customer id> <grant id>",details:["The grant's id is the one purchases:customer:grant printed. The entitlement stays on while a purchase still gives it."],args:{min:2,max:2},scope:"environment",async run(e){let t=await h(e),n=_t(e,0,"the customer's id (the user's id)"),s=_t(e,1,"the grant's id");await b(e,t,`end the grant ${s} of ${n}`),await c(e,t,"DELETE",`/purchases/customers/${_(n)}/grants/${_(s)}`,void 0,R()),e.out.result({revoked:s,customer_id:n},()=>e.out.line(`Ended the grant ${s} of ${n}.`))}},Cy={name:"purchases:stripe",summary:"the environment's Stripe account: whether one is connected, what Stripe last said of its key",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/stripe");e.out.result(n,()=>{if(!n.connected){e.out.line("No Stripe account is connected."),e.out.line("Connect one: palbase purchases:stripe:grant");return}e.out.line(`Stripe account ${n.account_id??"?"} (${n.livemode?"live":"test"} mode), key ${n.key_hint??"?"}`),e.out.line(`status: ${n.status??"not checked"}${n.checked_at?` (checked ${J(n.checked_at)})`:""}`),n.connected_at&&e.out.line(`connected ${J(n.connected_at)}${n.connected_by?` by ${n.connected_by}`:""}`),n.action&&e.out.line(`To do: ${n.action}`)})}},jy={name:"purchases:stripe:grant",summary:"a link that connects a Stripe restricted key: the key goes from the browser straight to the environment, never through Studio or this computer",details:["The link works once, for 30 minutes; a project holds 10 open at most."],scope:"environment",async run(e){let t=await h(e);await b(e,t,"make a link that connects a Stripe account");let n=await c(e,t,"POST","/purchases/stripe/grants",void 0,R());Ht(e,t,n.connect_url,"the connect link"),e.out.result({connect_url:n.connect_url,expires_at:n.expires_at},()=>{e.out.line(n.connect_url),e.out.note(`Open it in a browser and paste the Stripe restricted key; it works once, until ${J(n.expires_at)}.`)})}};function Oy(e){let t=n=>/^[\w./@:-]+$/.test(n)?n:JSON.stringify(n);return`${e.global.project!==void 0?` -P ${t(e.global.project)}`:""}${e.global.env!==void 0?` -E ${t(e.global.env)}`:""}`}async function Kr(e,t){let n=(await $e(e,t.project_id)).filter(o=>o.deleted_at===null),s=n.filter(o=>o.platform==="ios"),r=e.flags.app;if(r!==void 0){let o=Ne(n,r);if(o.platform!=="ios")throw g(`${o.slug} is a ${o.platform} app: a StoreKit configuration is an iOS app's`,`the project's iOS apps: ${s.map(l=>l.slug).join(", ")||"none"}`);return o}let a=(await Cr(e.io.cwd))?.apps.filter(o=>o.platform==="ios").map(o=>o.id)??[],i=s.filter(o=>a.includes(o.id));if(i.length===1)return i[0];if(s.length===1)return s[0];throw s.length===0?new m("no_ios_app",`${t.project_name} has no iOS app`,{hint:"register it: palbase apps:create ios <name> --team-id <team id>"}):g("name the iOS app the file is for: --app <app>",`the project's iOS apps: ${s.map(o=>o.slug).join(", ")}`)}function Dy(e,t){let n=gy(e.io.cwd,vs(e.io.cwd,t)).split(hy).join("/");if(!n.startsWith(`${B}/`)||n.split("/").includes(".."))throw g(`--out ${t}: the file goes under ${B}/, as the CLI writes nothing else in the checkout`,`palbase purchases:storekit --out ${B}/<name>.storekit`);if(!n.endsWith(".storekit"))throw g(`--out ${t}: Xcode takes a StoreKit configuration by its .storekit extension`);return n}function Iy(e){let t=new Map;for(let n of e.products)for(let s of n.channels.ios?.plans??[])t.set(s.price.currency.toUpperCase(),(t.get(s.price.currency.toUpperCase())??0)+1);return[...t].sort(([n,s],[r,a])=>a-s||(n<r?-1:1))[0]?.[0]}var Ly={name:"purchases:storekit",summary:"an Xcode StoreKit configuration of the environment's iOS products, under palbase/: a build Xcode runs buys them with no App Store",usage:"[--app <app>] [--out <palbase/\u2026.storekit>] [--storefront <code>]",details:["Each plan is the App Store product its store id names, with its text in every language the catalog has and its price; a subscription is in its group, at its tier's level, with its introductory offer.","Prices are in the storefront's currency: the catalog's price, or the amount set by hand in that currency. The storefront is --storefront, else the one the file has, else the one the catalog's currency is the App Store's own price in (USD: USA, EUR: Germany, TRY: Turkey \u2026), else USA.","Made again, the file is made whole from the catalog \u2014 the same catalog, the same file \u2014 keeping the test settings Xcode's Editor menu wrote in it. Nothing outside palbase/ is written: Xcode is pointed at the file in the scheme."],flags:{app:{type:"string",value:"app",description:"the iOS app it is for (default: the one this checkout syncs, or the project's only one)"},out:{type:"string",value:"palbase/\u2026.storekit",description:"where under palbase/ (default: palbase/environments/<env>/<app>.storekit)"},storefront:{type:"string",value:"code",description:"the App Store storefront prices are in, by its three letters (USA, DEU, TUR, GBR, JPN \u2026)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.storefront?.trim().toUpperCase();if(n!==void 0&&!Vt.has(n))throw g(`--storefront ${e.flags.storefront}: not an App Store storefront`,"a storefront is three letters, its country's ISO 3166-1 code: USA, DEU, TUR, GBR, JPN \u2026");let s=await Kr(e,t);if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(t.env_slug))throw new m("unexpected_answer",`the environment's slug ${JSON.stringify(t.env_slug)} is not one`);if(!/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/.test(s.slug))throw new m("unexpected_answer",`the project answered app ${JSON.stringify(s.slug)}, which is not one`);let r=e.flags.out!==void 0?Dy(e,e.flags.out):`${B}/environments/${t.env_slug}/${s.slug}.storekit`,a=await c(e,t,"GET","/purchases/offerings?channel=ios");if(!a.products.some(S=>(S.channels.ios?.plans.length??0)>0))throw new m("nothing_on_ios",`${t.env_name}'s catalog sells nothing on iOS yet: there is no product for Xcode to sell`,{hint:"give a product channels.ios in the catalog (palbase purchases:catalog:get --out catalog.json, then purchases:catalog:put)"});let i=await Bt(e.io.cwd,r),o,l;try{o=await Ur(i,"utf8");let S=JSON.parse(o);typeof S.settings=="object"&&S.settings!==null&&!Array.isArray(S.settings)&&(l=S.settings)}catch{}let p=typeof l?._storefront=="string"&&Vt.has(l._storefront)?l._storefront:void 0,u=Iy(a);n??=p??(u!==void 0?zp(u):void 0)??"USA";let d=Vp(a,{seed:`${t.env_ref}/${s.id}`,storefront:n,settings:l}),f=o!==d.text;f&&(await uy(my(i),{recursive:!0}),await Bt(e.io.cwd,r),await ld(i,d.text));for(let S of d.notes)e.out.warn(S);let y=t.env_slug===Pe,v=Oy(e),T=[`In Xcode: Product \u203A Scheme \u203A Edit Scheme\u2026 \u203A Run \u203A Options \u203A StoreKit Configuration \u2014 choose ${r} (not listed yet? add it from that menu).`,y?`${t.project_name} \u203A ${t.env_name} is the project's production environment: it never takes a purchase Xcode signs. Make the file for a test environment (-E <env>) to see a purchase reach the server.`:`Then select the file in Xcode, choose Editor \u203A Save Public Certificate, and let ${t.env_name} take the purchases Xcode signs: palbase purchases:apple:xcode-certificate <the saved .cer file>${v}`,`Run the app and buy: Xcode sells the products itself, nothing goes to the App Store. After the catalog changes: palbase purchases:storekit${e.flags.app!==void 0?` --app ${s.slug}`:""}${e.flags.out!==void 0?` --out ${r}`:""}${v}`],A=d.counts,P=[A.subscriptions>0?`${A.subscriptions} subscription${A.subscriptions===1?"":"s"} in ${A.groups} group${A.groups===1?"":"s"}`:"",A.products>0?`${A.products} in-app purchase${A.products===1?"":"s"}`:"",A.non_renewing>0?`${A.non_renewing} non-renewing subscription${A.non_renewing===1?"":"s"}`:""].filter(S=>S!=="").join(", ");e.out.result({file:r,app:s.slug,environment:t.env_slug,revision:a.revision,storefront:d.storefront,currency:d.currency,locale:d.locale,...A,written:f,next:T},()=>{e.out.line(`${f?"Wrote":"Up to date:"} ${r} \u2014 ${P}, priced in ${d.currency} (storefront ${d.storefront}), catalog revision ${a.revision}.`),e.out.line(""),T.forEach((S,C)=>e.out.line(`${C+1}. ${S}`))})}},Ny=/^SubscriptionKey_([A-Z0-9]{10})\.p8$/,qy=/^AuthKey_[A-Z0-9]{10}\.p8$/,xy=8192,My=16384;function cd(e,t){if(!t.connected)e.out.line("No In-App Purchase key is connected: the App Store's purchases are not taken."),e.out.line("Connect one: palbase purchases:apple:connect --key-file SubscriptionKey_<KEYID>.p8");else{e.out.line(`In-App Purchase key ${t.key_id??"?"} (issuer ${t.issuer_id??"?"}) for ${t.bundle_id??"?"}${t.app_apple_id?`, Apple ID ${t.app_apple_id}`:", its Apple ID not known yet"}`),e.out.line(`status: ${t.status??"not checked"}${t.checked_at?` (checked ${J(t.checked_at)})`:""}`);let s=t.notifications;s&&e.out.line(`notifications: ${s.url} \u2014 ${s.bound?`bound ${J(s.bound_at)}`:`not bound${s.error?`: ${s.error}`:""}`}; Apple's TEST notification ${s.verified_at?`arrived ${J(s.verified_at)}`:"not arrived yet"}`),t.connected_at&&e.out.line(`connected ${J(t.connected_at)}${t.connected_by?` by ${t.connected_by}`:""}`),t.action&&e.out.line(`To do: ${t.action}`)}let n=t.xcode_certificate;e.out.line(n?`Xcode's StoreKit Testing purchases: taken, signed with ${n.subject} until ${J(n.not_after)} (sha256 ${n.fingerprint.slice(0,16)}\u2026)`:"Xcode's StoreKit Testing purchases: not taken (palbase purchases:apple:xcode-certificate <file> takes them)")}var Uy={name:"purchases:apple",summary:"the environment's App Store connection: the In-App Purchase key, where the App Store reports, whether Xcode's purchases are taken",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/purchases/apple");e.out.result(n,()=>cd(e,n))}};function Ky(e,t){let n=e.environments.find(s=>s.ref===t.env_ref)?.ios?.bundle;if(n===void 0)throw new m("app_not_in_environment",`${e.slug} is not set in ${t.env_name}: the key is connected for the app's bundle there`,{hint:`palbase apps:env:set ${e.slug} -E ${t.env_slug} --bundle <bundle id>`});return n}var Gy={name:"purchases:apple:connect",summary:"connect an In-App Purchase key for an iOS app: it goes from this computer straight to the environment, never through Studio",usage:"--key-file <SubscriptionKey_KEYID.p8> [--key-id <KEYID>] [--issuer-id <issuer id>] [--app <app>] | --print-link",details:["App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes the key (SubscriptionKey_<KEYID>.p8) and shows the Issuer ID \u2014 the team's, the same as its App Store Connect API keys': left out, the one stores:apple connected is taken.","The environment verifies the app's purchases and reads its subscriptions with it, and has the stores module point the App Store's notifications at the environment.","Studio is asked for a one-time grant alone; the key is posted with it to the environment's own address. --print-link prints a link that takes the key in a browser instead."],flags:{"key-file":{type:"string",value:"SubscriptionKey_KEYID.p8",description:"the .p8 file Apple gave when the key was made"},"key-id":{type:"string",value:"KEYID",description:"the key's id (default: from the file's name)"},"issuer-id":{type:"string",value:"issuer id",description:"the team's issuer id (default: the connected App Store Connect key's)"},app:{type:"string",value:"app",description:"the iOS app (default: the one this checkout syncs, or the project's only one)"},"print-link":{type:"boolean",description:"only print a link that takes the key in a browser (valid 30 minutes, once)"}},scope:"environment",async run(e){let t=await h(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw g("--print-link or --key-file, not both");await b(e,t,"make a link that connects an In-App Purchase key");let y=await c(e,t,"POST","/purchases/apple/grants",void 0,R());Ht(e,t,y.connect_url,"the connect link"),e.out.result({connect_url:y.connect_url,expires_at:y.expires_at},()=>{e.out.line(y.connect_url),e.out.note(`Open it in a browser, pick the app and the .p8 file; it works once, until ${J(y.expires_at)}.`)});return}let n=e.flags["key-file"];if(n===void 0)throw g("--key-file names the In-App Purchase key's .p8 file","or --print-link for a link a browser opens");let s=cy(n),r=e.flags["key-id"];if(r===void 0){if(qy.test(s))throw g(`${s} is an App Store Connect API key (palbase stores:apple:connect takes it): this is the In-App Purchase key, SubscriptionKey_<KEYID>.p8`,"App Store Connect \u2192 Users and Access \u2192 Integrations \u2192 In-App Purchase makes one");if(r=Ny.exec(s)?.[1],r===void 0)throw g(`--key-id is needed: ${s} is not named SubscriptionKey_<KEYID>.p8`)}let a;try{let y=vs(e.io.cwd,n);if((await od(y)).size>xy)throw new m("not_a_key",`${n} is larger than a .p8 key`);a=await Ur(y,"utf8")}catch(y){throw y instanceof m?y:new m("unreadable_file",`${n} could not be read: ${y instanceof Error?y.message:String(y)}`)}if(!a.includes("-----BEGIN PRIVATE KEY-----"))throw new m("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);let i=e.flags["issuer-id"]?.trim();if(i===void 0||i===""){let y=await c(e,t,"GET","/stores/apple");if(i=y.connected?y.issuer_id:void 0,i===void 0)throw g("--issuer-id is the team's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations): no App Store Connect key is connected to read it from")}let o=await Kr(e,t),l=Ky(o,t);await b(e,t,`connect the In-App Purchase key ${r} for ${l}`);let p=await c(e,t,"POST","/purchases/apple/grants",void 0,R()),u=Ht(e,t,p.connect_url,"the connect link"),d={grant:p.grant,issuer_id:i,key_id:r,private_key:a,bundle_id:l},f=await bs(e,`${u}/purchases/apple/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(d)},"connecting the In-App Purchase key");e.out.result(f,()=>{e.out.line(`Connected key ${r} for ${l} to ${x(t)}.`),cd(e,f),e.out.line("Check what else the App Store needs: palbase purchases:doctor")})}},By={name:"purchases:apple:disconnect",summary:"forget the environment's In-App Purchase key: the App Store's purchases and notifications are refused from then on (what was bought stays)",scope:"environment",async run(e){let t=await h(e);await b(e,t,"disconnect the In-App Purchase key"),await c(e,t,"DELETE","/purchases/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the In-App Purchase key from ${x(t)}.`))}};async function Jy(e,t){let n=(await $e(e,t.project_id)).find(s=>s.deleted_at===null&&s.environments.some(r=>r.ref===t.env_ref));return n===void 0?t.env_slug===Pe:(await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`)).environment.production}async function zy(e,t){let n;try{let a=vs(t,e);if((await od(a)).size>My)throw new m("not_a_certificate",`${e} is larger than Xcode's certificate`);n=await Ur(a)}catch(a){throw a instanceof m?a:new m("unreadable_file",`${e} could not be read: ${a instanceof Error?a.message:String(a)}`)}let s;try{s=new dy(n)}catch{throw new m("not_a_certificate",`${e} is not a certificate`,{hint:"the one Xcode saves: select the .storekit file, then Editor \u203A Save Public Certificate"})}let r=n.toString("latin1");return{certificate:r.includes("-----BEGIN CERTIFICATE-----")?r.trim():n.toString("base64"),x509:s}}var Wy={name:"purchases:apple:xcode-certificate",summary:"take the purchases Xcode's StoreKit Testing signs with this certificate, as test purchases \u2014 never on production; --remove stops",usage:"<certificate.cer> | --remove",details:["Xcode saves the certificate: select the .storekit file (palbase purchases:storekit makes it), then Editor \u203A Save Public Certificate.","From then on what the app reports signed with it \u2014 Xcode's own purchases, on a developer's Mac \u2014 is a test purchase of the environment, with or without an In-App Purchase key; nothing of it is asked of Apple. Set again, it replaces the one before.","The production environment takes only what the App Store signed: it is refused there."],args:{min:0,max:1},flags:{remove:{type:"boolean",description:"stop taking Xcode's purchases (those taken stay)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.remove===!0,s=e.args[0];if(n===(s!==void 0))throw g("name the certificate file, or --remove","palbase purchases:apple:xcode-certificate StoreKitTestCertificate.cer");if(n){await b(e,t,"stop taking Xcode's StoreKit Testing purchases"),await c(e,t,"DELETE","/purchases/apple/xcode-certificate",void 0,R()),e.out.result({removed:!0},()=>e.out.line(`${x(t)} no longer takes Xcode's purchases; those it took stay.`));return}let r=await zy(s,e.io.cwd);if(await Jy(e,t))throw new m("production_environment",`${t.project_name} \u203A ${t.env_name} is the project's production environment: it takes only purchases the App Store signed, never Xcode's`,{hint:"set the certificate on a test environment: palbase purchases:apple:xcode-certificate <file> -E <env>"});await b(e,t,`take the purchases Xcode signs with ${r.x509.subject.replaceAll(`
652
+ `,", ")}`);let a=await c(e,t,"PUT","/purchases/apple/xcode-certificate",{certificate:r.certificate},R());e.out.result(a,()=>{e.out.line(`${x(t)} takes the purchases Xcode signs with ${a.subject}, until ${J(a.not_after)} (sha256 ${a.fingerprint.slice(0,16)}\u2026).`),e.out.line("Run the app from Xcode and buy: each purchase is a test purchase of this environment.")})}},Fy=new Set(["sandbox_access","xcode_certificate"]),Vy={name:"purchases:doctor",summary:"whether the environment sells in the App Store now, check by check \u2014 the App Store Connect side and the purchases side \u2014 with the fix of each that fails",usage:"[--app <app>]",details:["Apple is asked now: the keys, the agreements, the app's App Store Connect record, where the App Store reports, the sandbox testers, each product's state, the catalog's last confirmation; and whether Xcode's purchases are taken.","It exits 1 while a check that is not informational fails: run it again until every line is \u2713."],flags:{app:{type:"string",value:"app",description:"the iOS app (default: the environment's one linked app)"}},scope:"environment",async run(e){let t=await h(e),n=e.flags.app!==void 0?await Kr(e,t):void 0,s=n?`?${new URLSearchParams({app:n.id})}`:"",r=d=>d.then(f=>({value:f}),f=>({error:f})),[a,i]=await Promise.all([r(c(e,t,"GET",`/stores/apple/readiness${s}`)),r(c(e,t,"GET","/purchases/apple/readiness"))]),o=(d,f)=>{if(!(f instanceof Y))throw f;return{module:d,id:`${d}_unanswered`,ok:!1,informational:!1,message:`The ${d} module could not answer: ${f.message}`,fix:f.status===503||f.status===502?"palbase purchases:doctor (again, in a moment)":""}},l=[..."value"in a?a.value.checks.map(d=>({module:"stores",id:d.id,ok:d.ok,informational:!1,message:d.message,fix:d.fix})):[o("stores",a.error)],..."value"in i?i.value.checks.map(d=>({module:"purchases",id:d.id,ok:d.ok,informational:Fy.has(d.id),message:d.message,fix:d.fix})):[o("purchases",i.error)]],p=l.filter(d=>!d.ok&&!d.informational),u=p.length===0;return e.out.result({ready:u,checks:l,stores:"value"in a?a.value:null,purchases:"value"in i?i.value:null},()=>{let d=(f,y)=>{e.out.line(y);for(let v of l.filter(T=>T.module===f))e.out.line(` ${v.informational?"i":v.ok?"\u2713":"\u2717"} ${v.message}`),(!v.ok||v.informational)&&v.fix&&e.out.line(` \u2192 ${v.fix}`)};d("stores","App Store Connect (stores)"),d("purchases","In-App Purchases (purchases)"),e.out.line(""),e.out.line(u?`Ready: ${x(t)} sells in the App Store.`:`${p.length} of ${l.filter(f=>!f.informational).length} checks fail: fix each, then run palbase purchases:doctor again.`)}),u?void 0:1}},md=[by,wy,vy,Sy,ky,$y,Py,Ty,Ey,Cy,jy,Ly,Uy,Gy,By,Wy,Vy];import{readdir as Hy,readFile as Xy,stat as Yy}from"node:fs/promises";import{join as Zy,resolve as Qy}from"node:path";var eb=1e3,gd=900*1024,tb="palbase-argon2id";function nb(e,t){let n=e.trim();if(n.startsWith("{"))try{let s=JSON.parse(n);if(Array.isArray(s.users)){if(s.hash?.pepper!==void 0)throw g(`${t} holds a pepper: it goes on stdin, not in a file`);let r=s.hash&&typeof s.hash.algorithm=="string"?{algorithm:s.hash.algorithm,...typeof s.hash.recovery_code_prefix=="string"?{recovery_code_prefix:s.hash.recovery_code_prefix}:{}}:void 0;return{users:s.users,hash:r}}}catch(s){if(s instanceof Error&&s.name==="CliError")throw s}return{users:rb(e,t)}}function sb(e){let t=[],n=[],s=0;for(let r of e){let a=Buffer.byteLength(JSON.stringify(r))+1;if(a>gd)throw new m("user_too_large",`the user ${String(r.id??"")} is larger than one call takes (${a} bytes)`);(n.length===eb||s+a>gd)&&(t.push(n),n=[],s=0),n.push(r),s+=a}return n.length>0&&t.push(n),t}function rb(e,t){let n=e.trim();if(n==="")return[];if(n.startsWith("["))try{let s=JSON.parse(n);if(Array.isArray(s)&&s.every(r=>typeof r=="object"&&r!==null&&!Array.isArray(r)))return s}catch{}return n.split(`
653
+ `).flatMap((s,r)=>{if(s.trim()==="")return[];try{let a=JSON.parse(s);if(typeof a!="object"||a===null||Array.isArray(a))throw new Error("not an object");return[a]}catch(a){throw g(`${t}:${r+1} is not a user (one JSON object a line): ${a instanceof Error?a.message:String(a)}`)}})}var hd={name:"auth:import",summary:"bring users over with what they sign in with: ids, addresses, password hashes, providers, second factors, passkeys, open sessions",usage:"<file|folder> [--pepper-stdin]",details:["A folder holds import bodies, one a file (users-0001.json\u2026, each without its pepper); a file holds one such body, JSON lines (a user each) or one JSON array.","Password and recovery-code hashes keep the source's pepper: give it on stdin with --pepper-stdin \u2014 never on the command line.","Users go 1000 a call. One refused does not stop the others; a user whose id or address the project has is refused, never overwritten."],args:{min:1,max:1},flags:{"pepper-stdin":{type:"boolean",description:"read the source's pepper from stdin"}},scope:"environment",async run(e){let t=await h(e),n=e.args[0],s=Qy(e.io.cwd,n),r;try{r=(await Yy(s)).isDirectory()?(await Hy(s)).filter(d=>/^users-\d+\.json$/.test(d)).sort().map(d=>Zy(s,d)):[s]}catch(d){throw g(`${n} could not be read: ${d instanceof Error?d.message:String(d)}`)}let a=[],i;for(let d of r){let f=nb(await Xy(d,"utf8"),d);if(a.push(...f.users),f.hash){if(i&&JSON.stringify(i)!==JSON.stringify(f.hash))throw g(`${d} names another hash than the files before it`);i=f.hash}}if(a.length===0)throw g(`${n} holds no user`);let o=a.some(d=>d.password_hash!==void 0||Array.isArray(d.recovery_code_hashes)&&d.recovery_code_hashes.length>0),l;if(o){if(e.flags["pepper-stdin"]!==!0)throw g("these users carry password hashes: give the source's pepper with --pepper-stdin",`palbase auth:import ${n} --pepper-stdin < pepper.txt`);l={...i??{algorithm:tb},pepper:await fe(e,"the source's pepper")}}await b(e,t,`bring ${a.length} user${a.length===1?"":"s"} into ${x(t)}'s auth`);let p={imported:0,errors:[],sessions_imported:0,sessions_skipped:0},u=0;for(let d of sb(a)){let f=await c(e,t,"POST","/admin/users/import",{...l?{hash:l}:{},users:d});p.imported+=f.imported,p.sessions_imported+=f.sessions_imported,p.sessions_skipped+=f.sessions_skipped;for(let y of f.errors??[])p.errors.push({...y,index:y.index+u});u+=d.length,u<a.length&&e.out.note(`${u} of ${a.length}\u2026`)}if(e.out.result(p,()=>{e.out.line(`Imported ${p.imported} of ${a.length} users (${p.sessions_imported} open sessions; ${p.sessions_skipped} ended ones left behind).`);for(let d of p.errors.slice(0,50))e.out.line(` ${d.id??`#${d.index+1}`}: ${d.error} \u2014 ${d.message}`);p.errors.length>50&&e.out.line(` \u2026and ${p.errors.length-50} more (--json lists every one)`)}),p.errors.length>0)throw new m("import_incomplete",`${p.errors.length} user${p.errors.length===1?" was":"s were"} not imported`)}};import{writeFile as fd}from"node:fs/promises";import{resolve as yd}from"node:path";var ab=/^usr_[A-Za-z0-9-]{1,64}$/;function Xt(e){if(!ab.test(e))throw g(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function $n(e,t,n,s){if(e===void 0)return;let r=Number(e);if(!Number.isInteger(r)||r<n||r>s)throw g(`--${t} is a whole number from ${n} to ${s}`);return r}function bd(e,t){if(e===void 0)return;let n=/^(\d+)([smhd])$/.exec(String(e));if(!n)throw g(`--${t} is a span: 30m, 2h, 7d`);return Number(n[1])*{s:1,m:60,h:3600,d:86400}[n[2]]}var Xe=e=>e?e.slice(0,16).replace("T"," "):"";function Gr(e){let t=[e.email_verified===!1?"unverified":"",e.banned?`banned${e.ban_reason?` (${j(e.ban_reason)})`:""}`:"",e.is_test?"test":"",e.has_mfa?"mfa":""].filter(Boolean);return`${j(e.id)} ${j(e.email??e.phone??"")}${t.length?` [${t.join(", ")}]`:""}`}var ib={name:"auth:users:list",summary:"the environment's users: id, email, verified, last sign-in; --banned for the banned alone",usage:"[--banned] [--limit <n>]",flags:{banned:{type:"boolean",description:"only the banned"},limit:{type:"string",value:"n",description:"how many at most (default 100)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,1e4)??100,s=[],r;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{cursor:r}:{},...e.flags.banned===!0?{banned:"true"}:{}}),i=await c(e,t,"GET",`/admin/users?${a}`);s.push(...i.users),r=i.next_cursor||void 0}while(r&&s.length<n);e.out.result({users:s},()=>{s.length===0?e.out.line("No user yet."):e.out.table(s,[{header:"id",value:a=>a.id},{header:"email",value:a=>j(a.email??a.phone??"")},{header:"verified",value:a=>a.email_verified?"yes":"no"},{header:"last sign-in",value:a=>Xe(a.last_login_at)},{header:"notes",value:a=>[a.banned?"banned":"",a.is_test?"test":"",a.has_mfa?"mfa":""].filter(Boolean).join(", ")}])})}},ob={name:"auth:users:get",summary:"one user: their email and phone, how they sign in, whether verified or banned, their roles",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),[s,r]=await Promise.all([c(e,t,"GET",`/admin/users/${_(n)}`),c(e,t,"GET",`/admin/users/${_(n)}/roles`).then(a=>Array.isArray(a)?a:a.roles??[])]);e.out.result({...s,roles:r},()=>{e.out.line(Gr(s)),e.out.line(` signs in with ${s.providers?.length?j(s.providers.join(", ")):"a password"}; made ${j(Xe(s.created_at))}, last ${j(Xe(s.last_login_at))||"never"}`),e.out.line(` roles: ${r.length?r.map(j).join(", "):"none"}`)})}},lb={name:"auth:users:create",summary:"make a user with an email and a password (read from stdin, never the command line)",usage:"<email> [--verified]",args:{min:1,max:1},flags:{verified:{type:"boolean",description:"their email is taken as verified"}},scope:"environment",async run(e){let t=await h(e),n=await fe(e,"the password");await b(e,t,`make the user ${e.args[0]}`);let s=await c(e,t,"POST","/admin/users",{email:e.args[0],password:n,...e.flags.verified===!0?{email_verified:!0}:{}},R());e.out.result(s,()=>e.out.line(`Made ${Gr(s)}.`))}};function An(e,t,n,s,r,a={}){return{name:e,summary:t,usage:"<user-id>"+(a&&"reason"in a?" [--reason <text>]":""),args:{min:1,max:1},flags:a,scope:"environment",async run(i){let o=await h(i),l=Xt(i.args[0]);await b(i,o,n(l)),await s(i,o,l),i.out.result({user:l,done:e},()=>i.out.line(r(l)))}}}var pb=An("auth:users:delete","delete a user: their account, sessions and sign-ins go; backends hear user.deleted",e=>`delete the user ${e}`,(e,t,n)=>c(e,t,"DELETE",`/admin/users/${_(n)}`),e=>`Deleted ${e}.`),db=An("auth:users:ban","ban a user: their sessions end and they cannot sign in until unbanned",e=>`ban the user ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/ban`,e.flags.reason===void 0?{}:{reason:e.flags.reason}),e=>`Banned ${e}.`,{reason:{type:"string",value:"text",description:"why (kept with the ban)"}}),ub=An("auth:users:unban","let a banned user sign in again",e=>`unban the user ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/unban`,{}),e=>`${e} may sign in again.`),cb=An("auth:users:sign-out","end every session of a user's: each device signs in again",e=>`end every session of ${e}`,(e,t,n)=>c(e,t,"POST",`/admin/users/${_(n)}/sessions/revoke-all`,{}),e=>`Every session of ${e} is ended.`),mb={name:"auth:test-users:list",summary:"the environment's test users (made with its secret key; their tokens say test: true)",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/admin/test-users"),s=Array.isArray(n)?n:n.users;e.out.result({users:s},()=>{s.length===0&&e.out.line("No test user yet: palbase auth:test-users:create makes some.");for(let r of s)e.out.line(Gr(r))})}},gb={name:"auth:test-users:create",summary:"make test users for a suite: their passwords, and with --token an access token each, shown this once",usage:"[--count <n>] [--token]",details:["A test user signs in as any user does; its tokens say test: true, so a backend can tell them apart. --token gives each an access token at once, for a test that calls the backend as that user."],flags:{count:{type:"string",value:"n",description:"how many (default 1, at most 100)"},token:{type:"boolean",description:"an access token for each, too"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.count,"count",1,100)??1;await b(e,t,`make ${n} test user(s)`);let s=await c(e,t,"POST","/admin/test-users",{count:n,with_tokens:e.flags.token===!0},R());e.out.result(s,()=>{for(let r of s.users)e.out.line(`${j(r.user_id)} ${j(r.email??"")} password ${j(r.password??"")}`),r.access_token&&e.out.line(` access token: ${j(r.access_token)}`);e.out.line("Shown this once.")})}},hb=An("auth:test-users:delete","delete a test user",e=>`delete the test user ${e}`,(e,t,n)=>c(e,t,"DELETE",`/admin/test-users/${_(n)}`),e=>`Deleted the test user ${e}.`),fb={name:"flags:users:get",summary:"every flag as one user gets it now \u2014 their own values, the targeting, the defaults \u2014 as their client reads them",usage:"<user-id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=await c(e,t,"GET",`/flags/users/${_(n)}`);e.out.result({user:n,values:s.values},()=>{let r=Object.keys(s.values??{});r.length===0&&e.out.line("The environment has no flag yet.");for(let a of r.sort())e.out.line(`${j(a)} = ${JSON.stringify(s.values[a])}`)})}},yb={name:"flags:users:set",summary:"give a user a flag's value of their own, whatever its default and targeting say",usage:"<user-id> <key> <value>",args:{min:3,max:3},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=e.args[1],r=tt(e.args[2]);await b(e,t,`give ${n} the value ${JSON.stringify(r)} of ${s}`),await c(e,t,"PUT",`/flags/users/${_(n)}/${_(s)}`,{value:r}),e.out.result({user:n,key:s,value:r},()=>e.out.line(`${n} gets ${s} = ${JSON.stringify(r)}.`))}},bb={name:"flags:users:unset",summary:"take a user's own value of a flag away, or every one with no key: the flag decides for them again",usage:"<user-id> [key]",args:{min:1,max:2},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=e.args[1];await b(e,t,s?`take ${n}'s own value of ${s} away`:`take every value of ${n}'s own away`),await c(e,t,"DELETE",s?`/flags/users/${_(n)}/${_(s)}`:`/flags/users/${_(n)}`),e.out.result({user:n,key:s??null},()=>e.out.line(s?`${n} gets ${s} as the flag says.`:`${n} gets every flag as it says.`))}};function wb(e){let t=e===void 0?[]:Array.isArray(e)?e:[String(e)];if(t.length===0)return;let n={};for(let s of t){let r=s.indexOf("=");if(r<=0)throw g(`--var is name=value, not ${JSON.stringify(s)}`);n[s.slice(0,r)]=s.slice(r+1)}return n}var vb={name:"notify:send",summary:"send an email or a text through the environment's providers: written here, or a template with its variables",usage:"<email|sms> <to> [--template <slug> --var name=value\u2026] [--subject <s> --text <t> --html <h>] [--body <text>]",details:["A message is sent as a backend sends it (POST /notify/email, /notify/sms): its delivery is in palbase notify:messages."],args:{min:2,max:2},flags:{template:{type:"string",value:"slug",description:"a template of the environment (notify:templates:list)"},var:{type:"string",value:"name=value",multiple:!0,description:"a template's variable (again for each)"},locale:{type:"string",value:"locale",description:"the template's locale"},subject:{type:"string",value:"text",description:"an email's subject"},text:{type:"string",value:"text",description:"an email's text"},html:{type:"string",value:"html",description:"an email's HTML"},body:{type:"string",value:"text",description:"a text's words"}},scope:"environment",async run(e){let[t,n]=e.args;if(t!=="email"&&t!=="sms")throw g("notify:send email <to> or notify:send sms <to>");let s=await h(e),r=e.flags,a=wb(r.var),i={to:n,...r.template?{template:r.template}:{},...a?{variables:a}:{},...r.locale?{locale:r.locale}:{}};if(t==="email"){if(!r.template&&(r.subject===void 0||r.text===void 0&&r.html===void 0))throw g("an email is a --template, or a --subject with --text or --html");Object.assign(i,r.subject!==void 0?{subject:r.subject}:{},r.text!==void 0?{text:r.text}:{},r.html!==void 0?{html:r.html}:{})}else{if(!r.template&&r.body===void 0)throw g("a text is a --template or a --body");r.body!==void 0&&(i.body=r.body)}await b(e,s,`send ${t==="email"?"an email":"a text"} to ${n}`);let o=await c(e,s,"POST",`/notify/${t}`,i,R());e.out.result(o,()=>e.out.line(o.skipped?`Not sent to ${j(n)}: the user does not take this category (${j(o.message_id??"")}).`:`Queued to ${j(n)} (${j(o.message_id??"")}): palbase notify:messages says what became of it.`))}},_b={name:"notify:messages",summary:"the messages the environment sent, newest first, and what became of each",usage:"[--limit <n>]",flags:{limit:{type:"string",value:"n",description:"how many (default 20)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,100)??20,s=await c(e,t,"GET",`/notify/messages?limit=${n}`);e.out.result(s,()=>{s.messages.length===0?e.out.line("Nothing sent yet."):e.out.table(s.messages,[{header:"sent",value:r=>Xe(r.created_at)},{header:"channel",value:r=>r.channel},{header:"to",value:r=>j(r.recipient_hint??"")},{header:"template",value:r=>j(r.template??"")},{header:"status",value:r=>`${r.status}${r.error?` (${j(r.error)})`:""}`},{header:"id",value:r=>r.id}])})}},Sb={name:"notify:deliverability",summary:"whether each channel can send now, through which providers, and what to do when it cannot",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/notify/deliverability");e.out.result(n,()=>e.out.table(n.channels,[{header:"channel",value:s=>s.channel},{header:"sends",value:s=>s.ok?"yes":"no"},{header:"via",value:s=>j(s.via.join(" \u2192 "))||"-"},{header:"reports",value:s=>s.reports?"yes":"no"},{header:"why / what to do",value:s=>j([s.because,s.remedy].filter(Boolean).join(" \u2014 "))}]))}},kb={name:"auth:audit",summary:"who did what in the environment's auth, newest first: sign-ins, changes to users and settings, admin actions",usage:"[--event <type>] [--limit <n>]",flags:{event:{type:"string",value:"type",description:"only this kind of event (user.login, admin.user.update\u2026)"},limit:{type:"string",value:"n",description:"how many at most (default 50)"}},scope:"environment",async run(e){let t=await h(e),n=$n(e.flags.limit,"limit",1,1e4)??50,s=[],r;do{let a=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...r?{cursor:r}:{},...typeof e.flags.event=="string"?{event_type:e.flags.event}:{}}),i=await c(e,t,"GET",`/admin/audit?${a}`);s.push(...i.events),r=i.next_cursor||void 0}while(r&&s.length<n);e.out.result({events:s},()=>{s.length===0?e.out.line("Nothing recorded yet."):e.out.table(s,[{header:"when",value:a=>Xe(a.created_at)},{header:"event",value:a=>j(a.event_type)},{header:"result",value:a=>j(a.result??"")},{header:"by",value:a=>j(a.actor?.email??a.actor?.user_id??"")},{header:"from",value:a=>j(a.actor?.ip??"")},{header:"on",value:a=>j([a.target_type,a.target_id].filter(Boolean).join(" "))}])})}},$b={name:"auth:audit:verify",summary:"whether the audit trail is whole: each event chained to the one before, none changed or taken out",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/admin/audit/verify");if(!n.valid){let s=n.broken_at?` at event ${j(n.broken_at.event_id??"?")} (number ${(n.broken_at.index??0)+1} of ${n.total}, oldest first)`:"";throw new m("audit_trail_broken",`the audit trail is broken${s}: an event there was changed or taken out`)}e.out.result(n,()=>e.out.line(`The audit trail is whole: ${n.total} events, each chained to the one before.`))}},Ab={name:"auth:audit:export",summary:"the audit trail as a file: JSON lines or CSV, a span of it, one kind of event",usage:"[--format ndjson|csv] [--from <time>] [--to <time>] [--event <type>] [--out <file>]",flags:{format:{type:"string",value:"ndjson|csv",description:"JSON lines (default) or CSV"},from:{type:"string",value:"time",description:"from this time on (RFC 3339: 2026-10-01T00:00:00Z)"},to:{type:"string",value:"time",description:"up to this time"},event:{type:"string",value:"type",description:"only this kind of event"},out:{type:"string",value:"file",description:"write it here instead of to standard output"}},scope:"environment",async run(e){let t=e.flags.format??"ndjson";if(t!=="ndjson"&&t!=="csv")throw g("--format is ndjson or csv");for(let i of["from","to"]){let o=e.flags[i];if(o!==void 0&&Number.isNaN(Date.parse(String(o))))throw g(`--${i} is a time: 2026-10-01T00:00:00Z`)}let n=await h(e),s=new URLSearchParams({format:String(t)});for(let[i,o]of[["from","from"],["to","to"],["event","event_type"]])typeof e.flags[i]=="string"&&s.set(o,e.flags[i]);let r=await c(e,n,"GET",`/admin/audit/export?${s}`),a=typeof r=="string"?r:t==="ndjson"&&Array.isArray(r)?r.map(i=>JSON.stringify(i)).join(`
654
+ `):JSON.stringify(r);typeof e.flags.out=="string"?(await fd(yd(e.io.cwd,e.flags.out),a.endsWith(`
655
655
  `)||a===""?a:`${a}
656
656
  `,{mode:384}),e.out.result({file:e.flags.out,bytes:Buffer.byteLength(a)},()=>e.out.line(`Wrote the audit trail to ${e.flags.out} (${Buffer.byteLength(a)} bytes).`))):e.io.stdout(a.endsWith(`
657
657
  `)||a===""?a:`${a}
658
658
  `)}},Pb={name:"auth:signing-keys",summary:"the keys the environment's tokens are signed with: the active one, those still verifying, the retired",scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET","/admin/signing-keys");e.out.result(n,()=>{e.out.table(n.keys,[{header:"kid",value:s=>j(s.kid)+(s.kid===n.active_kid?" (signing)":"")},{header:"status",value:s=>j(s.status)},{header:"algorithm",value:s=>j(s.algorithm)},{header:"created",value:s=>Xe(s.created_at)},{header:"retired",value:s=>Xe(s.retired_at)}]),n.next_rotation&&e.out.line(`The next key starts signing ${Xe(n.next_rotation)}.`)})}},Rb={name:"auth:signing-keys:rotate",summary:"sign with a new key from now on; tokens the old one signed go on verifying until they end",scope:"environment",async run(e){let t=await h(e);await b(e,t,"rotate the token signing key");let n=await c(e,t,"POST","/admin/signing-keys/rotate");e.out.result(n,()=>e.out.line(`Tokens are signed with ${j(n.new_kid)} from now on; the old key goes on verifying the tokens it signed.`))}},Tb={name:"auth:users:export",summary:"everything the environment holds about one user, as the law lets them ask for it (GDPR art. 15/20): JSON",usage:"<user-id> [--out <file>]",args:{min:1,max:1},flags:{out:{type:"string",value:"file",description:"write it here instead of to standard output"}},scope:"environment",async run(e){let t=await h(e),n=Xt(e.args[0]),s=await c(e,t,"GET",`/admin/users/${_(n)}/export`),r=`${JSON.stringify(s,null,2)}
659
- `;typeof e.flags.out=="string"?(await fp(yp(e.io.cwd,e.flags.out),r,{mode:384}),e.out.result({file:e.flags.out},()=>e.out.line(`Wrote ${n}'s data to ${e.flags.out}.`))):e.io.stdout(r)}},Eb={name:"realtime:broadcast",summary:"send an event to a channel's subscribers, as the server does: its payload JSON (or text)",usage:"<topic> <event> [payload]",args:{min:2,max:3},scope:"environment",async run(e){let t=await h(e),[n,s,r]=e.args,a=r===void 0?null:tt(r);await b(e,t,`broadcast ${s} on ${n}`);let i=await c(e,t,"POST","/realtime/broadcast",{messages:[{topic:n,event:s,payload:a}]});e.out.result(i??{sent:1},()=>e.out.line(`Broadcast ${j(s)} on ${j(n)}.`))}},Cb={name:"analytics:events",summary:"the events analytics recorded, newest first: by name, by user, over a window",usage:"[--name <event>] [--user <distinct-id>] [--window <24h>] [--limit <n>]",flags:{name:{type:"string",value:"event",description:"only this event's"},user:{type:"string",value:"id",description:"only this user's (their distinct id)"},window:{type:"string",value:"span",description:"how far back: 30m, 24h, 7d (default 24h)"},limit:{type:"string",value:"n",description:"how many (default 50)"}},scope:"environment",async run(e){let t=await h(e),n=new URLSearchParams({window_seconds:String(bp(e.flags.window,"window")??86400),limit:String($n(e.flags.limit,"limit",1,1e3)??50),...e.flags.name?{name:e.flags.name}:{},...e.flags.user?{distinct_id:e.flags.user}:{}}),s=await c(e,t,"GET",`/analytics/events?${n}`);e.out.result(s,()=>{s.events.length===0&&e.out.line("No event in the window.");for(let r of s.events)e.out.line(`${j(Xe(r.timestamp??r.ts))} ${j(r.name)} ${j(r.distinct_id??"")}${r.properties&&Object.keys(r.properties).length?` ${j(JSON.stringify(r.properties)).slice(0,200)}`:""}`)})}},jb={name:"analytics:summary",summary:"what analytics counted over a window: events, users, sessions, the top events",usage:"[--window <24h>]",flags:{window:{type:"string",value:"span",description:"how far back: 30m, 24h, 7d (default 24h)"}},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",`/analytics/summary?window_seconds=${bp(e.flags.window,"window")??86400}`);e.out.result(n,()=>{for(let[s,r]of Object.entries(n))e.out.line(`${j(s)}: ${typeof r=="object"?JSON.stringify(r):j(String(r))}`)})}},wp=[ib,ob,lb,db,pb,ub,cb,Tb,kb,$b,Ab,Pb,Rb,mb,gb,hb,fb,yb,bb,vb,_b,Sb,Eb,Cb,jb];import{mkdtemp as Ob,rm as Db}from"node:fs/promises";import{tmpdir as Ib}from"node:os";import{extname as Lb,join as vp}from"node:path";async function Pn(e,t,n){if(n!==void 0)return n;let{body:s}=await Ae(e.studio,t.env_ref,"GET","/databases?limit=100");if(s.databases.length===1)return s.databases[0].name;throw s.databases.length===0?new m("no_database","the environment has no database",{hint:"palbase databases:create opens one"}):g("which database? name it",`the environment's databases: ${s.databases.map(r=>r.name).join(", ")}`)}async function Ye(e,t,n,s,r){let{body:a}=await Ae(e.studio,t.env_ref,"GET",`/databases/${encodeURIComponent(n)}/credentials`),i=(await e.state.databasePasswords(t.env_ref,n))[s];if(!i)throw new m("no_password",`this computer holds no ${s} password of ${n}: the platform showed it once, where the database was opened`,{hint:`palbase databases:password:reset ${n} --role ${s} makes a new one and keeps it here${s==="app"?" (the machines given it roll out with it)":""}`});let o=s==="owner"?a.owner:a.app,l=new URL(r?o.pooled_url:o.url);return l.password="",{url:l.toString(),password:i,role:s}}function Nb(e){let t=new URL(e.url);return t.password=encodeURIComponent(e.password),t.toString()}var _p={type:"string",value:"role",description:"owner (migrations, dumps) or app (what the machines use); default owner"};function Sp(e){let t=e.flags.role??"owner";if(t!=="owner"&&t!=="app")throw g("--role is owner or app");return t}var kp={PGSSLROOTCERT:"system"};async function Yt(e,t,n,s,r=!1){let a=await e.io.run(t,n,{env:{PGPASSWORD:s.password,...kp,PGAPPNAME:"palbase-cli"},inherit:r});if(a.code===127)throw new m("missing_tool",`${t} is not installed here`,{hint:"install the Postgres 18 client tools (brew install postgresql@18, apt install postgresql-client-18)"});return a}var $p={name:"db:url",summary:"a database's connection address, for any tool (Prisma, Drizzle, pg_dump, a GUI)",usage:"[name] [--role owner|app] [--pooled] [--with-password]",details:["Without --with-password the address holds no password: give the tool PGPASSWORD, or pass --with-password and keep the line out of your history.","The address asks for a checked certificate (sslmode=verify-full). Node drivers check it against the system's roots by themselves; psql, pg_dump and other libpq tools do only with PGSSLROOTCERT=system (Postgres 16+). The address does not say so itself: a driver that sends what it does not know to the server (postgres.js) would be refused."],args:{min:0,max:1},flags:{role:_p,pooled:{type:"boolean",description:"the pooled port (PgBouncer, transaction mode): for an app's many short connections"},"with-password":{type:"boolean",description:"put the password in the address"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,Sp(e),e.flags.pooled===!0),r=e.flags["with-password"]===!0?Nb(s):s.url;e.out.result({database:n,role:s.role,url:r,libpq_env:kp},()=>{e.io.stdout(`${r}
660
- `),e.out.note("psql, pg_dump and other libpq tools: PGSSLROOTCERT=system (Postgres 16+), or they cannot check the certificate")})}},Ap={name:"db:shell",summary:"psql on a database (it wakes if asleep); -c runs one statement and ends",usage:"[name] [--role owner|app] [--command <sql>]",args:{min:0,max:1},flags:{role:_p,command:{type:"string",short:"c",value:"sql",description:"run this and end"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,Sp(e),!1),r=e.flags.command;if(r!==void 0){let i=await Yt(e,"psql",[s.url,"-v","ON_ERROR_STOP=1","-X","-c",r],s);if(i.stdout&&e.io.stdout(i.stdout),i.code!==0)throw new m("psql_failed",i.stderr.trim()||`psql ended with ${i.code}`);return}if(!e.interactive)throw g("db:shell with no person at the terminal needs --command <sql>");return e.out.note(`${M(t)} \u203A ${n} as ${s.role}`),(await Yt(e,"psql",[s.url],s,!0)).code}},Pp={name:"db:dump",summary:"a copy of a database (pg_dump's custom format), as its owner",usage:"[name] [--file <path>] [--schema-only]",args:{min:0,max:1},flags:{file:{type:"string",value:"path",description:"where to write it (default <database>-<time>.dump)"},"schema-only":{type:"boolean",description:"its structure, not its data"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,"owner",!1),r=e.flags.file??`${n}-${new Date(e.io.now()).toISOString().replaceAll(":","-").slice(0,19)}.dump`,a=await Yt(e,"pg_dump",["--format=custom","--no-owner","--no-acl",...e.flags["schema-only"]===!0?["--schema-only"]:[],"--file",r,s.url],s);if(a.code!==0)throw new m("pg_dump_failed",a.stderr.trim()||`pg_dump ended with ${a.code}`);e.out.result({database:n,file:r},()=>e.out.line(`Wrote ${r}.`))}},Rp={name:"db:restore",summary:"load a dump into a database, as its owner: a custom-format dump (pg_restore) or a .sql file (psql)",usage:"<file> [--db <name>] [--clean]",details:["A database Palbase did not make is taken over by its first release: its tables stay as they are (palbase db:pull writes their schema files)."],args:{min:1,max:1},flags:{db:{type:"string",value:"name",description:"the database (default: the environment's only one)"},clean:{type:"boolean",description:"drop what the dump makes before making it again"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.flags.db),s=e.args[0];await b(e,t,`load ${s} into the database ${n}`);let r=await Ye(e,t,n,"owner",!1);await Tp(e,r,s,e.flags.clean===!0),e.out.result({database:n,loaded:s},()=>e.out.line(`Loaded ${s} into ${n}.`))}};async function Tp(e,t,n,s){let r=Lb(n).toLowerCase()===".sql",a=r?await Yt(e,"psql",[t.url,"-v","ON_ERROR_STOP=1","-X","--single-transaction","-f",n],t):await Yt(e,"pg_restore",["--no-owner","--no-acl","--exit-on-error","--single-transaction",...s?["--clean","--if-exists"]:[],"--dbname",t.url,n],t);if(a.code!==0)throw new m("restore_failed",a.stderr.trim()||`${r?"psql":"pg_restore"} ended with ${a.code}`)}var Ep={name:"db:copy",summary:"copy a database's data from one environment of the project to another",usage:"--from <env> --to <env> [--db <name>] [--to-db <name>]",details:["The target is overwritten with the source: what it held of the same tables goes (pg_restore --clean)."],flags:{from:{type:"string",value:"env",description:"the environment copied from (slug)"},to:{type:"string",value:"env",description:"the environment copied into (slug)"},db:{type:"string",value:"name",description:"the source's database (default: its only one)"},"to-db":{type:"string",value:"name",description:"the target's database (default: the same name)"}},scope:"environment",async run(e){let t=e.flags.from,n=e.flags.to;if(!t||!n)throw g("db:copy needs --from <env> and --to <env>");if(t===n)throw g("--from and --to are two environments");let s=await Fe(e),r=s.find(y=>y.slug===t),a=s.find(y=>y.slug===n);if(!r||!a)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(r?n:t)}`);let i=ue(r),o=ue(a),l=await Pn(e,i,e.flags.db),d=e.flags["to-db"]??l;await b(e,o,`overwrite the database ${d} with ${i.env_slug}'s ${l}`);let u=await Ye(e,i,l,"owner",!1),p=await Ye(e,o,d,"owner",!1),f=await Ob(vp(Ib(),"palbase-copy-"));try{let y=vp(f,"copy.dump"),v=await Yt(e,"pg_dump",["--format=custom","--no-owner","--no-acl","--file",y,u.url],u);if(v.code!==0)throw new m("pg_dump_failed",v.stderr.trim()||`pg_dump ended with ${v.code}`);await Tp(e,p,y,!0)}finally{await Db(f,{recursive:!0,force:!0})}e.out.result({from:{env:i.env_slug,database:l},to:{env:o.env_slug,database:d}},()=>e.out.line(`Copied ${i.env_slug}/${l} into ${o.env_slug}/${d}.`))}};import{lstat as qb,mkdir as Mb,readFile as Ub,rename as xb,rm as Kb,writeFile as Gb}from"node:fs/promises";import{dirname as Cp,resolve as Bb}from"node:path";var jp="// palbase:stack:begin",Br="// palbase:stack:end",Jb=/^[A-Za-z_$][A-Za-z0-9_$]*$/,_s=e=>Jb.test(e)?e:JSON.stringify(e),St=e=>[...new Set(e)].sort();function Ss(e,t){return t.length===0?[` interface ${e} {}`]:[` interface ${e} {`,...t.map(n=>` ${n}`)," }"]}function zb(e){let t=[...e.buckets].sort((n,s)=>n.name<s.name?-1:n.name>s.name?1:0);return[jp,'declare module "palbase/backend/stack" {',...Ss("Secrets",St(e.secrets).map(n=>`${_s(n)}: true;`)),"",...Ss("Flags",St(e.flags).map(n=>`${_s(n)}: true;`)),"",...Ss("Buckets",t.map(n=>{let s=St(n.variants);return`${_s(n.name)}: { variants: ${s.length===0?"never":s.map(r=>JSON.stringify(r)).join(" | ")} };`})),"",...Ss("Roles",St(e.roles).map(n=>`${_s(n)}: true;`)),"}",Br].join(`
659
+ `;typeof e.flags.out=="string"?(await fd(yd(e.io.cwd,e.flags.out),r,{mode:384}),e.out.result({file:e.flags.out},()=>e.out.line(`Wrote ${n}'s data to ${e.flags.out}.`))):e.io.stdout(r)}},Eb={name:"realtime:broadcast",summary:"send an event to a channel's subscribers, as the server does: its payload JSON (or text)",usage:"<topic> <event> [payload]",args:{min:2,max:3},scope:"environment",async run(e){let t=await h(e),[n,s,r]=e.args,a=r===void 0?null:tt(r);await b(e,t,`broadcast ${s} on ${n}`);let i=await c(e,t,"POST","/realtime/broadcast",{messages:[{topic:n,event:s,payload:a}]});e.out.result(i??{sent:1},()=>e.out.line(`Broadcast ${j(s)} on ${j(n)}.`))}},Cb={name:"analytics:events",summary:"the events analytics recorded, newest first: by name, by user, over a window",usage:"[--name <event>] [--user <distinct-id>] [--window <24h>] [--limit <n>]",flags:{name:{type:"string",value:"event",description:"only this event's"},user:{type:"string",value:"id",description:"only this user's (their distinct id)"},window:{type:"string",value:"span",description:"how far back: 30m, 24h, 7d (default 24h)"},limit:{type:"string",value:"n",description:"how many (default 50)"}},scope:"environment",async run(e){let t=await h(e),n=new URLSearchParams({window_seconds:String(bd(e.flags.window,"window")??86400),limit:String($n(e.flags.limit,"limit",1,1e3)??50),...e.flags.name?{name:e.flags.name}:{},...e.flags.user?{distinct_id:e.flags.user}:{}}),s=await c(e,t,"GET",`/analytics/events?${n}`);e.out.result(s,()=>{s.events.length===0&&e.out.line("No event in the window.");for(let r of s.events)e.out.line(`${j(Xe(r.timestamp??r.ts))} ${j(r.name)} ${j(r.distinct_id??"")}${r.properties&&Object.keys(r.properties).length?` ${j(JSON.stringify(r.properties)).slice(0,200)}`:""}`)})}},jb={name:"analytics:summary",summary:"what analytics counted over a window: events, users, sessions, the top events",usage:"[--window <24h>]",flags:{window:{type:"string",value:"span",description:"how far back: 30m, 24h, 7d (default 24h)"}},scope:"environment",async run(e){let t=await h(e),n=await c(e,t,"GET",`/analytics/summary?window_seconds=${bd(e.flags.window,"window")??86400}`);e.out.result(n,()=>{for(let[s,r]of Object.entries(n))e.out.line(`${j(s)}: ${typeof r=="object"?JSON.stringify(r):j(String(r))}`)})}},wd=[ib,ob,lb,pb,db,ub,cb,Tb,kb,$b,Ab,Pb,Rb,mb,gb,hb,fb,yb,bb,vb,_b,Sb,Eb,Cb,jb];import{mkdtemp as Ob,rm as Db}from"node:fs/promises";import{tmpdir as Ib}from"node:os";import{extname as Lb,join as vd}from"node:path";async function Pn(e,t,n){if(n!==void 0)return n;let{body:s}=await Ae(e.studio,t.env_ref,"GET","/databases?limit=100");if(s.databases.length===1)return s.databases[0].name;throw s.databases.length===0?new m("no_database","the environment has no database",{hint:"palbase databases:create opens one"}):g("which database? name it",`the environment's databases: ${s.databases.map(r=>r.name).join(", ")}`)}async function Ye(e,t,n,s,r){let{body:a}=await Ae(e.studio,t.env_ref,"GET",`/databases/${encodeURIComponent(n)}/credentials`),i=(await e.state.databasePasswords(t.env_ref,n))[s];if(!i)throw new m("no_password",`this computer holds no ${s} password of ${n}: the platform showed it once, where the database was opened`,{hint:`palbase databases:password:reset ${n} --role ${s} makes a new one and keeps it here${s==="app"?" (the machines given it roll out with it)":""}`});let o=s==="owner"?a.owner:a.app,l=new URL(r?o.pooled_url:o.url);return l.password="",{url:l.toString(),password:i,role:s}}function Nb(e){let t=new URL(e.url);return t.password=encodeURIComponent(e.password),t.toString()}var _d={type:"string",value:"role",description:"owner (migrations, dumps) or app (what the machines use); default owner"};function Sd(e){let t=e.flags.role??"owner";if(t!=="owner"&&t!=="app")throw g("--role is owner or app");return t}var kd={PGSSLROOTCERT:"system"};async function Yt(e,t,n,s,r=!1){let a=await e.io.run(t,n,{env:{PGPASSWORD:s.password,...kd,PGAPPNAME:"palbase-cli"},inherit:r});if(a.code===127)throw new m("missing_tool",`${t} is not installed here`,{hint:"install the Postgres 18 client tools (brew install postgresql@18, apt install postgresql-client-18)"});return a}var $d={name:"db:url",summary:"a database's connection address, for any tool (Prisma, Drizzle, pg_dump, a GUI)",usage:"[name] [--role owner|app] [--pooled] [--with-password]",details:["Without --with-password the address holds no password: give the tool PGPASSWORD, or pass --with-password and keep the line out of your history.","The address asks for a checked certificate (sslmode=verify-full). Node drivers check it against the system's roots by themselves; psql, pg_dump and other libpq tools do only with PGSSLROOTCERT=system (Postgres 16+). The address does not say so itself: a driver that sends what it does not know to the server (postgres.js) would be refused."],args:{min:0,max:1},flags:{role:_d,pooled:{type:"boolean",description:"the pooled port (PgBouncer, transaction mode): for an app's many short connections"},"with-password":{type:"boolean",description:"put the password in the address"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,Sd(e),e.flags.pooled===!0),r=e.flags["with-password"]===!0?Nb(s):s.url;e.out.result({database:n,role:s.role,url:r,libpq_env:kd},()=>{e.io.stdout(`${r}
660
+ `),e.out.note("psql, pg_dump and other libpq tools: PGSSLROOTCERT=system (Postgres 16+), or they cannot check the certificate")})}},Ad={name:"db:shell",summary:"psql on a database (it wakes if asleep); -c runs one statement and ends",usage:"[name] [--role owner|app] [--command <sql>]",args:{min:0,max:1},flags:{role:_d,command:{type:"string",short:"c",value:"sql",description:"run this and end"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,Sd(e),!1),r=e.flags.command;if(r!==void 0){let i=await Yt(e,"psql",[s.url,"-v","ON_ERROR_STOP=1","-X","-c",r],s);if(i.stdout&&e.io.stdout(i.stdout),i.code!==0)throw new m("psql_failed",i.stderr.trim()||`psql ended with ${i.code}`);return}if(!e.interactive)throw g("db:shell with no person at the terminal needs --command <sql>");return e.out.note(`${x(t)} \u203A ${n} as ${s.role}`),(await Yt(e,"psql",[s.url],s,!0)).code}},Pd={name:"db:dump",summary:"a copy of a database (pg_dump's custom format), as its owner",usage:"[name] [--file <path>] [--schema-only]",args:{min:0,max:1},flags:{file:{type:"string",value:"path",description:"where to write it (default <database>-<time>.dump)"},"schema-only":{type:"boolean",description:"its structure, not its data"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.args[0]),s=await Ye(e,t,n,"owner",!1),r=e.flags.file??`${n}-${new Date(e.io.now()).toISOString().replaceAll(":","-").slice(0,19)}.dump`,a=await Yt(e,"pg_dump",["--format=custom","--no-owner","--no-acl",...e.flags["schema-only"]===!0?["--schema-only"]:[],"--file",r,s.url],s);if(a.code!==0)throw new m("pg_dump_failed",a.stderr.trim()||`pg_dump ended with ${a.code}`);e.out.result({database:n,file:r},()=>e.out.line(`Wrote ${r}.`))}},Rd={name:"db:restore",summary:"load a dump into a database, as its owner: a custom-format dump (pg_restore) or a .sql file (psql)",usage:"<file> [--db <name>] [--clean]",details:["A database Palbase did not make is taken over by its first release: its tables stay as they are (palbase db:pull writes their schema files)."],args:{min:1,max:1},flags:{db:{type:"string",value:"name",description:"the database (default: the environment's only one)"},clean:{type:"boolean",description:"drop what the dump makes before making it again"}},scope:"environment",async run(e){let t=await h(e),n=await Pn(e,t,e.flags.db),s=e.args[0];await b(e,t,`load ${s} into the database ${n}`);let r=await Ye(e,t,n,"owner",!1);await Td(e,r,s,e.flags.clean===!0),e.out.result({database:n,loaded:s},()=>e.out.line(`Loaded ${s} into ${n}.`))}};async function Td(e,t,n,s){let r=Lb(n).toLowerCase()===".sql",a=r?await Yt(e,"psql",[t.url,"-v","ON_ERROR_STOP=1","-X","--single-transaction","-f",n],t):await Yt(e,"pg_restore",["--no-owner","--no-acl","--exit-on-error","--single-transaction",...s?["--clean","--if-exists"]:[],"--dbname",t.url,n],t);if(a.code!==0)throw new m("restore_failed",a.stderr.trim()||`${r?"psql":"pg_restore"} ended with ${a.code}`)}var Ed={name:"db:copy",summary:"copy a database's data from one environment of the project to another",usage:"--from <env> --to <env> [--db <name>] [--to-db <name>]",details:["The target is overwritten with the source: what it held of the same tables goes (pg_restore --clean)."],flags:{from:{type:"string",value:"env",description:"the environment copied from (slug)"},to:{type:"string",value:"env",description:"the environment copied into (slug)"},db:{type:"string",value:"name",description:"the source's database (default: its only one)"},"to-db":{type:"string",value:"name",description:"the target's database (default: the same name)"}},scope:"environment",async run(e){let t=e.flags.from,n=e.flags.to;if(!t||!n)throw g("db:copy needs --from <env> and --to <env>");if(t===n)throw g("--from and --to are two environments");let s=await Fe(e),r=s.find(y=>y.slug===t),a=s.find(y=>y.slug===n);if(!r||!a)throw new m("no_such_environment",`the project has no environment ${JSON.stringify(r?n:t)}`);let i=ue(r),o=ue(a),l=await Pn(e,i,e.flags.db),p=e.flags["to-db"]??l;await b(e,o,`overwrite the database ${p} with ${i.env_slug}'s ${l}`);let u=await Ye(e,i,l,"owner",!1),d=await Ye(e,o,p,"owner",!1),f=await Ob(vd(Ib(),"palbase-copy-"));try{let y=vd(f,"copy.dump"),v=await Yt(e,"pg_dump",["--format=custom","--no-owner","--no-acl","--file",y,u.url],u);if(v.code!==0)throw new m("pg_dump_failed",v.stderr.trim()||`pg_dump ended with ${v.code}`);await Td(e,d,y,!0)}finally{await Db(f,{recursive:!0,force:!0})}e.out.result({from:{env:i.env_slug,database:l},to:{env:o.env_slug,database:p}},()=>e.out.line(`Copied ${i.env_slug}/${l} into ${o.env_slug}/${p}.`))}};import{lstat as qb,mkdir as xb,readFile as Mb,rename as Ub,rm as Kb,writeFile as Gb}from"node:fs/promises";import{dirname as Cd,resolve as Bb}from"node:path";var jd="// palbase:stack:begin",Br="// palbase:stack:end",Jb=/^[A-Za-z_$][A-Za-z0-9_$]*$/,_s=e=>Jb.test(e)?e:JSON.stringify(e),St=e=>[...new Set(e)].sort();function Ss(e,t){return t.length===0?[` interface ${e} {}`]:[` interface ${e} {`,...t.map(n=>` ${n}`)," }"]}function zb(e){let t=[...e.buckets].sort((n,s)=>n.name<s.name?-1:n.name>s.name?1:0);return[jd,'declare module "palbase/backend/stack" {',...Ss("Secrets",St(e.secrets).map(n=>`${_s(n)}: true;`)),"",...Ss("Flags",St(e.flags).map(n=>`${_s(n)}: true;`)),"",...Ss("Buckets",t.map(n=>{let s=St(n.variants);return`${_s(n.name)}: { variants: ${s.length===0?"never":s.map(r=>JSON.stringify(r)).join(" | ")} };`})),"",...Ss("Roles",St(e.roles).map(n=>`${_s(n)}: true;`)),"}",Br].join(`
661
661
  `)}function Wb(e,t){if(e===void 0)return`// palbase types:pull writes the block between the markers: the names this environment holds, for palbase/backend/stack.
662
662
  ${t}
663
663
 
664
664
  export {};
665
- `;let n=e.indexOf(jp),s=e.indexOf(Br);if(n>=0&&s>n)return`${e.slice(0,n)}${t}${e.slice(s+Br.length)}`;let r=/^\s*export\s*\{\s*\}\s*;?\s*$/m.test(e)?"":`
665
+ `;let n=e.indexOf(jd),s=e.indexOf(Br);if(n>=0&&s>n)return`${e.slice(0,n)}${t}${e.slice(s+Br.length)}`;let r=/^\s*export\s*\{\s*\}\s*;?\s*$/m.test(e)?"":`
666
666
  export {};
667
667
  `;return`${e}${e===""||e.endsWith(`
668
668
  `)?"":`
669
669
  `}${t}
670
- ${r}`}var Op={name:"types:pull",summary:"write the environment's secret, flag, bucket and role names into palbase/palbase-env.d.ts as types: Secrets.get, Flags and Storage.buckets take them and no other",usage:"[--file <path>]",details:["The names of the production secrets \u2014 what the machines start with, and so what Secrets.get reads there; development's are a developer's \u2014 leaving out what the platform sets itself (PALBASE_SECRET_KEY \u2026), every flag, each bucket with its renditions (Storage.buckets.<name>.getPublicUrl(path, { variant })) and every role \u2014 never a value.","Only the block between the palbase:stack markers is written; the rest of the file stays as it is. Run it again when a name is added or taken away."],flags:{file:{type:"string",value:"path",description:"the file (default palbase/palbase-env.d.ts)"}},scope:"environment",async run(e){let t=await h(e),[n,s,r,a]=await Promise.all([Or(e,t,"production"),c(e,t,"GET","/flags/system"),c(e,t,"GET","/storage/buckets"),c(e,t,"GET","/admin/roles")]),i={secrets:St(n.map(u=>u.name).filter(u=>!Vs(u))),flags:St(s.map(u=>u.key)),buckets:r.buckets.map(u=>({name:u.name,variants:(u.variants??[]).map(p=>p.name)})),roles:St(a.roles.map(u=>u.name))},o=Bb(e.io.cwd,e.flags.file??"palbase/palbase-env.d.ts"),l;for(let u of[Cp(o),o])try{if((await qb(u)).isSymbolicLink())throw new m("symlink",`${u} is a link: types:pull writes into a file of its own`,{hint:"remove the link, or pass --file <path>"})}catch(p){if(p.code!=="ENOENT")throw p}try{l=await Ub(o,"utf8")}catch(u){if(u.code!=="ENOENT")throw u}await Mb(Cp(o),{recursive:!0});let d=`${o}.${process.pid}.${Date.now()}.tmp`;await Gb(d,Wb(l,zb(i)),{flag:"wx"});try{await xb(d,o)}catch(u){throw await Kb(d,{force:!0}),u}e.out.result({file:o,...i},()=>{e.out.line(`Wrote ${o} for ${M(t)}: ${i.secrets.length} secret(s), ${i.flags.length} flag(s), ${i.buckets.length} bucket(s), ${i.roles.length} role(s).`)})}};import{mkdir as Fb,stat as Vb,writeFile as Hb}from"node:fs/promises";import{basename as Xb,join as Jr,resolve as Yb}from"node:path";var Zb=`{
670
+ ${r}`}var Od={name:"types:pull",summary:"write the environment's secret, flag, bucket and role names into palbase/palbase-env.d.ts as types: Secrets.get, Flags and Storage.buckets take them and no other",usage:"[--file <path>]",details:["The names of the production secrets \u2014 what the machines start with, and so what Secrets.get reads there; development's are a developer's \u2014 leaving out what the platform sets itself (PALBASE_SECRET_KEY \u2026), every flag, each bucket with its renditions (Storage.buckets.<name>.getPublicUrl(path, { variant })) and every role \u2014 never a value.","Only the block between the palbase:stack markers is written; the rest of the file stays as it is. Run it again when a name is added or taken away."],flags:{file:{type:"string",value:"path",description:"the file (default palbase/palbase-env.d.ts)"}},scope:"environment",async run(e){let t=await h(e),[n,s,r,a]=await Promise.all([Or(e,t,"production"),c(e,t,"GET","/flags/system"),c(e,t,"GET","/storage/buckets"),c(e,t,"GET","/admin/roles")]),i={secrets:St(n.map(u=>u.name).filter(u=>!Vs(u))),flags:St(s.map(u=>u.key)),buckets:r.buckets.map(u=>({name:u.name,variants:(u.variants??[]).map(d=>d.name)})),roles:St(a.roles.map(u=>u.name))},o=Bb(e.io.cwd,e.flags.file??"palbase/palbase-env.d.ts"),l;for(let u of[Cd(o),o])try{if((await qb(u)).isSymbolicLink())throw new m("symlink",`${u} is a link: types:pull writes into a file of its own`,{hint:"remove the link, or pass --file <path>"})}catch(d){if(d.code!=="ENOENT")throw d}try{l=await Mb(o,"utf8")}catch(u){if(u.code!=="ENOENT")throw u}await xb(Cd(o),{recursive:!0});let p=`${o}.${process.pid}.${Date.now()}.tmp`;await Gb(p,Wb(l,zb(i)),{flag:"wx"});try{await Ub(p,o)}catch(u){throw await Kb(p,{force:!0}),u}e.out.result({file:o,...i},()=>{e.out.line(`Wrote ${o} for ${x(t)}: ${i.secrets.length} secret(s), ${i.flags.length} flag(s), ${i.buckets.length} bucket(s), ${i.roles.length} role(s).`)})}};import{mkdir as Fb,stat as Vb,writeFile as Hb}from"node:fs/promises";import{basename as Xb,join as Jr,resolve as Yb}from"node:path";var Zb=`{
671
671
  "compilerOptions": {
672
672
  "target": "es2023",
673
673
  "module": "esnext",
@@ -803,13 +803,13 @@ export class NotesController {
803
803
  return Database.public.notes.findMany({ orderBy: { column: "created_at", direction: "desc" }, limit: 20 });
804
804
  }
805
805
  }
806
- `;function lw(e){let t=Xb(e).toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[._-]+|[._-]+$/g,"");return t===""?"backend":t.slice(0,214)}function dw(e,t){let n={name:e,private:!0,type:"module",scripts:{dev:"bun --watch run src/main.ts",start:"bun run src/main.ts",test:"bun test ./tests"},dependencies:{palbase:`^${Ds}`},devDependencies:{"@types/bun":"^1.4.2"}};return{"package.json":`${JSON.stringify(n,null,2)}
807
- `,"tsconfig.json":Zb,Dockerfile:Qb,".gitignore":ew,"src/main.ts":aw(t),"src/app.module.ts":rw(t),"src/health.controller.ts":tw,"tests/health.test.ts":nw,...t?{"src/db/public.ts":iw,"src/notes/notes.controller.ts":ow,"tests/notes.test.ts":sw}:{}}}async function pw(e){try{return await Vb(e),!0}catch{return!1}}var Dp={name:"init",summary:"start a backend on palbase/backend here (or in <dir>): a health route, its module, its tests, a Dockerfile; --db adds a schema, its routes and their test",usage:"[dir] [--db]",details:["Nothing already there is written over: a file init would write that exists stops it, naming the file.","Then: bun install, palbase use <project>/<environment>, palbase env:pull, bun dev.",'Its tests: bun test (here, no app); palbase test --users alice,bob --base-url http://localhost:8080 -- bun test ./tests (against the app you started, as test users made for the run); on the platform, palbase machines:update <name> --test-command "bun test ./tests" --test-users alice,bob runs them against each new build before it takes traffic.'],args:{min:0,max:1},flags:{db:{type:"boolean",description:"with a database: a schema file (a table each user reads alone) and its routes"}},scope:"none",async run(e){let t=Yb(e.io.cwd,e.args[0]??"."),n=e.flags.db===!0,s=dw(lw(t),n),r=[];for(let i of Object.keys(s))await pw(Jr(t,i))&&r.push(i);if(r.length>0)throw new m("already_there",`${r.join(", ")} ${r.length===1?"is":"are"} already in ${t}: init starts a backend, it writes over nothing`,{hint:"run it in an empty directory, or palbase init <new-dir>"});for(let[i,o]of Object.entries(s))await Fb(Jr(t,i,".."),{recursive:!0}),await Hb(Jr(t,i),o,{flag:"wx"});let a=["bun install","palbase use <project>/<environment>","palbase env:pull",...n?["palbase db:push --url <your Postgres> (or palbase databases:create, then db:push)"]:[],"bun dev","palbase test --users alice,bob --base-url http://localhost:8080 -- bun test ./tests"];e.out.result({dir:t,files:Object.keys(s),db:n},()=>{e.out.line(`Started a backend in ${t}: ${Object.keys(s).join(", ")}.`),e.out.line(`Next: ${a.join(" \u2192 ")}.`)})}};import{access as Wr}from"node:fs/promises";import{dirname as uw,join as cw,resolve as ks}from"node:path";var Zt={url:{type:"string",value:"postgres-url",description:"a database of your own (a local Postgres 18): the schema goes in it at once"},db:{type:"string",value:"name",description:"the environment's database (default: its only one)"},entry:{type:"string",value:"file",description:"the app's entry, what it is started with (default src/main.ts)"}};async function Qt(e,t=!1){let n=e.flags.url;if(n!==void 0){if(e.flags.db!==void 0)throw g("--url or --db, not both");let l;try{l=new URL(n)}catch{throw g("--url is a postgres://\u2026 address")}if(l.protocol!=="postgres:"&&l.protocol!=="postgresql:")throw g("--url is a postgres://\u2026 address");let d=new URL(l.toString());return d.password="",{said:d.toString(),ownerUrl:n,appUrl:n,local:!0,production:!1}}let s=await h(e),r=e.flags.db;if(r===void 0){let{body:l}=await Ae(e.studio,s.env_ref,"GET","/databases?limit=100");if(l.databases.length!==1)throw g(l.databases.length===0?"the environment has no database: pass --url for one of your own":"which database? --db <name>");r=l.databases[0].name}let a=await Ye(e,s,r,"owner",!1),i=t?await Ye(e,s,r,"app",!1):void 0,o=l=>{let d=new URL(l.url);return d.password=encodeURIComponent(l.password),d.toString()};return{said:`${M(s)} \u203A ${r}`,ownerUrl:o(a),...i&&{appUrl:o(i)},local:!1,production:s.env_slug===Pe,target:s,db:r}}async function $s(e){let t=e.flags.entry,n=ks(e.io.cwd,t??"src/main.ts");try{await Wr(n)}catch{throw new m("no_entry",`${n} is not there: the app's entry is what starts it`,{hint:t?void 0:"pass --entry <file>"})}return n}async function As(e){let t=await e.state.readSession();return t?`cli:${t.user.email??t.user.id}`:"cli:local"}async function pt(e,t,n,s,r){let a=await e.io.run("bun",[t],{cwd:e.io.cwd,env:{PALBASE_DB_MIGRATE:s,PALBASE_DB_OWNER_URL:n.ownerUrl,...n.appUrl&&{PALBASE_DB_URL:n.appUrl},PALBASE_DB_CONNECTIONS:"4",PALBASE_DB_MIGRATION_BY:r}});if(a.code===127)throw new m("missing_tool","bun is not installed here: a Palbase backend runs on Bun",{hint:"https://bun.sh"});let i,o=[];for(let l of a.stdout.split(`
808
- `))if(l.startsWith('{"palbase_migrate"')){let d=JSON.parse(l);d.palbase_migrate===s&&(i=d.result)}else l.trim()!==""&&o.push(l);return o.push(...a.stderr.split(`
809
- `).filter(l=>l.trim()!=="")),{code:a.code,result:i,tail:o.slice(-8)}}function zr(e){for(let t of[...e.tail].reverse())try{let n=JSON.parse(t);if(n.message??n.msg)return n.message??n.msg}catch{return t}return`the app's migration run ended with ${e.code}`}function Ze(e,t,n){return t.code===75?new m("cut_off",`${e} was cut off: ${zr(t)}`,{hint:"run it again: it goes on from where it stopped"}):n?.db!==void 0&&/password authentication failed for user "owner"/.test(zr(t))?new m(`${e}_failed`,`${e}: the owner password this computer keeps for ${n.db} is not the database's now \u2014 it was made new elsewhere`,{hint:`palbase databases:password:reset ${n.db} --role owner makes a new one and keeps it here (another developer's kept one stops working then)`}):new m(`${e}_failed`,`${e}: ${zr(t)}`)}var Ip={name:"db:plan",summary:"what a start of the app's schema would do to a database, and what it would refuse \u2014 nothing is changed",usage:"[--url <postgres-url> | --db <name>] [--check]",details:["--check ends with 1 when the schema would be refused: run it in CI before a push to production (-E main)."],flags:{...Zt,check:{type:"boolean",description:"end with 1 when the plan has refusals"}},scope:"environment",async run(e){let t=await Qt(e),n=await pt(e,await $s(e),t,"plan",await As(e));if(n.code!==0&&n.code!==1)throw Ze("plan",n,t);let s=n.result;if(!s)throw Ze("plan",n,t);if(e.out.result({target:t.said,plan:s},()=>{if(s.adoption){e.out.line(`${t.said}: takes the database over as version 1${s.to>1?`, then version 1 \u2192 ${s.to}`:""}`);for(let r of s.adoption.unforced)e.out.line(` ~ ${r}: row-level security no longer FORCEd on its owner, as v2 made it`);for(let r of s.adoption.unmanaged)e.out.line(` = ${r}: left as it is (not in the schema)`)}else e.out.line(`${t.said}: version ${s.from??"none"} \u2192 ${s.to}`);s.changes.length===0&&s.refusals.length===0&&e.out.line(" nothing to change");for(let r of s.changes)e.out.line(` + ${r}`);for(let r of s.refusals)e.out.line(` \u2717 ${r}`)}),s.refusals.length>0)return e.flags.check===!0?1:0}},Lp={name:"db:status",summary:"a database's Palgres ledger: the version serving, and the migration open and who opened it",usage:"[--url <postgres-url> | --db <name>]",flags:Zt,scope:"environment",async run(e){let t=await Qt(e),n=await pt(e,await $s(e),t,"status",await As(e));if(n.code!==0)throw Ze("status",n,t);let s=n.result;e.out.result({target:t.said,status:s},()=>{e.out.line(`${t.said}: ${s.active?`version ${s.active.n} serving`:"no version yet"}`),s.open&&e.out.line(` open: ${s.open.from??"none"} \u2192 ${s.open.to} (${s.open.phase})${s.open.opened_by?`, opened by ${s.open.opened_by}`:""}`)})}},Np={name:"db:push",summary:"try the app's schema on a database: your own one at once, the environment's beside the version serving",usage:"[--url <postgres-url> | --db <name>] [--reset]",details:["On a database of your own (--url) the migration is started and completed at once; a refused one can --reset (its data goes, the schema is made anew).","On the environment's database it is only started: the release serving goes on with its version, your backend with yours; the next release completes it, or takes its place.","One migration is open at a time: yours is replaced by your next push; another developer's or a release's is said, and yours waits.","Production's schema changes with its releases alone: db:push refuses it (db:plan -E main shows what a release would do)."],flags:{...Zt,reset:{type:"boolean",description:"with --url: make the schema anew, its data gone"}},scope:"environment",async run(e){let t=await Qt(e);if(t.production)throw new m("production","the production environment's schema changes with its releases alone",{hint:"push the code: its release migrates (db:plan -E main shows what it would do)"});let n=await $s(e),s=await As(e);if(e.out.note(`Target: ${t.said}`),t.local){if(await mw(e,t),e.flags.reset===!0){if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed",`--reset drops what ${t.said} holds`,{hint:"confirm with -y"});let d=(await e.io.prompt("Its data goes. Reset it? [y/N] ")).trim().toLowerCase();if(d!=="y"&&d!=="yes")throw new m("cancelled","nothing was changed")}await gw(e,t)}let o=await pt(e,n,t,"start",s);if(o.code!==0){let d=Ze("start",o,t);throw new m(d.code,d.message,{hint:o.code===1?"on a database of your own, --reset makes the schema anew (its data goes)":d.hint})}let l=await pt(e,n,t,"complete",s);if(l.code!==0)throw Ze("complete",l,t);e.out.result({target:t.said,started:o.result,completed:l.result},()=>e.out.line(`${t.said} has the app's schema.`));return}if(e.flags.reset===!0)throw g("--reset is for a database of your own (--url): the environment's keeps its data");let r=await pt(e,n,t,"status",s);if(r.code!==0)throw Ze("status",r,t);let a=r.result.open;if(a&&a.opened_by!==s)throw new m("migration_open",`${t.said} has a migration open (${a.from??"none"} \u2192 ${a.to}) that ${a.opened_by??"a release"} opened`,{hint:a.opened_by?.startsWith("cli:")?"it is theirs to push again or to finish; your own environment keeps you apart: palbase envs:create":"a release is migrating: try once it has completed"});if(a){e.out.note(`Rolling back your own open migration (${a.from??"none"} \u2192 ${a.to}) first: stop a backend of yours still on it, or this waits for it.`);let o=await pt(e,n,t,"rollback",s);if(o.code!==0)throw Ze("rollback",o,t)}await b(e,t.target,"start the app's schema beside the version serving");let i=await pt(e,n,t,"start",s);if(i.code!==0)throw Ze("start",i,t);e.out.result({target:t.said,started:i.result},()=>{let o=i.result;e.out.line(`${t.said}: ${o?.state==="in-sync"?"already has the app's schema":`version ${o?.to??"?"} started beside ${o?.from??"none"}`}.`);for(let l of o?.changes??[])e.out.line(` + ${l}`);o?.state!=="in-sync"&&e.out.line("Your backend runs on it now; the release serving keeps its version until the next release completes yours.")})}},qp={name:"db:migrate",summary:"a migration step by hand: start, complete or rollback \u2014 for a database whose app runs elsewhere, or to finish one",usage:"<start|complete|rollback> [--url <postgres-url> | --db <name>]",details:["An app that is not one of the environment's machines: start, release the app, then complete once the old one is gone (pgroll's way)."],args:{min:1,max:1},flags:Zt,scope:"environment",async run(e){let t=e.args[0];if(t!=="start"&&t!=="complete"&&t!=="rollback")throw g("db:migrate start, complete or rollback");let n=await Qt(e);n.target&&await b(e,n.target,`${t} the migration of ${n.said}`);let s=await pt(e,await $s(e),n,t,await As(e));if(s.code!==0)throw Ze(t,s,n);e.out.result({target:n.said,[t]:s.result},()=>e.out.line(`${n.said}: ${t} done.`))}},Mp={name:"db:pull",summary:"write the schema files (db/*.ts) of what a database holds: to take a database over",usage:"[--url <postgres-url> | --db <name>] [--dir <dir>]",details:["Its first release then takes the database over: the tables stay as they are, nothing is copied."],flags:{...Zt,dir:{type:"string",value:"dir",description:"where db/ is written (default: here)"}},scope:"environment",async run(e){let t=await Qt(e),n=await hw(e),s=ks(e.io.cwd,e.flags.dir??"."),r=await e.io.run("bun",[n,"pull",s],{cwd:e.io.cwd,env:{PALGRES_DATABASE_URL:t.ownerUrl}});if(r.code!==0)throw new m("pull_failed",(r.stderr||r.stdout).trim().split(`
806
+ `;function lw(e){let t=Xb(e).toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[._-]+|[._-]+$/g,"");return t===""?"backend":t.slice(0,214)}function pw(e,t){let n={name:e,private:!0,type:"module",scripts:{dev:"bun --watch run src/main.ts",start:"bun run src/main.ts",test:"bun test ./tests"},dependencies:{palbase:`^${Ds}`},devDependencies:{"@types/bun":"^1.4.2"}};return{"package.json":`${JSON.stringify(n,null,2)}
807
+ `,"tsconfig.json":Zb,Dockerfile:Qb,".gitignore":ew,"src/main.ts":aw(t),"src/app.module.ts":rw(t),"src/health.controller.ts":tw,"tests/health.test.ts":nw,...t?{"src/db/public.ts":iw,"src/notes/notes.controller.ts":ow,"tests/notes.test.ts":sw}:{}}}async function dw(e){try{return await Vb(e),!0}catch{return!1}}var Dd={name:"init",summary:"start a backend on palbase/backend here (or in <dir>): a health route, its module, its tests, a Dockerfile; --db adds a schema, its routes and their test",usage:"[dir] [--db]",details:["Nothing already there is written over: a file init would write that exists stops it, naming the file.","Then: bun install, palbase use <project>/<environment>, palbase env:pull, bun dev.",'Its tests: bun test (here, no app); palbase test --users alice,bob --base-url http://localhost:8080 -- bun test ./tests (against the app you started, as test users made for the run); on the platform, palbase machines:update <name> --test-command "bun test ./tests" --test-users alice,bob runs them against each new build before it takes traffic.'],args:{min:0,max:1},flags:{db:{type:"boolean",description:"with a database: a schema file (a table each user reads alone) and its routes"}},scope:"none",async run(e){let t=Yb(e.io.cwd,e.args[0]??"."),n=e.flags.db===!0,s=pw(lw(t),n),r=[];for(let i of Object.keys(s))await dw(Jr(t,i))&&r.push(i);if(r.length>0)throw new m("already_there",`${r.join(", ")} ${r.length===1?"is":"are"} already in ${t}: init starts a backend, it writes over nothing`,{hint:"run it in an empty directory, or palbase init <new-dir>"});for(let[i,o]of Object.entries(s))await Fb(Jr(t,i,".."),{recursive:!0}),await Hb(Jr(t,i),o,{flag:"wx"});let a=["bun install","palbase use <project>/<environment>","palbase env:pull",...n?["palbase db:push --url <your Postgres> (or palbase databases:create, then db:push)"]:[],"bun dev","palbase test --users alice,bob --base-url http://localhost:8080 -- bun test ./tests"];e.out.result({dir:t,files:Object.keys(s),db:n},()=>{e.out.line(`Started a backend in ${t}: ${Object.keys(s).join(", ")}.`),e.out.line(`Next: ${a.join(" \u2192 ")}.`)})}};import{access as Wr}from"node:fs/promises";import{dirname as uw,join as cw,resolve as ks}from"node:path";var Zt={url:{type:"string",value:"postgres-url",description:"a database of your own (a local Postgres 18): the schema goes in it at once"},db:{type:"string",value:"name",description:"the environment's database (default: its only one)"},entry:{type:"string",value:"file",description:"the app's entry, what it is started with (default src/main.ts)"}};async function Qt(e,t=!1){let n=e.flags.url;if(n!==void 0){if(e.flags.db!==void 0)throw g("--url or --db, not both");let l;try{l=new URL(n)}catch{throw g("--url is a postgres://\u2026 address")}if(l.protocol!=="postgres:"&&l.protocol!=="postgresql:")throw g("--url is a postgres://\u2026 address");let p=new URL(l.toString());return p.password="",{said:p.toString(),ownerUrl:n,appUrl:n,local:!0,production:!1}}let s=await h(e),r=e.flags.db;if(r===void 0){let{body:l}=await Ae(e.studio,s.env_ref,"GET","/databases?limit=100");if(l.databases.length!==1)throw g(l.databases.length===0?"the environment has no database: pass --url for one of your own":"which database? --db <name>");r=l.databases[0].name}let a=await Ye(e,s,r,"owner",!1),i=t?await Ye(e,s,r,"app",!1):void 0,o=l=>{let p=new URL(l.url);return p.password=encodeURIComponent(l.password),p.toString()};return{said:`${x(s)} \u203A ${r}`,ownerUrl:o(a),...i&&{appUrl:o(i)},local:!1,production:s.env_slug===Pe,target:s,db:r}}async function $s(e){let t=e.flags.entry,n=ks(e.io.cwd,t??"src/main.ts");try{await Wr(n)}catch{throw new m("no_entry",`${n} is not there: the app's entry is what starts it`,{hint:t?void 0:"pass --entry <file>"})}return n}async function As(e){let t=await e.state.readSession();return t?`cli:${t.user.email??t.user.id}`:"cli:local"}async function dt(e,t,n,s,r){let a=await e.io.run("bun",[t],{cwd:e.io.cwd,env:{PALBASE_DB_MIGRATE:s,PALBASE_DB_OWNER_URL:n.ownerUrl,...n.appUrl&&{PALBASE_DB_URL:n.appUrl},PALBASE_DB_CONNECTIONS:"4",PALBASE_DB_MIGRATION_BY:r}});if(a.code===127)throw new m("missing_tool","bun is not installed here: a Palbase backend runs on Bun",{hint:"https://bun.sh"});let i,o=[];for(let l of a.stdout.split(`
808
+ `))if(l.startsWith('{"palbase_migrate"')){let p=JSON.parse(l);p.palbase_migrate===s&&(i=p.result)}else l.trim()!==""&&o.push(l);return o.push(...a.stderr.split(`
809
+ `).filter(l=>l.trim()!=="")),{code:a.code,result:i,tail:o.slice(-8)}}function zr(e){for(let t of[...e.tail].reverse())try{let n=JSON.parse(t);if(n.message??n.msg)return n.message??n.msg}catch{return t}return`the app's migration run ended with ${e.code}`}function Ze(e,t,n){return t.code===75?new m("cut_off",`${e} was cut off: ${zr(t)}`,{hint:"run it again: it goes on from where it stopped"}):n?.db!==void 0&&/password authentication failed for user "owner"/.test(zr(t))?new m(`${e}_failed`,`${e}: the owner password this computer keeps for ${n.db} is not the database's now \u2014 it was made new elsewhere`,{hint:`palbase databases:password:reset ${n.db} --role owner makes a new one and keeps it here (another developer's kept one stops working then)`}):new m(`${e}_failed`,`${e}: ${zr(t)}`)}var Id={name:"db:plan",summary:"what a start of the app's schema would do to a database, and what it would refuse \u2014 nothing is changed",usage:"[--url <postgres-url> | --db <name>] [--check]",details:["--check ends with 1 when the schema would be refused: run it in CI before a push to production (-E main)."],flags:{...Zt,check:{type:"boolean",description:"end with 1 when the plan has refusals"}},scope:"environment",async run(e){let t=await Qt(e),n=await dt(e,await $s(e),t,"plan",await As(e));if(n.code!==0&&n.code!==1)throw Ze("plan",n,t);let s=n.result;if(!s)throw Ze("plan",n,t);if(e.out.result({target:t.said,plan:s},()=>{if(s.adoption){e.out.line(`${t.said}: takes the database over as version 1${s.to>1?`, then version 1 \u2192 ${s.to}`:""}`);for(let r of s.adoption.unforced)e.out.line(` ~ ${r}: row-level security no longer FORCEd on its owner, as v2 made it`);for(let r of s.adoption.unmanaged)e.out.line(` = ${r}: left as it is (not in the schema)`)}else e.out.line(`${t.said}: version ${s.from??"none"} \u2192 ${s.to}`);s.changes.length===0&&s.refusals.length===0&&e.out.line(" nothing to change");for(let r of s.changes)e.out.line(` + ${r}`);for(let r of s.refusals)e.out.line(` \u2717 ${r}`)}),s.refusals.length>0)return e.flags.check===!0?1:0}},Ld={name:"db:status",summary:"a database's Palgres ledger: the version serving, and the migration open and who opened it",usage:"[--url <postgres-url> | --db <name>]",flags:Zt,scope:"environment",async run(e){let t=await Qt(e),n=await dt(e,await $s(e),t,"status",await As(e));if(n.code!==0)throw Ze("status",n,t);let s=n.result;e.out.result({target:t.said,status:s},()=>{e.out.line(`${t.said}: ${s.active?`version ${s.active.n} serving`:"no version yet"}`),s.open&&e.out.line(` open: ${s.open.from??"none"} \u2192 ${s.open.to} (${s.open.phase})${s.open.opened_by?`, opened by ${s.open.opened_by}`:""}`)})}},Nd={name:"db:push",summary:"try the app's schema on a database: your own one at once, the environment's beside the version serving",usage:"[--url <postgres-url> | --db <name>] [--reset]",details:["On a database of your own (--url) the migration is started and completed at once; a refused one can --reset (its data goes, the schema is made anew).","On the environment's database it is only started: the release serving goes on with its version, your backend with yours; the next release completes it, or takes its place.","One migration is open at a time: yours is replaced by your next push; another developer's or a release's is said, and yours waits.","Production's schema changes with its releases alone: db:push refuses it (db:plan -E main shows what a release would do)."],flags:{...Zt,reset:{type:"boolean",description:"with --url: make the schema anew, its data gone"}},scope:"environment",async run(e){let t=await Qt(e);if(t.production)throw new m("production","the production environment's schema changes with its releases alone",{hint:"push the code: its release migrates (db:plan -E main shows what it would do)"});let n=await $s(e),s=await As(e);if(e.out.note(`Target: ${t.said}`),t.local){if(await mw(e,t),e.flags.reset===!0){if(!e.global.yes){if(!e.interactive)throw new m("confirmation_needed",`--reset drops what ${t.said} holds`,{hint:"confirm with -y"});let p=(await e.io.prompt("Its data goes. Reset it? [y/N] ")).trim().toLowerCase();if(p!=="y"&&p!=="yes")throw new m("cancelled","nothing was changed")}await gw(e,t)}let o=await dt(e,n,t,"start",s);if(o.code!==0){let p=Ze("start",o,t);throw new m(p.code,p.message,{hint:o.code===1?"on a database of your own, --reset makes the schema anew (its data goes)":p.hint})}let l=await dt(e,n,t,"complete",s);if(l.code!==0)throw Ze("complete",l,t);e.out.result({target:t.said,started:o.result,completed:l.result},()=>e.out.line(`${t.said} has the app's schema.`));return}if(e.flags.reset===!0)throw g("--reset is for a database of your own (--url): the environment's keeps its data");let r=await dt(e,n,t,"status",s);if(r.code!==0)throw Ze("status",r,t);let a=r.result.open;if(a&&a.opened_by!==s)throw new m("migration_open",`${t.said} has a migration open (${a.from??"none"} \u2192 ${a.to}) that ${a.opened_by??"a release"} opened`,{hint:a.opened_by?.startsWith("cli:")?"it is theirs to push again or to finish; your own environment keeps you apart: palbase envs:create":"a release is migrating: try once it has completed"});if(a){e.out.note(`Rolling back your own open migration (${a.from??"none"} \u2192 ${a.to}) first: stop a backend of yours still on it, or this waits for it.`);let o=await dt(e,n,t,"rollback",s);if(o.code!==0)throw Ze("rollback",o,t)}await b(e,t.target,"start the app's schema beside the version serving");let i=await dt(e,n,t,"start",s);if(i.code!==0)throw Ze("start",i,t);e.out.result({target:t.said,started:i.result},()=>{let o=i.result;e.out.line(`${t.said}: ${o?.state==="in-sync"?"already has the app's schema":`version ${o?.to??"?"} started beside ${o?.from??"none"}`}.`);for(let l of o?.changes??[])e.out.line(` + ${l}`);o?.state!=="in-sync"&&e.out.line("Your backend runs on it now; the release serving keeps its version until the next release completes yours.")})}},qd={name:"db:migrate",summary:"a migration step by hand: start, complete or rollback \u2014 for a database whose app runs elsewhere, or to finish one",usage:"<start|complete|rollback> [--url <postgres-url> | --db <name>]",details:["An app that is not one of the environment's machines: start, release the app, then complete once the old one is gone (pgroll's way)."],args:{min:1,max:1},flags:Zt,scope:"environment",async run(e){let t=e.args[0];if(t!=="start"&&t!=="complete"&&t!=="rollback")throw g("db:migrate start, complete or rollback");let n=await Qt(e);n.target&&await b(e,n.target,`${t} the migration of ${n.said}`);let s=await dt(e,await $s(e),n,t,await As(e));if(s.code!==0)throw Ze(t,s,n);e.out.result({target:n.said,[t]:s.result},()=>e.out.line(`${n.said}: ${t} done.`))}},xd={name:"db:pull",summary:"write the schema files (db/*.ts) of what a database holds: to take a database over",usage:"[--url <postgres-url> | --db <name>] [--dir <dir>]",details:["Its first release then takes the database over: the tables stay as they are, nothing is copied."],flags:{...Zt,dir:{type:"string",value:"dir",description:"where db/ is written (default: here)"}},scope:"environment",async run(e){let t=await Qt(e),n=await hw(e),s=ks(e.io.cwd,e.flags.dir??"."),r=await e.io.run("bun",[n,"pull",s],{cwd:e.io.cwd,env:{PALGRES_DATABASE_URL:t.ownerUrl}});if(r.code!==0)throw new m("pull_failed",(r.stderr||r.stdout).trim().split(`
810
810
  `).pop()??`palgres pull ended with ${r.code}`);e.out.result({target:t.said,written:r.stdout.split(`
811
- `).filter(a=>a.startsWith("wrote ")).map(a=>a.slice(6))},()=>e.io.stdout(r.stdout))}},Up={name:"db:seed",summary:"run db/seed.ts against a database that is not production",usage:"[--url <postgres-url> | --db <name>] [--file <path>]",flags:{...Zt,file:{type:"string",value:"path",description:"the seed script (default db/seed.ts)"}},scope:"environment",async run(e){let t=await Qt(e,!0);if(t.production)throw new m("production","seeding writes made-up rows: never into production's database");let n=ks(e.io.cwd,e.flags.file??"db/seed.ts");try{await Wr(n)}catch{throw new m("no_seed",`${n} is not there`)}t.target&&await b(e,t.target,`seed ${t.said}`);let s=await e.io.run("bun",[n],{cwd:e.io.cwd,env:{PALBASE_DB_URL:t.appUrl,PALBASE_DB_OWNER_URL:t.ownerUrl},inherit:!0});if(s.code!==0)throw new m("seed_failed",`${n} ended with ${s.code}`);e.out.result({target:t.said,seeded:n},()=>e.out.line(`Seeded ${t.said}.`))}};async function mw(e,t){let n=[["anon","NOLOGIN"],["authenticated","NOLOGIN"],["service_role","NOLOGIN BYPASSRLS"]].map(([r,a])=>`DO $$ BEGIN CREATE ROLE ${r} ${a}; EXCEPTION WHEN duplicate_object THEN NULL; END $$;`).join(`
812
- `),s=await e.io.run("psql",[t.ownerUrl,"-v","ON_ERROR_STOP=1","-X","-q","-c",n],{});if(s.code===127)throw new m("missing_tool","psql is not installed here",{hint:"install the Postgres 18 client tools"});if(s.code!==0)throw new m("roles_failed",`the roles Palgres needs could not be made: ${s.stderr.trim()}`,{hint:"connect as a role that may CREATE ROLE (the database's superuser)"})}async function gw(e,t){let n=await e.io.run("psql",[t.ownerUrl,"-v","ON_ERROR_STOP=1","-X","-q","-c","DROP SCHEMA IF EXISTS palgres CASCADE; DROP SCHEMA public CASCADE; CREATE SCHEMA public;"],{});if(n.code!==0)throw new m("reset_failed",n.stderr.trim())}async function hw(e){let t=ks(e.io.cwd);for(;;){let n=cw(t,"node_modules","palgres","src","cli.ts");try{return await Wr(n),n}catch{let s=uw(t);if(s===t)throw new m("no_palgres","this project has no palgres in node_modules: db:pull reads the database with the project's own",{hint:"install palbase/backend (it brings palgres)"});t=s}}}var xp=[ba,wa,va,io,oo,lo,Dp,ja,Op,Oa,Na,zi,Wi,Fi,Vi,Hi,Yi,Xi,Zi,Qi,eo,to,no,so,ro,ao,po,uo,co,...Ji,hp,...wp,...Td,...Cd,...Kd,...ap,...mp,md,gd,hd,fd,yd,bd,wd,vd,Sd,Rd,_d,zo,Wo,Fo,Vo,mo,ho,fo,vo,_o,So,$o,Ao,Po,Ro,To,Eo,Co,jo,Oo,Do,Io,go,Lo,No,Mo,Uo,xo,Ko,Go,Bo,Jo,$p,Ap,Pp,Rp,Ep,Ip,Lp,Np,qp,Mp,Up];import{spawn as Gp}from"node:child_process";import{constants as ww,homedir as vw}from"node:os";import{createInterface as _w}from"node:readline/promises";var fw=new Set(["UND_ERR_CONNECT_TIMEOUT","ECONNREFUSED","ENOTFOUND","EAI_AGAIN","ENETUNREACH","EHOSTUNREACH","ENETDOWN"]),yw=new Set(["ECONNRESET","UND_ERR_SOCKET","EPIPE","UND_ERR_CLOSED"]);function bw(e){let t=e?.cause,n=t?.code??t?.errors?.[0]?.code;return typeof n=="string"?n:""}function Kp(e,t,n=3){return(async(s,r={})=>{let a=(r.method??"GET").toUpperCase(),i=r.body instanceof ReadableStream;for(let o=1;;o++)try{return await e(s,r)}catch(l){let d=bw(l);if(!(fw.has(d)||yw.has(d)&&(a==="GET"||a==="HEAD"))||i||o>=n||r.signal?.aborted)throw l;await t(300*o)}})}function Bp(){let e=process.env;return{stdout:t=>{process.stdout.write(t)},stderr:t=>{process.stderr.write(t)},async readStdin(){if(process.stdin.isTTY)return Rw();let t=[];for await(let n of process.stdin)t.push(n);return Buffer.concat(t).toString("utf8")},async prompt(t){let n=_w({input:process.stdin,output:process.stderr});try{return await n.question(t)}finally{n.close()}},openBrowser:t=>Pw(t,e),env:e,cwd:process.cwd(),interactive:!!(process.stdin.isTTY&&process.stderr.isTTY)&&e.CI===void 0,configDir:Yr(e,vw()),fetch:Kp(globalThis.fetch.bind(globalThis),t=>new Promise(n=>setTimeout(n,t))),run:(t,n,s={})=>new Promise(r=>{let a=Gp(t,n,{cwd:s.cwd,env:{...s.envOnly?Aw(process.env):process.env,...s.env??{}},stdio:s.inherit?"inherit":["ignore","pipe","pipe"]}),i="",o="",l=s.inherit?kw(a,process.stdin.isTTY===!0):()=>{};a.stdout?.on("data",d=>i+=d.toString("utf8")),a.stderr?.on("data",d=>o+=d.toString("utf8")),a.once("error",d=>{l(),r({code:d.code==="ENOENT"?127:1,stdout:i,stderr:d.message})}),a.once("close",(d,u)=>{l(),r({code:d??(u?128+(ww.signals[u]??0):1),stdout:i,stderr:o})})}),now:()=>Date.now(),sleep:t=>new Promise(n=>setTimeout(n,t))}}var Sw=["SIGHUP","SIGINT","SIGTERM"];function kw(e,t){let n=Sw.map(s=>{let r=()=>{s==="SIGINT"&&t?void 0:e.kill(s)};return process.on(s,r),[s,r]});return()=>{for(let[s,r]of n)process.off(s,r)}}var $w=["PATH","Path","PATHEXT","HOME","USERPROFILE","TMPDIR","TEMP","TMP","SystemRoot","LANG","LC_ALL"];function Aw(e){return Object.fromEntries($w.flatMap(t=>e[t]===void 0?[]:[[t,e[t]]]))}function Pw(e,t){if(t.PALBASE_NO_BROWSER||t.CI!==void 0)return Promise.resolve(!1);let[n,s]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];return new Promise(r=>{try{let a=Gp(n,s,{stdio:"ignore",detached:!0});a.once("error",()=>r(!1)),a.once("spawn",()=>{a.unref(),r(!0)})}catch{r(!1)}})}function Rw(){let e=process.stdin;return new Promise((t,n)=>{let s="",r=i=>{e.off("data",a),e.setRawMode(!1),e.pause(),process.stderr.write(`
811
+ `).filter(a=>a.startsWith("wrote ")).map(a=>a.slice(6))},()=>e.io.stdout(r.stdout))}},Md={name:"db:seed",summary:"run db/seed.ts against a database that is not production",usage:"[--url <postgres-url> | --db <name>] [--file <path>]",flags:{...Zt,file:{type:"string",value:"path",description:"the seed script (default db/seed.ts)"}},scope:"environment",async run(e){let t=await Qt(e,!0);if(t.production)throw new m("production","seeding writes made-up rows: never into production's database");let n=ks(e.io.cwd,e.flags.file??"db/seed.ts");try{await Wr(n)}catch{throw new m("no_seed",`${n} is not there`)}t.target&&await b(e,t.target,`seed ${t.said}`);let s=await e.io.run("bun",[n],{cwd:e.io.cwd,env:{PALBASE_DB_URL:t.appUrl,PALBASE_DB_OWNER_URL:t.ownerUrl},inherit:!0});if(s.code!==0)throw new m("seed_failed",`${n} ended with ${s.code}`);e.out.result({target:t.said,seeded:n},()=>e.out.line(`Seeded ${t.said}.`))}};async function mw(e,t){let n=[["anon","NOLOGIN"],["authenticated","NOLOGIN"],["service_role","NOLOGIN BYPASSRLS"]].map(([r,a])=>`DO $$ BEGIN CREATE ROLE ${r} ${a}; EXCEPTION WHEN duplicate_object THEN NULL; END $$;`).join(`
812
+ `),s=await e.io.run("psql",[t.ownerUrl,"-v","ON_ERROR_STOP=1","-X","-q","-c",n],{});if(s.code===127)throw new m("missing_tool","psql is not installed here",{hint:"install the Postgres 18 client tools"});if(s.code!==0)throw new m("roles_failed",`the roles Palgres needs could not be made: ${s.stderr.trim()}`,{hint:"connect as a role that may CREATE ROLE (the database's superuser)"})}async function gw(e,t){let n=await e.io.run("psql",[t.ownerUrl,"-v","ON_ERROR_STOP=1","-X","-q","-c","DROP SCHEMA IF EXISTS palgres CASCADE; DROP SCHEMA public CASCADE; CREATE SCHEMA public;"],{});if(n.code!==0)throw new m("reset_failed",n.stderr.trim())}async function hw(e){let t=ks(e.io.cwd);for(;;){let n=cw(t,"node_modules","palgres","src","cli.ts");try{return await Wr(n),n}catch{let s=uw(t);if(s===t)throw new m("no_palgres","this project has no palgres in node_modules: db:pull reads the database with the project's own",{hint:"install palbase/backend (it brings palgres)"});t=s}}}var Ud=[ba,wa,va,io,oo,lo,Dd,ja,Od,Oa,Na,zi,Wi,Fi,Vi,Hi,Yi,Xi,Zi,Qi,eo,to,no,so,ro,ao,po,uo,co,...Ji,hd,...wd,...Tp,...Cp,...Kp,...ad,...md,mp,gp,hp,fp,yp,bp,wp,vp,Sp,Rp,_p,zo,Wo,Fo,Vo,mo,ho,fo,vo,_o,So,$o,Ao,Po,Ro,To,Eo,Co,jo,Oo,Do,Io,go,Lo,No,xo,Mo,Uo,Ko,Go,Bo,Jo,$d,Ad,Pd,Rd,Ed,Id,Ld,Nd,qd,xd,Md];import{spawn as Gd}from"node:child_process";import{constants as ww,homedir as vw}from"node:os";import{createInterface as _w}from"node:readline/promises";var fw=new Set(["UND_ERR_CONNECT_TIMEOUT","ECONNREFUSED","ENOTFOUND","EAI_AGAIN","ENETUNREACH","EHOSTUNREACH","ENETDOWN"]),yw=new Set(["ECONNRESET","UND_ERR_SOCKET","EPIPE","UND_ERR_CLOSED"]);function bw(e){let t=e?.cause,n=t?.code??t?.errors?.[0]?.code;return typeof n=="string"?n:""}function Kd(e,t,n=3){return(async(s,r={})=>{let a=(r.method??"GET").toUpperCase(),i=r.body instanceof ReadableStream;for(let o=1;;o++)try{return await e(s,r)}catch(l){let p=bw(l);if(!(fw.has(p)||yw.has(p)&&(a==="GET"||a==="HEAD"))||i||o>=n||r.signal?.aborted)throw l;await t(300*o)}})}function Bd(){let e=process.env;return{stdout:t=>{process.stdout.write(t)},stderr:t=>{process.stderr.write(t)},async readStdin(){if(process.stdin.isTTY)return Rw();let t=[];for await(let n of process.stdin)t.push(n);return Buffer.concat(t).toString("utf8")},async prompt(t){let n=_w({input:process.stdin,output:process.stderr});try{return await n.question(t)}finally{n.close()}},openBrowser:t=>Pw(t,e),env:e,cwd:process.cwd(),interactive:!!(process.stdin.isTTY&&process.stderr.isTTY)&&e.CI===void 0,configDir:Yr(e,vw()),fetch:Kd(globalThis.fetch.bind(globalThis),t=>new Promise(n=>setTimeout(n,t))),run:(t,n,s={})=>new Promise(r=>{let a=Gd(t,n,{cwd:s.cwd,env:{...s.envOnly?Aw(process.env):process.env,...s.env??{}},stdio:s.inherit?"inherit":["ignore","pipe","pipe"]}),i="",o="",l=s.inherit?kw(a,process.stdin.isTTY===!0):()=>{};a.stdout?.on("data",p=>i+=p.toString("utf8")),a.stderr?.on("data",p=>o+=p.toString("utf8")),a.once("error",p=>{l(),r({code:p.code==="ENOENT"?127:1,stdout:i,stderr:p.message})}),a.once("close",(p,u)=>{l(),r({code:p??(u?128+(ww.signals[u]??0):1),stdout:i,stderr:o})})}),now:()=>Date.now(),sleep:t=>new Promise(n=>setTimeout(n,t))}}var Sw=["SIGHUP","SIGINT","SIGTERM"];function kw(e,t){let n=Sw.map(s=>{let r=()=>{s==="SIGINT"&&t?void 0:e.kill(s)};return process.on(s,r),[s,r]});return()=>{for(let[s,r]of n)process.off(s,r)}}var $w=["PATH","Path","PATHEXT","HOME","USERPROFILE","TMPDIR","TEMP","TMP","SystemRoot","LANG","LC_ALL"];function Aw(e){return Object.fromEntries($w.flatMap(t=>e[t]===void 0?[]:[[t,e[t]]]))}function Pw(e,t){if(t.PALBASE_NO_BROWSER||t.CI!==void 0)return Promise.resolve(!1);let[n,s]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];return new Promise(r=>{try{let a=Gd(n,s,{stdio:"ignore",detached:!0});a.once("error",()=>r(!1)),a.once("spawn",()=>{a.unref(),r(!0)})}catch{r(!1)}})}function Rw(){let e=process.stdin;return new Promise((t,n)=>{let s="",r=i=>{e.off("data",a),e.setRawMode(!1),e.pause(),process.stderr.write(`
813
813
  `),i()},a=i=>{for(let o of i.toString("utf8")){if(o==="")return r(()=>t(s));if(o==="")return r(()=>n(new Error("cancelled: nothing was read")));o==="\r"||o===`
814
814
  `?s+=`
815
- `:o==="\x7F"||o==="\b"?s=s.slice(0,-1):s+=o}};e.setRawMode(!0),e.resume(),e.on("data",a)})}process.exitCode=await ma(process.argv.slice(2),Bp(),xp);
815
+ `:o==="\x7F"||o==="\b"?s=s.slice(0,-1):s+=o}};e.setRawMode(!0),e.resume(),e.on("data",a)})}process.exitCode=await ma(process.argv.slice(2),Bd(),Ud);