palbase 0.3.0 → 0.3.2

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 (38) hide show
  1. package/LICENSE +14 -5
  2. package/dist/ai/index.js +2 -2
  3. package/dist/backend/engine.js +2 -2
  4. package/dist/backend/env.js +1 -1
  5. package/dist/backend/index.js +3 -3
  6. package/dist/backend/stack.js +1 -1
  7. package/dist/backend/test.js +2 -2
  8. package/dist/chunks/{UNOLIHKD.js → 32BPNFZX.js} +1 -1
  9. package/dist/chunks/33DZIPGE.js +2 -0
  10. package/dist/chunks/44SLUFBO.js +92 -0
  11. package/dist/chunks/{AR6WQDU5.js → CNRFJ7AK.js} +2 -2
  12. package/dist/chunks/{RK6M2LNY.js → HHSKJTI6.js} +2 -2
  13. package/dist/chunks/{IQIXNAI4.js → HJVB6KIR.js} +1 -1
  14. package/dist/chunks/{MJ35SVVY.js → JFWH2INT.js} +2 -2
  15. package/dist/chunks/{YQYMP55L.js → QA3GIZMI.js} +1 -1
  16. package/dist/chunks/{4LGRTNOK.js → QD5XLRIJ.js} +2 -2
  17. package/dist/chunks/{W2GV6UU3.js → QRWBXKEM.js} +2 -2
  18. package/dist/chunks/{4A6S7JKW.js → RHQ2NBCI.js} +2 -2
  19. package/dist/chunks/{6BUQ7N4I.js → TTKDWRP5.js} +2 -2
  20. package/dist/chunks/{LLBV62FM.js → XUG2RG4F.js} +2 -2
  21. package/dist/chunks/{BPWQDLXP.js → YSZ2RSTS.js} +1 -1
  22. package/dist/chunks/{NDJAXNM6.js → ZEFNZ6DZ.js} +2 -2
  23. package/dist/cli.js +157 -133
  24. package/dist/refuse/ai.js +1 -1
  25. package/dist/refuse/backend.js +1 -1
  26. package/dist/refuse/server.js +1 -1
  27. package/dist/server/index.js +2 -2
  28. package/dist/server/testing.js +2 -2
  29. package/dist/web/index.js +2 -2
  30. package/dist/web/palbe.js +2 -2
  31. package/dist/web/paywall.js +2 -2
  32. package/dist/web/vite.js +2 -2
  33. package/package.json +7 -7
  34. package/types/internal/src/modules/purchases.d.ts +77 -4
  35. package/types/internal/src/modules/storage.d.ts +3 -2
  36. package/types/server/src/auth/jwt.d.ts +4 -1
  37. package/dist/chunks/7D5J3M3D.js +0 -87
  38. package/dist/chunks/A4VJFSSX.js +0 -2
package/dist/cli.js CHANGED
@@ -1,37 +1,37 @@
1
1
  #!/usr/bin/env node
2
- /*! palbase 0.3.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 g=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 m(e,t){return new g("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
- `):n()}line(t=""){this.io.stdout(`${G(t)}
5
- `)}note(t){this.io.stderr(`${G(t)}
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 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 g("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 g("access_denied","the sign-in was denied in Studio");case"expired_token":throw new g("expired_code","the code expired before it was approved",{hint:"run palbase login again"});default:throw new g(i.error??`http_${a.status}`,`the sign-in failed: ${i.error_description??i.error??a.status}`)}}throw new g("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 g("signed_out","you are not signed in",{hint:"run palbase login"})}function aa(e,t){if(typeof t.access_token!="string")throw new g("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 g("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 g("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 g(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 g("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 g("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
- `);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 g("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.3.0"};var Ds=la.version;async function ga(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 $=e[o+1],S=$===void 0?void 0:s.get($);if($!==void 0&&!S)throw pa($,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 m(`${l.name} needs ${f.min===1?"an argument":`${f.min} arguments`}`,Os(l));if(d.length>f.max)throw m(`${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 m(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 g?(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
- `):(t.stderr(`error: ${G(a)}${l?` (request ${G(l)})`:""}
13
- `),i&&t.stderr(` ${G(i)}
2
+ /*! palbase 0.3.2 — Palbase SDK License (see LICENSE) */
3
+ import{parseArgs as rm}from"node:util";var Gu=Symbol.for("palbase.data"),Bu=globalThis,Jr=Bu[Gu]??=new WeakSet;function Ju(e){return e instanceof Date||ArrayBuffer.isView(e)||e instanceof ArrayBuffer}function ra(e){let t=[e];for(;t.length>0;){let n=t.pop();if(!(typeof n!="object"||n===null||Jr.has(n)||Ju(n))){Jr.add(n);for(let s of Array.isArray(n)?n:Object.values(n))t.push(s)}}return e}var Q_=typeof globalThis.setImmediate=="function"?()=>new Promise(e=>globalThis.setImmediate(e)):()=>new Promise(e=>setTimeout(e,0));var x=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 rk=64*1024;function ia(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 Jn={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 h=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 m(e,t){return new h("usage",e,{hint:t,exitCode:2})}var Fn=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
+ `):n()}line(t=""){this.io.stdout(`${W(t)}
5
+ `)}note(t){this.io.stderr(`${W(t)}
6
+ `)}warn(t){this.io.stderr(`warning: ${W(t)}
7
+ `)}table(t,n){if(t.length===0)return;let s=t.map(i=>n.map(o=>N(o.value(i)))),a=n.map((i,o)=>Math.max(i.header.length,...s.map(l=>l[o].length))),r=i=>i.map((o,l)=>l===i.length-1?o:o.padEnd(a[l])).join(" ").trimEnd();this.line(r(n.map(i=>i.header.toUpperCase())));for(let i of s)this.line(r(i))}};function W(e){return e.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}function N(e){return W(e).replace(/[\n\t\u2028\u2029]/g,t=>`\\u${t.charCodeAt(0).toString(16).padStart(4,"0")}`)}import{chmod as Fu,mkdir as Fr,readFile as Wu,rename as Vu,rm as bn,stat as Hu,writeFile as Wr}from"node:fs/promises";import{dirname as Xu,join as dt,parse as Yu,resolve as oa}from"node:path";import{randomBytes as Zu}from"node:crypto";var Wn=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 bn(dt(this.dir,"session.json"),{force:!0})}async targetFor(t){let n=await this.read("targets.json")??{},s=oa(t),a=Yu(s).root;for(;;){let r=n[s];if(r)return{dir:s,target:r};if(s===a)return null;s=Xu(s)}}async setTarget(t,n){await this.update("targets.json",s=>(s[oa(t)]=n,!0))}async clearTarget(t){let n=!1;return await this.update("targets.json",s=>{let a=oa(t);return n=a in s,n&&delete s[a],n}),n}async databasePasswords(t,n){return(await this.read("databases.json")??{})[t]?.[n]??{}}async keepDatabasePasswords(t,n,s){await this.update("databases.json",a=>(a[t]={...a[t]??{},[n]:{...a[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 bn(dt(this.dir,"keys.json"),{force:!0})}async forget(t){await bn(dt(this.dir,t),{force:!0})}async read(t){let n;try{n=await Wu(dt(this.dir,t),"utf8")}catch(s){if(s.code==="ENOENT")return null;throw s}return JSON.parse(n)}async update(t,n){await Fr(this.dir,{recursive:!0,mode:448});let s=dt(this.dir,`${t}.lock`);for(let a=0;;a+=Vr)try{await Wr(s,String(process.pid),{flag:"wx",mode:384});break}catch(r){if(r.code!=="EEXIST")throw r;if(await Hu(s).then(o=>Date.now()-o.mtimeMs,()=>0)>em)await bn(s,{force:!0});else{if(a>Qu)throw new Error(`${s} is held by another palbase command: try again, or remove it if none runs`);await new Promise(o=>setTimeout(o,Vr))}}try{let a=await this.read(t)??{};n(a)&&await this.write(t,a)}finally{await bn(s,{force:!0})}}async write(t,n){await Fr(this.dir,{recursive:!0,mode:448}),await Fu(this.dir,448);let s=dt(this.dir,t),a=`${s}.${Zu(6).toString("hex")}.tmp`;await Wr(a,`${JSON.stringify(n,null,2)}
8
+ `,{mode:384}),await Vu(a,s)}},Vr=25,Qu=1e4,em=3e4;function Hr(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:dt(t,".config");return dt(n,"palbase")}var Xr="v3.palbase.studio",wn="palbase-cli";function Yr(e){let t=e.PALBASE_DOMAIN&&e.PALBASE_DOMAIN!==""?e.PALBASE_DOMAIN:Xr;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 Fe(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 Zr(e,t){return t.domain===Xr?e:`${e}/domains/${t.domain}`}var tm="openid profile email offline_access";async function Qr(e,t,n){let s=await Hn(e,t,"/oauth/device",{client_id:wn,scope:tm,...n!==void 0?{max_age:String(n)}:{}});if(s.status!==200)throw await la("the sign-in could not be started",s);let a=await s.json();if(typeof a.device_code!="string"||typeof a.user_code!="string")throw new h("auth_unavailable","the sign-in could not be started: auth's answer has no code");return{device_code:a.device_code,user_code:a.user_code,expires_in:a.expires_in??900,interval:a.interval??5}}async function ei(e,t,n){let s=Math.max(1,n.interval),a=e.now()+n.expires_in*1e3;for(;e.now()<a;){await e.sleep(s*1e3);let r;try{r=await Hn(e,t,"/oauth/token",{grant_type:"urn:ietf:params:oauth:grant-type:device_code",device_code:n.device_code,client_id:wn})}catch{continue}if(r.status>=500||r.status===429)continue;if(r.status===200)return si(e,await r.json());let i=await r.json().catch(()=>({}));switch(i.error){case"authorization_pending":continue;case"slow_down":s+=5;continue;case"access_denied":throw new h("access_denied","the sign-in was denied in Studio");case"expired_token":throw new h("expired_code","the code expired before it was approved",{hint:"run palbase login again"});default:throw new h(i.error??`http_${r.status}`,`the sign-in failed: ${i.error_description??i.error??r.status}`)}}throw new h("expired_code","the code expired before it was approved",{hint:"run palbase login again"})}async function ti(e,t,n){if(!n.refresh_token)throw vn();let s=await Hn(e,t,"/oauth/token",{grant_type:"refresh_token",refresh_token:n.refresh_token,client_id:wn});if(s.status===400||s.status===401)throw vn();if(s.status!==200)throw await la("the session could not be renewed",s);let a=si(e,await s.json());return{...a,refresh_token:a.refresh_token??n.refresh_token}}async function ni(e,t,n){if(!n.refresh_token)return;let s=await Hn(e,t,"/oauth/revoke",{token:n.refresh_token,token_type_hint:"refresh_token",client_id:wn});if(s.status!==200)throw await la("the session could not be ended at auth",s)}function vn(){return new h("signed_out","you are not signed in",{hint:"run palbase login"})}function si(e,t){if(typeof t.access_token!="string")throw new h("auth_unavailable","auth's answer has no access token");let n=Vn(t.access_token),s=typeof n.sub=="string"?n.sub:"";if(s==="")throw new h("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 Vn(e){let t=e.split(".")[1];if(!t)return{};try{return JSON.parse(Buffer.from(t,"base64url").toString("utf8"))}catch{return{}}}async function Hn(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(a){throw new h("network_error",`Palbase (${t.platformUrl}) could not be reached: ${a instanceof Error?a.message:String(a)}`)}}async function la(e,t){let n=await t.json().catch(()=>({}));return new h(n.error??`http_${t.status}`,`${e}: ${n.error_description??n.error??`auth answered ${t.status}`}`)}var nm="/v1",sm="x-studio-module-answer",ai=3e4,Xn=class{cloud;io;state;debug;session=null;constructor(t,n,s,a=!1){this.io=t,this.cloud=n,this.state=s,this.debug=a}async currentSession(){if(this.session&&this.io.now()<this.session.expires_at-ai)return this.session;let t=this.session??await this.state.readSession();if(!t)throw vn();return this.io.now()<t.expires_at-ai?(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:a,renewable:r}=await this.exchange(t,n,s);return this.answered(t,n,a,r)}async call(t,n){let{res:s,renewable:a}=await this.exchange("POST",t,n);if(s.headers.get(sm)!=="1")throw await this.answered("POST",t,s,a),new h("studio_answer",`Studio answered ${t} ${s.status} as no module's answer`);let r=s.status===204||s.status===304?"":await s.text(),i=Number(s.headers.get("retry-after")??"");return{status:s.status,body:r===""?void 0:ri(r),etag:s.headers.get("etag")??void 0,retryAfter:Number.isFinite(i)&&i>0?i:void 0}}async exchange(t,n,s){let a=await this.token(),r=await this.send(t,n,s,a.value);if(r.status===401&&a.renewable){let i=await this.renew(await this.currentSession());r=await this.send(t,n,s,i.access_token)}return{res:r,renewable:a.renewable}}async answered(t,n,s,a){if(s.status===204)return;let r=await s.text(),i=r===""?void 0:ri(r);if(s.ok)return i;let o=i??{};throw s.status===401?a?vn():new h("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 x({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 ti(this.io,this.cloud,t);return await this.state.writeSession(n),this.session=n,n}async send(t,n,s,a){let r=`${this.cloud.studioApi}${nm}${n}`;this.debug&&this.io.stderr(`> ${t} ${r}
9
+ `);try{let i=await this.io.fetch(r,{method:t,headers:{authorization:`Bearer ${a}`,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 h("network_error",`Studio (${this.cloud.studioApi}) could not be reached: ${i instanceof Error?i.message:String(i)}`)}}};function ri(e){try{return JSON.parse(e)}catch{return e}}var ii={version:"0.3.2"};var da=ii.version;async function ci(e,t,n){let s=new Map(n.map(i=>[i.name,i])),a=e.includes("--json"),r=e.includes("--debug");try{let{name:i,at:o}=im(e);if(i===void 0)return e.includes("--version")?(t.stdout(`palbase ${da}
11
+ `),0):(t.stdout(di(n)),e.length===0||e.includes("--help")||e.includes("-h")?0:2);if(i==="help"){let k=e[o+1],$=k===void 0?void 0:s.get(k);if(k!==void 0&&!$)throw oi(k,n);return t.stdout($?pi($):di(n)),0}let l=s.get(i);if(!l)throw oi(i,n);let p=[...e.slice(0,o),...e.slice(o+1)];if(p.includes("--help")||p.includes("-h"))return t.stdout(pi(l)),0;let{flags:d,positionals:c}=om(l,p),g=l.args??{min:0,max:0};if(c.length<g.min)throw m(`${l.name} needs ${g.min===1?"an argument":`${g.min} arguments`}`,pa(l));if(c.length>g.max)throw m(`${l.name} takes ${g.max===0?"no argument":`at most ${g.max}`}: ${JSON.stringify(c.slice(g.max))} is left over`,pa(l));let f={project:d.project,env:d.env,json:d.json===!0,nonInteractive:d["non-interactive"]===!0,yes:d.yes===!0,debug:d.debug===!0},b=Yr(t.env),P=new Wn(Zr(t.configDir,b)),E={io:t,state:P,out:new Fn(t,f.json),global:f,args:c,flags:d,interactive:t.interactive&&!f.nonInteractive,cloud:b,studio:new Xn(t,b,P,f.debug)};return await l.run(E)??0}catch(i){return lm(i,t,a,r)}}function im(e){let t=new Set;for(let[n,s]of Object.entries(Jn))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 om(e,t){let n={...Jn,...e.flags??{}},s={};for(let[a,r]of Object.entries(n))s[a]={type:r.type,...r.short?{short:r.short}:{},...r.multiple?{multiple:!0}:{}};try{let{values:a,positionals:r}=rm({args:t,options:s,strict:!0,allowPositionals:!0});return{flags:a,positionals:r}}catch(a){throw m(a instanceof Error?a.message.replace(/^.*?: /,""):String(a),`palbase help ${e.name}`)}}function lm(e,t,n,s){let a="error",r,i,o=1,l;return e instanceof h?(a=e.code,r=e.message,i=e.hint,o=e.exitCode):e instanceof x?(a=e.code,r=e.message,l=e.requestId):r=e instanceof Error?e.message:String(e),n?t.stdout(`${JSON.stringify({error:a,message:r,...i?{hint:i}:{},...l?{request_id:l}:{}},null,2)}
12
+ `):(t.stderr(`error: ${W(r)}${l?` (request ${W(l)})`:""}
13
+ `),i&&t.stderr(` ${W(i)}
14
14
  `)),s&&e instanceof Error&&e.stack&&t.stderr(`${e.stack}
15
- `),o}function pa(e,t){let n=t.map(s=>s.name).filter(s=>s.startsWith(e.split(":")[0])||gu(s,e)<=2);return m(`unknown command ${JSON.stringify(e)}`,n.length>0?`did you mean: ${n.slice(0,5).join(", ")}`:"palbase help lists every command")}function gu(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(`
15
+ `),o}function oi(e,t){let n=t.map(s=>s.name).filter(s=>s.startsWith(e.split(":")[0])||pm(s,e)<=2);return m(`unknown command ${JSON.stringify(e)}`,n.length>0?`did you mean: ${n.slice(0,5).join(", ")}`:"palbase help lists every command")}function pm(e,t){let n=Array.from({length:e.length+1},(s,a)=>[a,...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 a=1;a<=t.length;a++)n[s][a]=Math.min(n[s-1][a]+1,n[s][a-1]+1,n[s-1][a-1]+(e[s-1]===t[a-1]?0:1));return n[e.length][t.length]}function pa(e){return`usage: palbase ${e.name}${e.usage?` ${e.usage}`:""}`}function li(e){let t=Object.entries(e).map(([s,a])=>({name:`${a.short?`-${a.short}, `:" "}--${s}${a.type==="string"?` <${a.value??"value"}>`:""}`,text:a.description})),n=Math.max(...t.map(s=>s.name.length));return t.map(s=>` ${s.name.padEnd(n)} ${s.text}`)}function pi(e){let t=[pa(e),"",e.summary];return e.details?.length&&t.push("",...e.details),e.flags&&Object.keys(e.flags).length>0&&t.push("","Flags:",...li(e.flags)),t.push("","Global flags:",...li(Jn)),`${t.join(`
16
16
  `)}
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(`
17
+ `}function di(e){let t=new Map;for(let a of e){let r=a.name.includes(":")?a.name.slice(0,a.name.indexOf(":")):"";t.set(r,[...t.get(r)??[],a])}let n=Math.max(...e.map(a=>a.name.length)),s=["palbase \u2014 your Palbase organizations, projects and environments from the terminal","","usage: palbase <command> [flags]",""];for(let[a,r]of t){s.push(a===""?"Commands:":`${a}:`);for(let i of r)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 ma,timingSafeEqual as mu}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=ma(32).toString("base64url"),r=fa("sha256").update(s,"ascii").digest("base64url"),a=ma(24).toString("base64url"),i,o=new Promise((A,$)=>{i={ok:A,fail:$}}),l="",p=async A=>{let $=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 $.json().catch(()=>({}));if($.status!==200||!S.access_token)throw new g(S.error??"owner_sign_in_failed",S.error_description??`the owner's code was not redeemed (${$.status})`,{hint:"run palbase login --owner again"});let C=rn(S.access_token),P=typeof C.sub=="string"?C.sub:"",U=Number(S.expires_in);if(P===""||!Number.isFinite(U)||U<=0)throw new g("owner_sign_in_failed","the token endpoint answered no token");return{access_token:S.access_token,expires_at:e.now()+Math.min(U,900)*1e3,user:{id:P,...typeof C.email=="string"?{email:C.email}:{}}}},u=!1,d=hu((A,$)=>{let S=L=>{$.writeHead(303,{location:`${t.studioWeb}/cli/owner/done?result=${L}`,"cache-control":"no-store","referrer-policy":"no-referrer"}),$.end()},C=new URL(A.url??"/","http://127.0.0.1");if(A.method!=="GET"||C.pathname!==ha)return $.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),$.end("Not here.");let P=Buffer.from(C.searchParams.get("state")??""),U=Buffer.from(a);if(P.length!==U.length||!mu(P,U))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,$)=>{d.once("error",$),d.listen(0,"127.0.0.1",()=>A())}),l=`http://127.0.0.1:${d.address().port}${ha}`;let y=setTimeout(()=>i.fail(new g("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 g("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 g("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",Pu=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()>Pu)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 g("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 g("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 $u(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 g(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 $u(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 $e="main";async function qe(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 q(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 qe(e.studio),o;if(t!==void 0)o=gt(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 g("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 Pa(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 qe(e.studio);if(r.length===0)throw new g("no_environments","your account reaches no environment yet",{hint:"make one: palbase projects:create <name>"});let a=ue(await Pa(e,r,"Which environment?"));return e.out.note(`Acting on ${q(a)}. palbase use remembers it for this directory.`),a}function gt(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 g("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 g("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(`${q(t)}: ${n}`);let r=t.env_slug===$e;if(!r&&s===void 0||e.global.yes)return;if(!e.interactive)throw new g("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 g("cancelled","nothing was changed")}async function Pa(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)}) ${q(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 g("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,$a=[];async function Du(e){zs<Ou?zs++:await new Promise(t=>$a.push(t));try{return await e()}finally{let t=$a.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 m(`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 m(`${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 $t(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 g("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 m(`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 m(`${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 m(`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 m("--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 m(`--${t} is yes or no`)}}function ge(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 Ke(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw m(`--${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 M(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 Uu,lstat as Gu,readFile as Ea,rename as Bu,writeFile as Ca}from"node:fs/promises";var u0=1024*1024;var m0=8*1024;var _0=16*1024,S0=10*6e4;var qu=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Ku=/^(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!qu.test(e)||Ku.test(e)||/_PROXY$/i.test(e)}var Mu=/^(PORT|PALBASE_(URL|PROJECT_ID|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function Vs(e){return Mu.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 xn(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 Nn(e){let t=e.flags.secrets??"development";if(t!=="development"&&t!=="production")throw m("--secrets is development or production");return t}function qn(e,t){return t==="production"?`the app reads ${e.project_name} \u203A ${e.env_name}'s production secrets here`:void 0}var Kn={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:Kn},scope:"environment",async run(e){let t=await h(e),n=Nn(e);(t.env_slug===$e||n==="production")&&await b(e,t,`write a key of ${t.env_slug===$e?"the production environment":`${t.env_slug}`} into a file on this computer`,qn(t,n));let s=await xn(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(q(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 g("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(`
19
+ `}import{createHash as Lk,randomBytes as Dk,timingSafeEqual as Ik}from"node:crypto";import{createServer as Mk}from"node:http";var ca="owner.json";var Uk=300*1e3;var ui={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"}},scope:"none",async run(e){if(e.io.env.PALBASE_TOKEN)throw new h("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"});let t=await Qr(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 a=await ei(e.io,e.cloud,t);await e.state.writeSession(a),e.out.result({user:a.user},()=>e.out.line(`Signed in as ${a.user.email??a.user.id}.`))}},mi={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 h("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(a){a instanceof Error&&"status"in a&&a.status===404||e.out.warn(`your key at ${n} (${s.label}) could not be revoked: ${a instanceof Error?a.message:String(a)} \u2014 revoke it in Studio, or it ends ${s.expires_at}`)}await e.state.forgetPersonalKeys(),await e.state.forget(ca);try{await ni(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}.`))}},hi={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:a}=await e.studio.get("/orgs"),r=a[0];n={id:`token \u2026${t.slice(-4)}`,via:"token",...r&&{organization:{id:r.id,name:r.name,role:r.role}}}}else if(t){let a=Vn(t);n={id:String(a.sub??""),...typeof a.email=="string"?{email:a.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 ${W(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 um}from"node:os";async function Oe(e,t,n,s,a,r){let i=await e.call(`/environments/${encodeURIComponent(t)}/call`,{method:n,path:s,...a!==void 0?{body:a}:{},...r&&Object.keys(r).length>0?{headers:r}:{}});if(i.status>=200&&i.status<300)return{status:i.status,body:ra(i.body),etag:i.etag};let o=i.body??{};throw new x({op:`${n} ${s}`,status:i.status,code:typeof o.error=="string"?o.error:`http_${i.status}`,message:ka(o,i.status),requestId:typeof o.request_id=="string"?o.request_id:void 0,body:i.body})}var mm=10080*60*1e3;async function _a(e,t,n=!1){let s=await e.state.personalKey(t);if(!n&&s&&Date.parse(s.expires_at)-e.io.now()>mm)return s;let a=await e.post(`/environments/${encodeURIComponent(t)}/personal-key`,{host:um().slice(0,100)||"computer"});return await e.state.keepPersonalKey(t,a),a}function ka(e,t){let n=e??{},s=typeof n.error_description=="string"?n.error_description:`the module answered ${t}`,a=Array.isArray(n.field_errors)?n.field_errors.flatMap(r=>{let{pointer:i,message:o}=r??{};return typeof o=="string"?[`${typeof i=="string"&&i!==""?`${i}: `:""}${o}`]:[]}):[];return a.length>0?`${s}: ${a.join("; ")}`:s}import{relative as hm}from"node:path";async function Sa(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(),a=await e.run("git",["remote","get-url","origin"],{cwd:t}),r=await e.run("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:t}),i=r.code===0?r.stdout.trim():"";return{root:s,repository:a.code===0?gm(a.stdout.trim()):void 0,branch:i!==""&&i!=="HEAD"?i:void 0,path:hm(s,t).split("\\").join("/")}}function gm(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 xe="main";async function We(e){let[t,n]=await Promise.all([e.get("/environments"),e.get("/orgs")]),s=new Map(n.organizations.map(a=>[a.id,a.name]));return t.environments.map(a=>({...a,org_name:s.get(a.organization_id)??a.organization_id}))}function be(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 B(e){return`${e.org_name} \u203A ${e.project_name} \u203A ${e.env_name}${e.env_name!==e.env_slug?` (${e.env_slug})`:""}`}async function y(e){let{project:t,env:n}=e.global;if(t!==void 0||n!==void 0){let i=await We(e.studio),o;if(t!==void 0)o=St(i,t);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw $a("-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 h("no_such_environment",`the project has no environment ${JSON.stringify(n)}`,{hint:`its environments: ${o.map(p=>p.slug).join(", ")}`});return be(l)}return o.length===1?be(o[0]):be(await bi(e,o,"Which environment?"))}let s=await e.state.targetFor(e.io.cwd);if(s)return s.target;if(!e.interactive)throw $a("no environment is chosen for this directory");let a=await We(e.studio);if(a.length===0)throw new h("no_environments","your account reaches no environment yet",{hint:"make one: palbase projects:create <name>"});let r=be(await bi(e,a,"Which environment?"));return e.out.note(`Acting on ${B(r)}. palbase use remembers it for this directory.`),r}function St(e,t){let n=e.filter(l=>l.project_id===t);if(n.length>0)return n;let s=t.indexOf("/"),[a,r]=s>=0?[t.slice(0,s),t.slice(s+1)]:[void 0,t],i=e.filter(l=>wi(l.project_name,r)&&(a===void 0||wi(l.org_name,a)||l.organization_id===a)),o=new Set(i.map(l=>l.project_id));if(o.size===0)throw new h("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 h("ambiguous_project",`${JSON.stringify(t)} is a project in more than one organization: ${l}`,{hint:"name it as org/project"})}return i}async function w(e,t,n,s){e.out.note(`${B(t)}: ${n}`);let a=t.env_slug===xe;if(!a&&s===void 0||e.global.yes)return;if(!e.interactive)throw new h("confirmation_needed",a?`${t.project_name} \u203A ${t.env_name} is the project's production environment`:s,{hint:"confirm with -y"});let r=(await e.io.prompt(W(`${a?`This is ${t.project_name}'s production environment`:s}. Go ahead? [y/N] `))).trim().toLowerCase();if(r!=="y"&&r!=="yes")throw new h("cancelled","nothing was changed")}async function bi(e,t,n){if(!e.interactive)throw $a(`${n.replace(/\?$/,"")}: ${t.map(s=>`${s.project_name}/${s.slug}`).join(", ")}`);for(t.forEach((s,a)=>e.out.note(` ${String(a+1).padStart(2)}) ${B(be(s))}`));;){let s=(await e.io.prompt(`${n} [1-${t.length}] `)).trim(),a=Number(s);if(Number.isInteger(a)&&a>=1&&a<=t.length)return t[a-1];e.out.note(`answer with a number from 1 to ${t.length}`)}}function $a(e){return new h("no_target",e,{hint:"choose one here with palbase use, or pass -P <project> -E <env>"})}function wi(e,t){return e.localeCompare(t,void 0,{sensitivity:"accent"})===0}import{randomUUID as fm}from"node:crypto";import{readFile as ym}from"node:fs/promises";import{resolve as bm}from"node:path";var wm=6,Pa=0,vi=[];async function vm(e){Pa<wm?Pa++:await new Promise(t=>vi.push(t));try{return await e()}finally{let t=vi.shift();t?t():Pa--}}async function we(e,t,n,s,a,r){return vm(()=>Oe(e.studio,t.env_ref,n,s,a,r))}async function u(e,t,n,s,a,r){return(await we(e,t,n,s,a,r)).body}async function ve(e,t,n){let s=await we(e,t,"GET",n);return{body:s.body,etag:s.etag}}function de(e){return e?{"if-match":e}:void 0}function R(){return{"idempotency-key":fm()}}function v(e){return encodeURIComponent(e)}function ct(e){return e.map(t=>{let n=t.indexOf("=");if(n<=0)throw m(`a setting is key=value, not ${JSON.stringify(t)}`);let s=t.slice(0,n).split(".");if(s.some(a=>a===""||a==="__proto__"||a==="constructor"||a==="prototype"))throw m(`${JSON.stringify(t.slice(0,n))} is not a key`);return{path:s,value:Ve(t.slice(n+1))}})}function Ve(e){try{return JSON.parse(e)}catch{return e}}function ut(e,t){let n=structuredClone(e);for(let{path:s,value:a}of t){let r=n;for(let i of s.slice(0,-1)){let o=r[i];(typeof o!="object"||o===null)&&(r[i]={}),r=r[i]}r[s[s.length-1]]=a}return n}function et(e,t){return[...new Set([...Object.keys(e),...Object.keys(t)])].filter(n=>!Le(e[n],t[n])).sort()}function _i(e){return JSON.stringify(e,(t,n)=>n!==null&&typeof n=="object"&&!Array.isArray(n)?Object.fromEntries(Object.entries(n).sort(([s],[a])=>s<a?-1:s>a?1:0)):n)}function Le(e,t){return _i(e)===_i(t)}function ce(e){return e===void 0?"(not set)":typeof e=="string"&&e!==""?e:JSON.stringify(e)}function Mt(e,t){let n=Object.keys(t).sort(),s=Math.max(0,...n.map(a=>a.length));for(let a of n)e.out.line(`${a.padEnd(s)} ${ce(t[a])}`)}function tt(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 _e(e,t,n){let s;if(t!==void 0&&t!=="-")try{s=await ym(bm(e.io.cwd,t),"utf8")}catch(a){throw new h("unreadable_file",`${t} could not be read: ${a instanceof Error?a.message:String(a)}`)}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 m(`no ${n} was given`,t===void 0?"pass --file <path>, or pipe it in":void 0);return s}function ge(e,t){try{return JSON.parse(e)}catch(n){throw m(`${t} is not JSON: ${n instanceof Error?n.message:String(n)}`)}}async function $e(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 m(`no ${t} was piped in`,"pipe it in: palbase \u2026 < file");return n}function qt(e){if(e.flags.enable===!0&&e.flags.disable===!0)throw m("--enable and --disable are one or the other");if(e.flags.enable===!0)return!0;if(e.flags.disable===!0)return!1}function Kt(e,t){if(e!==void 0){if(e==="yes"||e==="true")return!0;if(e==="no"||e==="false")return!1;throw m(`--${t} is yes or no`)}}function Q(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 fe(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw m(`--${t} is a whole number`);return n}var _n={enable:{type:"boolean",description:"turn it on"},disable:{type:"boolean",description:"turn it off"}};async function F(e,t,n){try{await n(),e.done(t)}catch(s){if(!(s instanceof x))throw s;e.left(`${t}: refused \u2014 ${s.message}`)}}function te(e,t){return e.filter(n=>n.key.startsWith(t)).map(n=>({...n,name:n.key.slice(t.length)}))}function Aa(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>!(t==null||t===""||Array.isArray(t)&&t.length===0)))}import{randomBytes as Ti}from"node:crypto";import{chmod as Tm,lstat as Em,readFile as Si,rename as Cm,writeFile as $i}from"node:fs/promises";var US=1024*1024;var BS=8*1024;var YS=16*1024,ZS=10*6e4;var Pm=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Am=/^(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 Ra(e){return!Pm.test(e)||Am.test(e)||/_PROXY$/i.test(e)}var Rm=/^(PORT|PALBASE_(URL|PROJECT_ID|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function Ta(e){return Rm.test(e)}import"node:os";import{join as jm,resolve as Om}from"node:path";var D$=10080*60*1e3,ki="# >>> palbase (palbase env:pull writes this block; edit outside it)",Ea="# <<< palbase";async function ja(e,t){let n=await _a(e.studio,t.env_ref),{keys:s}=await e.studio.get(`/environments/${encodeURIComponent(t.env_ref)}/keys`),a=s.find(r=>r.id===n.id);return a&&!a.revoked_at?n:_a(e.studio,t.env_ref,!0)}async function Zn(e,t){let n=await e.studio.get(`/environments/${t.env_ref}`);return{url:Fe(e.cloud,t.env_ref),projectId:n.platform_id,key:await ja(e,t)}}function Qn(e){let t=e.flags.secrets??"development";if(t!=="development"&&t!=="production")throw m("--secrets is development or production");return t}function es(e,t){return t==="production"?`the app reads ${e.project_name} \u203A ${e.env_name}'s production secrets here`:void 0}var ts={type:"string",value:"set",description:"the project's secrets the app reads at boot: development (default) or production"},Pi={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:ts},scope:"environment",async run(e){let t=await y(e),n=Qn(e);(t.env_slug===xe||n==="production")&&await w(e,t,`write a key of ${t.env_slug===xe?"the production environment":`${t.env_slug}`} into a file on this computer`,es(t,n));let s=await Zn(e,t),a=await Oe(e.studio,t.env_ref,"GET",`/secrets/${n}`).then(f=>(f.body.secrets??f.body.items??[]).map(b=>b.name).filter(b=>typeof b=="string"&&!Ra(b)).sort(),()=>[]),r=Om(e.io.cwd,e.flags.file??".env.local"),i=[ki,`# ${Ca(B(t))} \u2014 ${new Date(e.io.now()).toISOString()}`,`# PALBASE_SECRET_KEY is your own: ${Ca(s.key.label)}, good until ${Ca(s.key.expires_at)}.`,`PALBASE_URL=${s.url}`,`PALBASE_PROJECT_ID=${s.projectId}`,`PALBASE_SECRET_KEY=${s.key.key}`,`PALBASE_SECRETS=${n}`,...a.length>0?[`# The ${n} secrets, read at boot \u2014 set one here to override it:`,...a.map(f=>`# ${f}=`)]:[],Ea].join(`
20
+ `),o="";try{if((await Em(r)).isSymbolicLink())throw new h("symlink",`${r} is a link: env:pull writes the key into a file of its own`,{hint:"remove the link, or pass --file <path>"});o=await Si(r,"utf8")}catch(f){if(f.code!=="ENOENT")throw f}let l=o.indexOf(ki),p=o.indexOf(Ea),d=l>=0&&p>l?`${o.slice(0,l)}${i}${o.slice(p+Ea.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 g("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 Uu(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 ${q(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","--",r],{cwd:e.io.cwd})).code===0)throw new h("tracked",`${r} is tracked by git: the key would go into a commit with it`,{hint:`git rm --cached ${r}, then run this again \u2014 or pass --file <path>`});let c=`${r}.${process.pid}.${Date.now()}.tmp`;await $i(c,d,{mode:384,flag:"wx"}),await Tm(c,384),await Cm(c,r);let g=await xm(e,r);e.out.result({file:r,secrets:n,secret_names:a,key:{id:s.key.id,expires_at:s.key.expires_at},gitignore:g},()=>{e.out.line(`Wrote ${r} for ${B(t)}: the address, your own key, and the ${n} secrets' names (${a.length}).`),g==="added"&&e.out.line("Added it to .gitignore."),g==="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 Ca(e){return e.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g," ")}async function xm(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 a=n.stdout.trim(),r=t.startsWith(`${a}/`)?t.slice(a.length+1):t,i=jm(a,".gitignore"),o="";try{o=await Si(i,"utf8")}catch(l){if(l.code!=="ENOENT")throw l}return await $i(i,`${o}${o===""||o.endsWith(`
24
24
  `)?"":`
25
25
  `}# palbase env:pull: your own key
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:Kn},scope:"environment",async run(e){let t=await h(e),n=Nn(e);(t.env_slug===$e||n==="production")&&await b(e,t,`run ${e.args[0]} with a key of ${t.env_slug}`,qn(t,n));let s=await xn(e,t);e.out.note(`${q(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 g("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 g("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 g("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 g("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(ge(e)??[])];for(let s of t)if(!Vu.test(s))throw m(`${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 m(`${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 m(`--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 xa={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:Kn},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 m(`--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 m("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 m(`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=Nn(e);(r.env_slug===$e||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`:""}`,qn(r,o));let l=await xn(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(`${q(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 g("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 Na={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,()=>$t(e,n))}},qa={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"]),Ka={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}]))}},Ma={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 g("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 m(`no provider ${JSON.stringify(t)}`,`the providers: email, phone, ${Et.join(", ")}`);if(e.args.length>1)throw m(`${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 m("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 m("--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 m("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 m(`--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 m('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",$=[...o.browser_clients],S=$.findIndex(P=>P.key===A),C=d!==void 0&&f.every(P=>P==="credential");if(S>=0||!C){let P=S>=0?{...$[S]}:{key:A,enabled:!0,application_key:"app",platform:"web",variant:"release",return_uris:[]},U=ec[n];if(s["client-id"]!==void 0&&(P[U]=s["client-id"]),!P[U])throw m(`the ${n} client ${A} is new: give it --client-id`);if(s["return-uri"]!==void 0&&(P.return_uris=s["return-uri"]),s.application!==void 0&&(P.application_key=s.application),s.variant!==void 0&&(P.variant=s.variant),n==="microsoft")P.directory=s.directory!==void 0?sc(s.directory):P.directory??{mode:"common"};else if(s.directory!==void 0)throw m("--directory is a Microsoft client's");(d!==void 0||s.credential!==void 0||!P.credential_ref)&&(P.credential_ref=u),$[S>=0?S:$.length]=P,l.browser_clients=$,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 m("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 Ua={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 m(`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 g("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 m(`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=ge(e.flags.permissions),i=Tt(e.flags.default,"default");if(r&&a===void 0&&e.flags.description===void 0&&i===void 0)throw m("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 m("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:Ke(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 g("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:Ke(e.flags.timeout,"timeout")??s.timeout_ms,enabled:Rt(e)??s.enabled},a=ze(ac(s),r);if(a.length===0)throw m("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:ge(e.flags.grant),scopes:ge(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 g("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 M(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 M(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 M(s,`delete the role ${a.name}`,async()=>{await c(e,t,"DELETE",`/admin/roles/${_(a.name)}`)}):await M(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 M(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 M(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 M(s,`delete the hook ${o} \u2192 ${l}`,async()=>{await c(e,t,"DELETE",`/admin/hooks/${_(p.id)}`)});else if(p)await M(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 M(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 M(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 M(s,`change the OAuth client ${a.name}`,async()=>{await c(e,t,"PATCH",`/admin/oauth-clients/${_(i.id)}`,u)})}else{let l;await M(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 m(`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 m(`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 m(`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 m(`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 m(`--type is one of ${ti.join(", ")}`);if(e.flags.condition!==void 0&&e.flags["clear-conditions"]===!0)throw m("--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:ge(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 g("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 gc(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 g("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||gc(e,s.body),!s.body.can_publish)throw new g("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 m("--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 g("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 M(s,`delete the flag ${i.name}`,async()=>{await c(e,t,"DELETE",`/flags/system/${_(i.name)}`)}):await M(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 M(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 M(s,"publish the flags draft",async()=>{await Qs(e,t,a,"config:copy")})}};var ci=["email","sms","verify","whatsapp","push"],er=["email","sms","whatsapp"],gi={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"}])})}},mi={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 m(`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 m("the credentials are a JSON object");let a=Rt(e),i=Ke(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 m(`--channel is one of ${er.join(", ")}`);return t}function sr(e,t){let n=e.flags.locale;if(n===void 0)throw m("which locale? pass --locale <locale>");return`/notify/templates/${pn(e)}/${_(t)}/${_(n)}`}function mc(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 m(`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 m('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
- `)[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=pn(e),s=sr(e,e.args[0]),r=mc(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,()=>$t(e,n))}},Pi={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 M(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 M(s,`remove the template override ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())}):await M(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 M(s,`delete the topic ${o.name}`,async()=>{await c(e,t,"DELETE",l,void 0,R())})}else o.change==="only_in_from"?await M(s,`make the topic ${o.name}`,async()=>{await c(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:p},R())}):await M(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 $i={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 m('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 m(`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 M(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 M(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,()=>$t(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 M(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 M(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 M(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 M(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 M(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 m(`${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 m(`--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 Pc(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 xi={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(", ")}])})}},Ni={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}`:""}`)})}},qi={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 m("--public and --private are one or the other");let s=await Pc(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=ge(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 m("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(", ")}.`))}},Ki={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]}.`))}},Mi={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 M(s,`delete the bucket ${r.name}`,async()=>{await c(e,t,"DELETE",jt(r.name))}):await M(s,`declare the bucket ${r.name}`,async()=>{await c(e,t,"PUT",jt(r.name),r.from)})}};var Ot=[ei,Mi,ui,Ai,Ci,Di],Un=[Li,Ii],Ui={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 ${Un.map(e=>e.name).join(",")} when named`}};async function Gi(e){if(e.global.env!==void 0)throw m("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 m("name both environments: --from <env> --to <env>");let s=await qe(e.studio),r;if(e.global.project!==void 0)r=gt(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new g("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 g("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 m("--from and --to name the same environment");return{from:i,to:o}}function Bi(e){let t=ge(e.flags.only);if(t===void 0)return Ot;let n=[...Ot,...Un];for(let s of t)if(!n.some(r=>r.name===s))throw m(`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 Mn(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} ${Mn(o.from)} \xB7 ${s} ${Mn(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${Mn(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${Mn(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 $c={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:Ui,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:Ui,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=[Na,qa,Ka,Ma,Ga,Ua,Ba,Ja,za,Fa,Va,Ha,Xa,Ya,Za,Qa,xi,Ni,qi,Ki,ri,ai,ii,oi,pi,di,li,gi,mi,hi,fi,yi,bi,wi,vi,_i,Si,ki,Pi,$i,Ri,Ti,Ei,ji,Oi,$c,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 m(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 g("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new g("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 m("--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 g("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 m("--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 m("--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 m("--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 g("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 g("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 qe(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=gt(await qe(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 g("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 g("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 m("--with-cloud goes with --from <env>");let r=s===void 0?[]:(await qe(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 g("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,...Un]: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 g("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=ue(n);if(e.out.note(`${q(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new g("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 g("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 ${q(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 qe(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 ${q(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(q(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 m("--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 qe(e.studio);if(e.global.project!==void 0)return gt(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new g("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 g("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 m(`${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=gt(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 g("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 g("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 m(`${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 g("no_machine","the environment has no machine yet",{hint:"make one with palbase machines:create"}):m("which machine? name it",`the environment's machines: ${s.map(i=>i.name).join(", ")}`)}var go={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??""}`}]))}},mo={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 m(`a setting is K=V, not ${JSON.stringify(n)}`);t[n.slice(0,s)]=n.slice(s+1)}return t}function mt(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw m(`--${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 m("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!r)throw m("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 g("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=mt(e.flags.replicas,"replicas"),p=mt(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 m("--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=mt(e.flags.replicas,"replicas"),a=mt(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 m("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 m("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 Po={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 g("no_such_setting",`the machine is not started with ${n}`);delete t[n]}return e.args.map(n=>`unset ${n}`)})}},$o={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
- `)[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 g("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=mt(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 g("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 g("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 g("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 g("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 g("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 g("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 g("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 g("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 g("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 m(`--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}])})}},xo={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 No(e){if(e!==void 0)return String(e).split(",").map(t=>t.trim()).filter(t=>t!=="")}var qo={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=No(e.flags.allow);s&&(n.allow=s);let r=mt(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:gr(o.owner),app:gr(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 gr(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 Ko={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 m("--size is one of palbase databases:sizes");s.size=r}let a=No(e.flags.allow);a&&(s.allow=a);let i=mt(e.flags["backup-days"],"backup-days");if(i!==void 0&&(s.backup_days=i),Object.keys(s).length===0)throw m("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 g("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 g("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 g("timeout",`${n} was still moving to ${s} after 30 minutes; it serves on meanwhile`,{hint:`palbase databases:get ${n}`})}var Mo={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 m("--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 g("no_password","the platform answered no password");await e.state.keepDatabasePasswords(n.env_ref,e.args[0],{[t]:r.password}),await xc(e,n,e.args[0]),e.out.result(e.flags["show-password"]===!0?r:gr(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 xc(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 g("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 Uo={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 m("--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 mr(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 g("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 g("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=mr("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=mr("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=mr("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 Nc(e,s.client_id),i=await qc(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 Nc(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 g("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 g("access_denied","the sign-in was denied on GitHub"):new g("github_denied",`GitHub's sign-in ended: ${l.error??o.status}`)}}throw new g("expired_code","GitHub's code expired before it was entered",{hint:"run the command again"})}async function qc(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 g("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 g("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 g("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 m("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 g("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 Ym}from"node:crypto";import{lstat as Zm,mkdir as Qm,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 Uc(e){return e.replace(/\s+/g," ").trim()}var cS=String.raw`
26
+ /${r}
27
+ `),"added"}var Ai={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:ts},scope:"environment",async run(e){let t=await y(e),n=Qn(e);(t.env_slug===xe||n==="production")&&await w(e,t,`run ${e.args[0]} with a key of ${t.env_slug}`,es(t,n));let s=await Zn(e,t);e.out.note(`${B(t)}: ${e.args.join(" ")}`);let a=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(a.code===127)throw new h("not_found",`${e.args[0]}: no such program`);return a.code}};var Lm=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/,Ri=20;async function Dm(e,t,n,s){if(n.length===0)return{};let a=n.map(i=>({email:`${i.toLowerCase()}.${s}@test.invalid`,password:Ti(18).toString("base64url")})),r=await u(e,t,"POST","/admin/test-users",{users:a,with_tokens:!0},R());if(r.users.length!==n.length)throw new h("unexpected_answer",`auth made ${r.users.length} test users of the ${n.length} asked for`);return Object.fromEntries(n.map((i,o)=>{let l=r.users[o];return[i,{id:l.user_id,email:l.email??a[o].email,password:a[o].password,...l.access_token?{accessToken:l.access_token}:{}}]}))}async function Im(e,t,n){let s=[];for(let a of Object.values(n))try{await u(e,t,"DELETE",`/admin/test-users/${v(a.id)}`)}catch(r){r.status!==404&&s.push(a.id)}return s}async function Nm(e,t,n){if(e.out.note(`${t}'s test command, as the platform holds it: ${Mm(n)}`),e.global.yes)return;if(!e.interactive)throw new h("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 h("cancelled","nothing was run")}function Mm(e){return N(e).replace(/\p{Cf}/gu,t=>`\\u${t.codePointAt(0).toString(16).padStart(4,"0")}`)}function Oa(e){let t=[...new Set(Q(e)??[])];for(let s of t)if(!Lm.test(s))throw m(`${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 a=n.get(s.toLowerCase());if(a!==void 0)throw m(`${JSON.stringify(a)} 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>Ri)throw m(`--users names ${t.length}; a run makes ${Ri} at most`);return t}var Ei={SIGHUP:1,SIGINT:2,SIGTERM:15};function qm(){let e,t=Object.keys(Ei).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 Ci={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:ts},scope:"environment",async run(e){let t=Oa(e.flags.users),n=e.flags["base-url"];if(n!==void 0&&!/^https?:\/\/[^\s/]+/.test(n))throw m(`--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 m("which tests? give their command after --, or --machine <name> to run its own","palbase test -- bun test");let a=await y(e),r=e.args.length>0?{cmd:e.args[0],args:e.args.slice(1)}:void 0;if(s!==void 0){let f=await u(e,a,"GET",`/machines/${v(s)}`);if(e.flags.users===void 0&&(t=f.test_users??[]),r===void 0){if(!f.test_command)throw m(`the machine ${s} runs no tests`,`give their command after --, or set it: palbase machines:update ${s} --test-command "bun test"`);await Nm(e,s,f.test_command),r={cmd:"sh",args:["-c",f.test_command]}}}let i=r,o=Qn(e);(a.env_slug===xe||o==="production")&&await w(e,a,`run ${i.cmd} with a key of ${a.env_slug}${t.length?` and ${t.length} test user(s) made there`:""}`,es(a,o));let l=await Zn(e,a),p=Ti(4).toString("hex"),d=t.length>0?qm():void 0,c=0;try{let f=await Dm(e,a,t,p);t.length>0&&e.out.note(`${B(a)}: test users ${t.join(", ")} made for this run (${p})`);try{if(d?.got()===void 0){let b=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(f),...n!==void 0?{PALBASE_TEST_BASE_URL:n}:{}},inherit:!0});if(b.code===127)throw new h("not_found",`${i.cmd}: no such program`);c=b.code}}finally{if(t.length>0){let b=await Im(e,a,f);b.length>0?e.out.warn(`test users not deleted: ${b.join(", ")} \u2014 palbase auth:test-users:delete <id> deletes one`):e.out.note(`the run's ${t.length} test user(s) deleted`)}}}finally{d?.release()}let g=d?.got();return g===void 0?c:128+Ei[g]}};var ji={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 y(e),{body:n}=await ve(e,t,"/admin/auth-settings");e.out.result(n,()=>Mt(e,n))}},Oi={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 y(e),n=ct(e.args),{body:s,etag:a}=await ve(e,t,"/admin/auth-settings"),r=ut(s,n),i=et(s,r);if(i.length===0)return tt(e,"the auth settings",s);await w(e,t,`set the auth settings ${i.join(", ")}`);let o=await u(e,t,"PUT","/admin/auth-settings",Object.fromEntries(i.map(l=>[l,r[l]])),de(a));e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${ce(s[l])} \u2192 ${ce(o[l])}`)})}},Ut=["google","apple","github","microsoft"],Km={google:"web_client_id",apple:"services_id",microsoft:"application_id",github:"oauth_client_id"},Um=new Set(["organizations","consumers","common"]),xi={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 y(e),[n,s]=await Promise.all([u(e,t,"GET","/admin/providers"),u(e,t,"GET","/admin/social-auth")]),a=[...n.providers.map(r=>({provider:r.id,on:r.enabled,detail:r.settings?`codes by ${r.settings.sender??"sms"}, ${r.settings.otp_ttl_seconds}s, ${r.settings.otp_max_attempts} tries, sign-up ${r.settings.allow_signups?"on":"off"}`:"",secrets:""})),...Ut.map(r=>{let i=s.providers[r]??{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:r,on:i.enabled,detail:o.length===0?"no client":o.join(", "),secrets:s.credentials.filter(l=>l.provider===r).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(a,[{header:"provider",value:r=>r.provider},{header:"on",value:r=>r.on?"yes":"no"},{header:"clients / settings",value:r=>r.detail},{header:"secrets",value:r=>r.secrets}]))}},Li={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:{..._n,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 y(e);if(t==="email")throw new h("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 zm(e,n,e.args.slice(1));if(!Ut.includes(t))throw m(`no provider ${JSON.stringify(t)}`,`the providers: email, phone, ${Ut.join(", ")}`);if(e.args.length>1)throw m(`${t} takes flags, not settings: palbase help auth:providers:set`);await Bm(e,n,t)}};async function zm(e,t,n){let s=qt(e),a=ct(n);if(s===void 0&&a.length===0)throw m("nothing to change: --enable, --disable or setting=value","palbase help auth:providers:set");let r=await u(e,t,"GET","/admin/providers/phone/settings"),i=ut(r,a),o=et(r,i);if(s===void 0&&o.length===0)return tt(e,"the phone settings",r);await w(e,t,`phone sign-in: ${[...s===void 0?[]:[s?"turn it on":"turn it off"],...o.map(d=>`set ${d}`)].join(", ")}`);let l=o.length>0?await u(e,t,"PUT","/admin/providers/phone/settings",i):r,p=s===void 0?void 0:await u(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 d of o)e.out.line(`${d}: ${ce(r[d])} \u2192 ${ce(l[d])}`)})}function Gm(e){if(Um.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 m("--directory is a tenant id, organizations, consumers or common")}async function Bm(e,t,n){let s=e.flags,a=qt(e),r=await ve(e,t,"/admin/social-auth"),i=r.etag,o=r.body.providers[n]??{enabled:!1,browser_clients:[],native_clients:[]},l={},p=[],d=s.credential??n,c;if(s["secret-stdin"]===!0){if(n==="apple"&&(s["team-id"]===void 0||s["key-id"]===void 0))throw m("Apple's signing key goes with --team-id and --key-id");c=await $e(e,n==="apple"?"Apple's .p8 signing key":`${n}'s client secret`),p.push(`keep its secret as ${d}`)}let g=["client","client-id","return-uri","application","variant","directory","credential"].filter(E=>s[E]!==void 0);if(s["clients-file"]!==void 0){if(g.length>0)throw m(`--clients-file and --${g[0]} are one or the other`);let E=ge(await _e(e,s["clients-file"],"the clients"),"the clients file");if(typeof E!="object"||E===null||E.browser_clients===void 0&&E.native_clients===void 0)throw m('the clients file is {"browser_clients": [\u2026], "native_clients": [\u2026]}');l.browser_clients=E.browser_clients??o.browser_clients,l.native_clients=E.native_clients??o.native_clients,p.push(`set its clients from ${s["clients-file"]}`)}else if(g.length>0||c!==void 0){let E=s.client??"web",k=[...o.browser_clients],$=k.findIndex(A=>A.key===E),O=c!==void 0&&g.every(A=>A==="credential");if($>=0||!O){let A=$>=0?{...k[$]}:{key:E,enabled:!0,application_key:"app",platform:"web",variant:"release",return_uris:[]},L=Km[n];if(s["client-id"]!==void 0&&(A[L]=s["client-id"]),!A[L])throw m(`the ${n} client ${E} is new: give it --client-id`);if(s["return-uri"]!==void 0&&(A.return_uris=s["return-uri"]),s.application!==void 0&&(A.application_key=s.application),s.variant!==void 0&&(A.variant=s.variant),n==="microsoft")A.directory=s.directory!==void 0?Gm(s.directory):A.directory??{mode:"common"};else if(s.directory!==void 0)throw m("--directory is a Microsoft client's");(c!==void 0||s.credential!==void 0||!A.credential_ref)&&(A.credential_ref=d),k[$>=0?$:k.length]=A,l.browser_clients=k,p.push(`${$>=0?"change":"add"} its browser client ${E}`)}}if(a!==void 0&&(l.enabled=a,p.push(a?"turn it on":"turn it off")),p.length===0)throw m("nothing to change: give a flag","palbase help auth:providers:set");await w(e,t,`${n} sign-in: ${p.join(", ")}`);let f=r.body;if(c!==void 0){let E=n==="apple"?{provider:n,kind:"apple_signing_key",team_id:s["team-id"],key_id:s["key-id"],private_key:c}:{provider:n,kind:"oauth_client_secret",client_secret:c};i=(await we(e,t,"PUT",`/admin/social-auth/credentials/${v(d)}`,E,de(i))).etag??i}Object.keys(l).length>0?f=await u(e,t,"PATCH",`/admin/social-auth/providers/${v(n)}`,l,de(i)):f=await u(e,t,"GET","/admin/social-auth");let b=f.providers[n],P=f.credentials.filter(E=>E.provider===n);e.out.result({provider:n,...b,credentials:P},()=>{e.out.line(`${n} sign-in is ${b.enabled?"on":"off"}: ${b.browser_clients.length} browser client(s), ${b.native_clients.length} native.`);for(let E of P)e.out.line(` secret ${E.key}: ${E.configured?"set":"not set"}`)})}var Di={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 y(e),{body:s,etag:a}=await ve(e,n,"/admin/social-auth");if(!s.credentials.some(r=>r.key===t))throw m(`the environment holds no provider secret ${JSON.stringify(t)}`,"palbase auth:providers:list names them");await w(e,n,`delete the provider secret ${t}`),await u(e,n,"DELETE",`/admin/social-auth/credentials/${v(t)}`,void 0,de(a)),e.out.result({deleted:t},()=>e.out.line(`Deleted the provider secret ${t}.`))}},Ii={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 y(e);if(t==="email")throw new h("always_on","email sign-in cannot be turned off");if(t==="phone"){await w(e,n,"turn phone sign-in off");let r=await u(e,n,"POST","/admin/providers/phone",{enabled:!1});e.out.result(r,()=>e.out.line("phone sign-in is off."));return}if(!Ut.includes(t))throw m(`no provider ${JSON.stringify(t)}`,`the providers: phone, ${Ut.join(", ")}`);let{etag:s}=await ve(e,n,"/admin/social-auth");await w(e,n,`turn ${t} sign-in off`);let a=await u(e,n,"PATCH",`/admin/social-auth/providers/${v(t)}`,{enabled:!1},de(s));e.out.result({provider:t,...a.providers[t]},()=>e.out.line(`${t} sign-in is off; its clients and secrets stay.`))}},Ni={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 y(e),{roles:n}=await u(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}])})}},Mi={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 y(e),n=e.args[0],{roles:s}=await u(e,t,"GET","/admin/roles"),a=s.find(d=>d.name===n),r=Q(e.flags.permissions),i=Kt(e.flags.default,"default");if(a&&r===void 0&&e.flags.description===void 0&&i===void 0)throw m("nothing to change: give a flag","palbase help auth:roles:set");let o={permissions:r??a?.permissions??[],description:e.flags.description??a?.description??"",is_default:i??a?.is_default??!1};await w(e,t,`${a?"change":"make"} the role ${n}${a?.user_count?` (${a.user_count} users hold it)`:""}`);let l=await u(e,t,"PUT",`/admin/roles/${v(n)}`,o),p=l.roles.find(d=>d.name===n);e.out.result(p??l,()=>e.out.line(`${a?"Changed":"Made"} the role ${n}: ${(p?.permissions??o.permissions).join(", ")||"no permission"}${p?.is_default?", given to new users":""}.`))}},qi={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 y(e),n=e.args[0];await w(e,t,`delete the role ${n}${e.flags.force===!0?", taking it from the users who hold it":""}`),await u(e,t,"DELETE",`/admin/roles/${v(n)}${e.flags.force===!0?"?confirm=true":""}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the role ${n}.`))}},Ki={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)"}},Ui={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 y(e),{hooks:n}=await u(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"}])})}},zi={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:{...Ki,disable:{type:"boolean",description:"register it turned off"}},scope:"environment",async run(e){let t=await y(e);if(e.flags.event===void 0||e.flags.url===void 0)throw m("a hook needs --event and --url");let n=Aa({event:e.flags.event,url:e.flags.url,failure_mode:e.flags["failure-mode"],timeout_ms:fe(e.flags.timeout,"timeout"),...e.flags.disable===!0?{enabled:!1}:{}});await w(e,t,`register a hook on ${String(n.event)} calling ${String(n.url)}`);let s=await u(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}`)})}},Gi={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:{...Ki,..._n},scope:"environment",async run(e){let t=await y(e),{hooks:n}=await u(e,t,"GET","/admin/hooks"),s=n.find(o=>o.id===e.args[0]);if(!s)throw new h("no_such_hook",`no hook ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:hooks:list shows them"});let a={event:e.flags.event??s.event,url:e.flags.url??s.url,failure_mode:e.flags["failure-mode"]??s.failure_mode,timeout_ms:fe(e.flags.timeout,"timeout")??s.timeout_ms,enabled:qt(e)??s.enabled},r=et(Jm(s),a);if(r.length===0)throw m("nothing to change: give a flag","palbase help auth:hooks:update");await w(e,t,`change the hook ${s.id}: ${r.join(", ")}`);let i=await u(e,t,"PUT",`/admin/hooks/${v(s.id)}`,a);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 Jm(e){return{event:e.event,url:e.url,failure_mode:e.failure_mode,timeout_ms:e.timeout_ms,enabled:e.enabled}}var Bi={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 y(e);await w(e,t,`delete the hook ${e.args[0]}`),await u(e,t,"DELETE",`/admin/hooks/${v(e.args[0])}`),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the hook ${e.args[0]}.`))}},Ji={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 y(e),{clients:n}=await u(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(", ")}])})}},Fi={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 y(e),n=Aa({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:Q(e.flags.grant),scopes:Q(e.flags.scope)});await w(e,t,`register the OAuth client ${e.args[0]}`);let s=await u(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}`)})}},Wi={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 y(e),{clients:n}=await u(e,t,"GET","/admin/oauth-clients"),s=n.find(a=>a.id===e.args[0]||a.client_id===e.args[0]);if(!s)throw new h("no_such_client",`no OAuth client ${JSON.stringify(e.args[0])}`,{hint:"palbase auth:oauth-clients:list shows them"});await w(e,t,`delete the OAuth client ${s.client_name} (${s.client_id})`),await u(e,t,"DELETE",`/admin/oauth-clients/${v(s.id)}`),e.out.result({deleted:s.id},()=>e.out.line(`Deleted the OAuth client ${s.client_name}.`))}};function Fm(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 Vi={name:"auth",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([u(e,t,"GET","/admin/auth-settings"),u(e,t,"GET","/admin/providers"),u(e,t,"GET","/admin/social-auth"),u(e,t,"GET","/admin/roles"),u(e,t,"GET","/admin/hooks"),u(e,t,"GET","/admin/oauth-clients")]),l={};for(let[d,c]of Object.entries(n))l[`settings.${d}`]=c;let p=s.providers.find(d=>d.id==="phone");p&&(l["phone.enabled"]=p.enabled,p.settings&&(l["phone.settings"]=p.settings));for(let d of Ut){let c=a.providers[d];c&&(l[`social.${d}`]={enabled:c.enabled,browser_clients:c.browser_clients,native_clients:c.native_clients})}for(let d of a.credentials)l[`secrets.${d.key}`]={provider:d.provider,kind:d.kind,value:d.configured?"set":"not set"};for(let d of r.roles)l[`roles.${d.name}`]={description:d.description,is_default:d.is_default,permissions:[...d.permissions].sort()};for(let d of i.hooks)l[`hooks.${d.event} ${d.url}`]={failure_mode:d.failure_mode,timeout_ms:d.timeout_ms,enabled:d.enabled};for(let d of o.clients)l[`oauth_clients.${d.client_name}`]=Fm(d);return l},async copy(e,t,n,s){await Wm(e,t,te(n,"settings."),s);for(let r of te(n,"phone."))r.name==="enabled"&&await F(s,`turn phone sign-in ${r.from?"on":"off"}`,async()=>{await u(e,t,"POST","/admin/providers/phone",{enabled:r.from===!0})}),r.name==="settings"&&r.from!==void 0&&await F(s,"set the phone code settings",async()=>{await u(e,t,"PUT","/admin/providers/phone/settings",r.from)});await Vm(e,t,te(n,"social."),te(n,"secrets."),s);let a=te(n,"roles.").sort((r,i)=>+(r.from?.is_default===!0)-+(i.from?.is_default===!0));for(let r of a)r.from===void 0?await F(s,`delete the role ${r.name}`,async()=>{await u(e,t,"DELETE",`/admin/roles/${v(r.name)}`)}):await F(s,`set the role ${r.name}`,async()=>{await u(e,t,"PUT",`/admin/roles/${v(r.name)}`,r.from)});await Hm(e,t,te(n,"hooks."),s),await Xm(e,t,te(n,"oauth_clients."),s)}};async function Wm(e,t,n,s){let a=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(a.length===0)return;let{etag:r}=await ve(e,t,"/admin/auth-settings");await F(s,`set the auth settings ${a.map(i=>i.name).join(", ")}`,async()=>{await u(e,t,"PUT","/admin/auth-settings",Object.fromEntries(a.map(i=>[i.name,i.from])),de(r))})}async function Vm(e,t,n,s,a){for(let l of s){let p=l.from;p===void 0?a.left(`secret ${l.name}: only in the target, left as it is (delete it with palbase auth:providers:secret:delete ${l.name})`):a.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 r=await ve(e,t,"/admin/social-auth"),i=r.etag,o=new Set(r.body.credentials.filter(l=>l.configured).map(l=>l.key));for(let l of n){let p=l.from,d=[...new Set(p.browser_clients.map(c=>c.credential_ref).filter(c=>typeof c=="string"&&!o.has(c)))];if(d.length>0){a.left(`${l.name} sign-in: its clients use the secret ${d.join(", ")}, which the target does not hold \u2014 set it first (auth:providers:set ${l.name} --secret-stdin --credential ${d[0]})`);continue}await F(a,`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/${v(l.name)}`,p,de(i))).etag??i})}}async function Hm(e,t,n,s){if(n.length===0)return;let{hooks:a}=await u(e,t,"GET","/admin/hooks");for(let r of n){let i=r.name.indexOf(" "),o=r.name.slice(0,i),l=r.name.slice(i+1),p=a.find(d=>d.event===o&&d.url===l);if(r.from===void 0)p&&await F(s,`delete the hook ${o} \u2192 ${l}`,async()=>{await u(e,t,"DELETE",`/admin/hooks/${v(p.id)}`)});else if(p)await F(s,`change the hook ${o} \u2192 ${l}`,async()=>{await u(e,t,"PUT",`/admin/hooks/${v(p.id)}`,{event:o,url:l,...r.from})});else{let d;await F(s,`register the hook ${o} \u2192 ${l}`,async()=>{d=(await u(e,t,"POST","/admin/hooks",{event:o,url:l,...r.from},R())).secret}),d&&s.shown(`the hook ${o} \u2192 ${l} signs with`,d)}}}async function Xm(e,t,n,s){if(n.length===0)return;let{clients:a}=await u(e,t,"GET","/admin/oauth-clients");for(let r of n){let i=a.find(l=>l.client_name===r.name),o=r.from;if(o===void 0)i&&await F(s,`delete the OAuth client ${r.name}`,async()=>{await u(e,t,"DELETE",`/admin/oauth-clients/${v(i.id)}`)});else if(i)if(!Le(o.is_public,i.is_public)||o.client_id!==void 0&&o.client_id!==i.client_id)s.left(`OAuth client ${r.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,...d}=o;await F(s,`change the OAuth client ${r.name}`,async()=>{await u(e,t,"PATCH",`/admin/oauth-clients/${v(i.id)}`,d)})}else{let l;await F(s,`register the OAuth client ${r.name}`,async()=>{l=await u(e,t,"POST","/admin/oauth-clients",{client_name:r.name,...o},R())}),l?.client_secret&&s.shown(`the OAuth client ${r.name} (${l.client_id}) has the secret`,l.client_secret)}}}var Hi=["bool","number","string","object"];async function ht(e,t){return(await u(e,t,"GET","/flags/templates/capabilities")).publish_enabled}async function mt(e,t,n){let s=await ve(e,t,"/flags/templates/draft");if(s.body.draft||!n)return{draft:s.body.draft,etag:s.etag};let a=await ve(e,t,"/flags/templates/active"),r=await we(e,t,"POST","/flags/templates/draft",{},de(a.etag));return{draft:r.body.draft,etag:r.etag}}function Xi(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 Ym(e){return typeof e=="boolean"?"bool":typeof e=="number"?"number":typeof e=="string"?"string":"object"}function Yi(e,t){switch(t){case"bool":if(e==="true"||e==="false")return e==="true";throw m(`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 m(`a number flag's value is a number, not ${JSON.stringify(e)}`);return n}case"string":{let n=Ve(e);return typeof n=="string"?n:e}default:{let n=Ve(e);if(typeof n!="object"||n===null)throw m(`an object flag's value is a JSON object or list, not ${JSON.stringify(e)}`);return n}}}function Zm(e,t){let n=e.lastIndexOf("=>");if(n<=0)throw m(`a condition is "<expression> => <value>", not ${JSON.stringify(e)}`,`--condition "client.platform == 'ios' => true"`);return{when:e.slice(0,n).trim(),value:Yi(e.slice(n+2).trim(),t)}}var Zi={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 y(e),n=await ht(e,t),s,a,r;if(n){let[i,o]=await Promise.all([u(e,t,"GET","/flags/templates/active"),mt(e,t,!1)]);s=Object.entries(i.definitions).map(([l,p])=>({key:l,...p})),a=i.version,r=o.draft}else s=await u(e,t,"GET","/flags/system");s.sort((i,o)=>i.key<o.key?-1:1),e.out.result({publishing:n,...a?{version:a}:{},flags:s,...r?{draft:{revision:r.revision,base_version:r.base_version}}:{}},()=>{n&&e.out.line(`Version ${a} is live${r?`; a draft (from ${r.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??""}])})}},Qi={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 y(e),[n,s]=e.args,a=e.flags.type;if(a!==void 0&&!Hi.includes(a))throw m(`--type is one of ${Hi.join(", ")}`);if(e.flags.condition!==void 0&&e.flags["clear-conditions"]===!0)throw m("--condition and --clear-conditions are one or the other");let r=await ht(e,t),i,o;r?(o=await mt(e,t,!1),i=o.draft?.definitions[n]??(await u(e,t,"GET","/flags/templates/active")).definitions[n]):i=(await u(e,t,"GET","/flags/system")).find(c=>c.key===n);let l=a??i?.value_type??Ym(Ve(s)),p=e.flags["clear-conditions"]===!0?[]:e.flags.condition!==void 0?e.flags.condition.map(c=>Zm(c,l)):i?.value_type===l?i.conditions??[]:[],d=Xi({value_type:l,value:Yi(s,l),description:e.flags.description??i?.description,variants:Q(e.flags.variants)??(i?.value_type===l?i.variants:void 0),conditions:p});if(await w(e,t,r?`put ${n} = ${ce(d.value)} into the flags draft`:`set the flag ${n} = ${ce(d.value)}, live at once`),r){o?.draft||(o=await mt(e,t,!0));let c=await we(e,t,"PUT",`/flags/templates/draft/parameters/${v(n)}`,d,de(o.etag));e.out.result({key:n,...d,draft:{revision:c.body.draft?.revision}},()=>e.out.line(`${n} = ${JSON.stringify(d.value)} is in the draft: palbase flags:publish makes it live.`))}else{let c=await u(e,t,"PUT",`/flags/system/${v(n)}`,d);e.out.result(c,()=>e.out.line(`${n} = ${JSON.stringify(c.value)} (${c.value_type}) is live.`))}}},eo={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 y(e),n=e.args[0],s=await ht(e,t);if(await w(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 a=await mt(e,t,!0);await u(e,t,"DELETE",`/flags/templates/draft/parameters/${v(n)}`,void 0,de(a.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 u(e,t,"DELETE",`/flags/system/${v(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the flag ${n}.`))}};function xa(){return new h("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 Qm(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 La(e,t,n,s){return u(e,t,"POST","/flags/templates/publish",s===""?{}:{note:s},{...de(n),...R()})}var to={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 y(e);if(!await ht(e,t))throw xa();if(!(await mt(e,t,!1)).draft)throw new h("no_draft","there is no draft to publish",{hint:"palbase flags:set puts a change into one"});let s=await ve(e,t,"/flags/templates/draft/review");if(e.out.json||Qm(e,s.body),!s.body.can_publish)throw new h("cannot_publish",`the draft cannot be published: ${s.body.issues.map(r=>r.message).join("; ")}`);if(s.body.changes.length===0){e.out.result({unchanged:!0,review:s.body},()=>e.out.line("Nothing to publish."));return}await w(e,t,`publish the flags draft (${s.body.changes.length} change(s)) as the version after ${s.body.active_version}`);let a=await La(e,t,s.etag,e.flags.note??"");e.out.result({...a,review:s.body},()=>e.out.line(a.unchanged?"The draft changed nothing; nothing was published.":`Published version ${a.template.version}.`))}},no={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 y(e);if(await ht(e,t)){e.out.result({publishing:!0,changed:!1},()=>e.out.line("The environment's flags are published through drafts already."));return}await w(e,t,"turn publishing on for the flags \u2014 what is live becomes version 1, and it cannot be turned off");let n=await u(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.`))}},so={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 y(e);if(!await ht(e,t))throw xa();let n=Number(e.flags.limit??20);if(!Number.isInteger(n)||n<=0)throw m("--limit is a whole number");let s=await u(e,t,"GET",`/flags/templates/versions?limit=${n}`);e.out.result(s,()=>e.out.table(s.versions,[{header:"",value:a=>a.version===s.active_version?"*":""},{header:"version",value:a=>a.version},{header:"kind",value:a=>`${a.kind}${a.restored_from?` of ${a.restored_from}`:""}`},{header:"published",value:a=>a.created_at},{header:"by",value:a=>a.actor},{header:"note",value:a=>a.note}]))}},ao={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 y(e),n=e.args[0];if(!await ht(e,t))throw xa();if((await mt(e,t,!1)).draft)throw new h("draft_in_progress","a draft is in progress: publish it (flags:publish) before a rollback");let s=await ve(e,t,"/flags/templates/active");await w(e,t,`make version ${n} of the flags live again (now ${s.body.version})`);let a=await we(e,t,"POST",`/flags/templates/restore/${v(n)}`,{},de(s.etag)),r=await La(e,t,a.etag,e.flags.note??`rollback to ${n}`);e.out.result(r,()=>e.out.line(r.unchanged?`Version ${n} is what is live already.`:`Version ${n} is live again, as version ${r.template.version}.`))}},ro={name:"flags",async read(e,t){let n=await u(e,t,"GET","/flags/system"),s={};for(let a of n)s[`flags.${a.key}`]=Xi(a);return s},async copy(e,t,n,s){let a=te(n,"flags.");if(a.length===0)return;if(!await ht(e,t)){for(let i of a)i.from===void 0?await F(s,`delete the flag ${i.name}`,async()=>{await u(e,t,"DELETE",`/flags/system/${v(i.name)}`)}):await F(s,`set the flag ${i.name}`,async()=>{await u(e,t,"PUT",`/flags/system/${v(i.name)}`,i.from)});return}if((await mt(e,t,!1)).draft){s.left("flags: the target has a draft in progress \u2014 publish it (flags:publish) and copy again");return}let r=(await mt(e,t,!0)).etag;for(let i of a)await F(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/${v(i.name)}`;r=(i.from===void 0?await we(e,t,"DELETE",o,void 0,de(r)):await we(e,t,"PUT",o,i.from,de(r))).etag??r});await F(s,"publish the flags draft",async()=>{await La(e,t,r,"config:copy")})}};var io=["email","sms","verify","whatsapp","push"],Da=["email","sms","whatsapp"],oo={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 y(e),{providers:n}=await u(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"}])})}},lo={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"},..._n},scope:"environment",async run(e){let[t,n]=e.args;if(!io.includes(t))throw m(`the channels: ${io.join(", ")}`);let s=await y(e),a=ge(await $e(e,`${n}'s credentials (JSON)`),"the credentials");if(typeof a!="object"||a===null||Array.isArray(a))throw m("the credentials are a JSON object");let r=qt(e),i=fe(e.flags.priority,"priority");await w(e,s,`send ${t} through ${n}`);let o=await u(e,s,"PUT",`/notify/providers/${v(t)}/${v(n)}`,{credentials:a,...r!==void 0?{active:r}:{},...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.`))}},po={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 y(e);await w(e,s,`forget the ${t} provider ${n} and its credentials`),await u(e,s,"DELETE",`/notify/providers/${v(t)}/${v(n)}`,void 0,R()),e.out.result({removed:{channel:t,provider:n}},()=>e.out.line(`${t} no longer goes through ${n}.`))}},Na={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 kn(e){let t=e.flags.channel??"email";if(!Da.includes(t))throw m(`--channel is one of ${Da.join(", ")}`);return t}function Ma(e,t){let n=e.flags.locale;if(n===void 0)throw m("which locale? pass --locale <locale>");return`/notify/templates/${kn(e)}/${v(t)}/${v(n)}`}function eh(e,t){if(e==="sms"){let s=th(t);return typeof s=="object"&&s!==null&&typeof s.body=="string"?{body:s.body}:{body:t.replace(/\n$/,"")}}let n=ge(t,`the ${e} template`);if(e==="email"){for(let s of["subject","html_body","text_body"])if(typeof n[s]!="string")throw m(`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 m('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 th(e){try{return JSON.parse(e)}catch{return e}}var co={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 y(e),n=e.flags.channel===void 0?Da:[kn(e)],s=await Promise.all(n.map(r=>u(e,t,"GET",`/notify/templates/${r}`))),a=n.flatMap((r,i)=>s[i].templates.map(o=>({channel:r,...o})));e.out.result({templates:a},()=>e.out.table(a,[{header:"channel",value:r=>r.channel},{header:"slug",value:r=>r.slug},{header:"locale",value:r=>r.locale},{header:"yours",value:r=>"customized"in r?r.customized?"yes":"default":"yes"},{header:"subject / text / template",value:r=>"subject"in r?r.subject:"body"in r?r.body.split(`
28
+ `)[0].slice(0,60):`${r.name} (${r.language})`}]))}},uo={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:Na,scope:"environment",async run(e){let t=await y(e),n=await u(e,t,"GET",Ma(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 a of n[s].split(`
29
+ `))e.out.line(a)}})}},mo={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:{...Na,file:{type:"string",value:"path",description:"the template (default: stdin)"}},scope:"environment",async run(e){let t=await y(e),n=kn(e),s=Ma(e,e.args[0]),a=eh(n,await _e(e,e.flags.file,"the template"));await w(e,t,`override the ${n} template ${e.args[0]} (${e.flags.locale})`);let r=await u(e,t,"PUT",s,a,R());e.out.result(r,()=>e.out.line(`The ${n} template ${e.args[0]} (${e.flags.locale}) is yours now${typeof r.subject=="string"?`: "${r.subject}"`:""}.`))}},ho={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:Na,scope:"environment",async run(e){let t=await y(e),n=Ma(e,e.args[0]);await w(e,t,`remove the ${kn(e)} template ${e.args[0]} (${e.flags.locale})`),await u(e,t,"DELETE",n,void 0,R()),e.out.result({deleted:{channel:kn(e),slug:e.args[0],locale:e.flags.locale}},()=>e.out.line(`Removed the override of ${e.args[0]} (${e.flags.locale}).`))}};async function Ia(e,t){let n=[],s;do{let a=await u(e,t,"GET",`/notify/topics?limit=100${s?`&cursor=${v(s)}`:""}`);n.push(...a.topics),s=a.next_cursor}while(s);return n}var go={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 y(e),n=await Ia(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"}])})}},fo={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 y(e),n=Kt(e.flags["self-subscribe"],"self-subscribe");await w(e,t,`make the topic ${e.args[0]}`);let s=await u(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":""}.`))}},yo={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 y(e);await w(e,t,`delete the topic ${e.args[0]} and who follows it`),await u(e,t,"DELETE",`/notify/topics/${v(e.args[0])}`,void 0,R()),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the topic ${e.args[0]}.`))}},bo={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 y(e),{body:n}=await ve(e,t,"/notify/settings");e.out.result(n,()=>Mt(e,n))}},wo={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 y(e),n=ct(e.args),{body:s,etag:a}=await ve(e,t,"/notify/settings"),r=ut(s,n),i=et(s,r);if(i.length===0)return tt(e,"the message settings",s);await w(e,t,`set the message settings ${i.join(", ")}`);let o=await u(e,t,"PUT","/notify/settings",r,{...de(a),...R()});e.out.result({settings:o,changed:i},()=>{for(let l of i)e.out.line(`${l}: ${ce(s[l])} \u2192 ${ce(o[l])}`)})}},nh=new Set(["sms_countries","sms_blocked_countries"]);function sh(e){return{...e,sms_countries:e.sms_countries??[],sms_blocked_countries:e.sms_blocked_countries??[],mx_check:e.mx_check??!1}}var vo={name:"notify",async read(e,t){let[n,s,a,r,i,o]=await Promise.all([u(e,t,"GET","/notify/settings"),u(e,t,"GET","/notify/providers"),u(e,t,"GET","/notify/templates/email"),u(e,t,"GET","/notify/templates/sms"),u(e,t,"GET","/notify/templates/whatsapp"),Ia(e,t)]),l={};for(let[c,g]of Object.entries(sh(n)))l[`settings.${c}`]=g;for(let c of s.providers)l[`providers.${c.channel}/${c.provider}`]={active:c.active,priority:c.priority,credentials:"set"};let p=a.templates.filter(c=>c.customized),d=await Promise.all(p.map(c=>u(e,t,"GET",`/notify/templates/email/${v(c.slug)}/${v(c.locale)}`)));for(let c of d)l[`templates.email/${c.slug}/${c.locale}`]={subject:c.subject,html_body:c.html_body,text_body:c.text_body};for(let c of r.templates.filter(g=>g.customized))l[`templates.sms/${c.slug}/${c.locale}`]={body:c.body};for(let c of i.templates)l[`templates.whatsapp/${c.slug}/${c.locale}`]={name:c.name,language:c.language,variables:c.variables,code_button:c.code_button};for(let c of o)l[`topics.${c.name}`]={self_subscribe:c.self_subscribe};return l},async copy(e,t,n,s){let a=te(n,"settings.");if(a.length>0){let{body:o,etag:l}=await ve(e,t,"/notify/settings"),p={...o};for(let d of a)p[d.name]=nh.has(d.name)&&Array.isArray(d.from)&&d.from.length===0?null:d.from;await F(s,`set the message settings ${a.map(d=>d.name).join(", ")}`,async()=>{await u(e,t,"PUT","/notify/settings",p,{...de(l),...R()})})}for(let o of te(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 te(n,"templates.")){let l=`/notify/templates/${o.name.split("/").map(v).join("/")}`;o.from===void 0?await F(s,`remove the template override ${o.name}`,async()=>{await u(e,t,"DELETE",l,void 0,R())}):await F(s,`override the template ${o.name}`,async()=>{await u(e,t,"PUT",l,o.from,R())})}let r=te(n,"topics."),i=r.some(o=>o.from===void 0)?new Map((await Ia(e,t)).map(o=>[o.name,o.subscribers])):new Map;for(let o of r){let l=`/notify/topics/${v(o.name)}`,p=o.from?.self_subscribe;if(o.from===void 0){let d=i.get(o.name)??0;d>0?s.left(`the topic ${o.name}: only in the target, and ${d} users follow it \u2014 delete it with notify:topics:delete`):await F(s,`delete the topic ${o.name}`,async()=>{await u(e,t,"DELETE",l,void 0,R())})}else o.change==="only_in_from"?await F(s,`make the topic ${o.name}`,async()=>{await u(e,t,"POST","/notify/topics",{name:o.name,self_subscribe:p},R())}):await F(s,`set the topic ${o.name} ${p?"followed from the app":"followed by the project's adding"}`,async()=>{await u(e,t,"PATCH",l,{self_subscribe:p},R())})}}};var zt=["max_connections","max_events_per_second","max_joins_per_second","max_channels_per_connection"];function ah(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 qa(e){return Object.fromEntries(zt.map(t=>[t,e[t]??null]))}var _o={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 y(e),n=await u(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=>ah(s)}])})}},ko={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 y(e),n=ge(await _e(e,e.flags.file,"the channel declaration"),"the channel declaration"),s=Array.isArray(n)?n:n.channels;if(!Array.isArray(s))throw m('the declaration is {"channels": [\u2026]} or a list of channels');let a=await u(e,t,"GET","/realtime/channels");if(Le(a.channels,s))return tt(e,"the channel declaration",a);await w(e,t,`declare ${s.length} channel pattern(s) (now ${a.channels.length})`);let r=await u(e,t,"PUT","/realtime/channels",{channels:s});e.out.result(r,()=>e.out.line(r.channels.length===0?"No channel is declared now.":`Declared ${r.channels.length} channel pattern(s): ${r.channels.map(i=>i.pattern).join(", ")}.`))}},So={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 y(e),n=await u(e,t,"GET","/realtime/limits");e.out.result(n,()=>{let s=n.platform??{},a=Math.max(...zt.map(r=>r.length));for(let r of zt)e.out.line(`${r.padEnd(a)} ${n[r]===void 0||n[r]===null?"the platform's":ce(n[r])}${s[r]!==void 0?` (platform ${s[r]===0?"no ceiling":ce(s[r])})`:""}`)})}},$o={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: ${zt.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 y(e),n=ct(e.args);for(let o of n)if(o.path.length!==1||!zt.includes(o.path[0]))throw m(`no limit ${JSON.stringify(o.path.join("."))}`,`the limits: ${zt.join(", ")}`);let s=qa(await u(e,t,"GET","/realtime/limits")),a=ut(s,n),r=et(s,a);if(r.length===0)return tt(e,"the realtime limits",s);await w(e,t,`set the realtime limits ${r.join(", ")}`);let i=await u(e,t,"PUT","/realtime/limits",a);e.out.result({limits:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${ce(s[o]??"the platform's")} \u2192 ${ce(i[o]??"the platform's")}`)})}},Po={name:"realtime",async read(e,t){let[n,s]=await Promise.all([u(e,t,"GET","/realtime/channels"),u(e,t,"GET","/realtime/limits")]),a={channels:n.channels};for(let[r,i]of Object.entries(qa(s)))a[`limits.${r}`]=i;return a},async copy(e,t,n,s){let a=n.find(i=>i.key==="channels");a&&await F(s,"declare the channels",async()=>{await u(e,t,"PUT","/realtime/channels",{channels:a.from??[]})});let r=te(n,"limits.");if(r.length>0){let i=qa(await u(e,t,"GET","/realtime/limits"));for(let o of r)i[o.name]=o.from??null;await F(s,`set the realtime limits ${r.map(o=>o.name).join(", ")}`,async()=>{await u(e,t,"PUT","/realtime/limits",i)})}}},Ao={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 y(e),n=await u(e,t,"GET","/analytics/settings");e.out.result(n,()=>Mt(e,n))}},Ro={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 y(e),n=ct(e.args),s=await u(e,t,"GET","/analytics/settings"),a=ut(s,n),r=et(s,a);if(r.length===0)return tt(e,"the analytics settings",s);await w(e,t,`set the analytics settings ${r.join(", ")}`);let i=await u(e,t,"PUT","/analytics/settings",a);e.out.result({settings:i,changed:r},()=>{for(let o of r)e.out.line(`${o}: ${ce(s[o])} \u2192 ${ce(i[o])}`)})}},To={name:"analytics",async read(e,t){let n=await u(e,t,"GET","/analytics/settings");return Object.fromEntries(Object.entries(n).map(([s,a])=>[`settings.${s}`,a]))},async copy(e,t,n,s){let a=te(n,"settings.");if(a.length===0)return;let r=await u(e,t,"GET","/analytics/settings");for(let i of a)i.from!==void 0&&(r[i.name]=i.from);await F(s,`set the analytics settings ${a.map(i=>i.name).join(", ")}`,async()=>{await u(e,t,"PUT","/analytics/settings",r)})}};function rh(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 Eo={name:"machines",async read(e,t){let{machines:n}=await u(e,t,"GET","/machines?limit=100");return Object.fromEntries(n.map(s=>[`machines.${s.name}`,rh(s)]))},async copy(e,t,n,s){for(let a of te(n,"machines.")){if(a.change==="only_in_to"){s.left(`the machine ${a.name}: only the target has it \u2014 it serves on (palbase machines:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await F(s,`opened the machine ${a.name}, built from ${r.source.branch}'s head`,()=>u(e,t,"POST","/machines",{name:a.name,...r,database:r.database??void 0},R()));continue}let i=a.to,o=Object.fromEntries(Object.keys(r).filter(l=>!Le(r[l],i[l])).map(l=>[l,l==="database"&&r[l]===null?{name:""}:r[l]]));await F(s,`changed the machine ${a.name}: ${Object.keys(o).join(", ")} (a new version rolls out beside the one serving)`,()=>u(e,t,"PATCH",`/machines/${v(a.name)}`,o))}}};function ih(e){return{size:e.size,allow:[...e.allow].sort(),backup_days:e.backup_days}}async function oh(e,t,n,s){let a=await u(e,t,"POST","/databases",{name:n,...s},R());await e.state.keepDatabasePasswords(t.env_ref,a.name,{owner:a.credentials.owner.password,app:a.credentials.app.password})}var Co={name:"databases",async read(e,t){let{databases:n}=await u(e,t,"GET","/databases?limit=100");return Object.fromEntries(n.map(s=>[`databases.${s.name}`,ih(s)]))},async copy(e,t,n,s){for(let a of te(n,"databases.")){if(a.change==="only_in_to"){s.left(`the database ${a.name}: only the target has it \u2014 its data stays (palbase databases:delete ${a.name} takes it away)`);continue}let r=a.from;if(a.change==="only_in_from"){await F(s,`opened the database ${a.name} (${r.size}; its passwords kept on this computer \u2014 palbase db:copy brings its data)`,()=>oh(e,t,a.name,r));continue}let i=a.to,o=Object.fromEntries(["size","allow","backup_days"].filter(p=>!Le(r[p],i[p])).map(p=>[p,r[p]]));if(Object.keys(o).length===0)continue;let l=o.size===void 0?"":` (moving to ${o.size}, serving on)`;await F(s,`changed the database ${a.name}: ${Object.keys(o).join(", ")}${l}`,()=>u(e,t,"PATCH",`/databases/${v(a.name)}`,o))}}};var lh=/^[a-z][a-z0-9-]{1,62}$/;function Gt(e){if(!lh.test(e))throw m(`${JSON.stringify(e)} is not a bucket's name: lowercase letters, digits and dashes, starting with a letter (2\u201363)`);return`/storage/buckets/${v(e)}`}function Ua(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 ph(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 m(`--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 Ka(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 dh(e,t,n){try{return await u(e,t,"GET",Gt(n))}catch(s){if(s instanceof x&&s.status===404)return;throw s}}var jo={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 y(e),{buckets:n}=await u(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=>Ua(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(a=>a.name).join(", ")}])})}},Oo={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 y(e),n=await u(e,t,"GET",Gt(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 ${Ua(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}`:""}`)})}},xo={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 y(e),n=e.args[0];if(e.flags.public===!0&&e.flags.private===!0)throw m("--public and --private are one or the other");let s=await dh(e,t,n),a=Ka(s??{name:n,public:!1,allowed_mime_types:[],variants:[]});if(e.flags.public===!0&&(a.public=!0),e.flags.private===!0&&(a.public=!1),e.flags["max-size"]!==void 0&&(a.file_size_limit=ph(e.flags["max-size"])),e.flags.types!==void 0&&(a.allowed_mime_types=Q(e.flags.types)??[]),e.flags["variants-file"]!==void 0){let i=ge(await _e(e,e.flags["variants-file"],"the renditions"),"the renditions file"),o=Array.isArray(i)?i:i.variants;if(!Array.isArray(o))throw m("the renditions file is a JSON list of {name, width, height, fit, format, quality}");a.variants=o}if(s&&Le(Ka(s),a)){e.out.result(s,()=>e.out.line(`Nothing to change: ${n} already is so.`));return}await w(e,t,`${s?"change":"make"} the bucket ${n} (${a.public?"public":"private"})`);let r=await u(e,t,"PUT",Gt(n),a);e.out.result(r,()=>e.out.line(`${s?"Changed":"Made"} the bucket ${r.name}: ${r.public?"public":"private"}, files up to ${Ua(r.file_size_limit)}, ${r.allowed_mime_types.length===0?"any type":r.allowed_mime_types.join(", ")}.`))}},Lo={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 y(e),n=Gt(e.args[0]);await w(e,t,`delete the bucket ${e.args[0]}`),await u(e,t,"DELETE",n),e.out.result({deleted:e.args[0]},()=>e.out.line(`Deleted the bucket ${e.args[0]}.`))}},Do={name:"storage",async read(e,t){let{buckets:n}=await u(e,t,"GET","/storage/buckets"),s={};for(let a of n){let r=Ka(a);s[`buckets.${a.name}`]={...r,allowed_mime_types:[...r.allowed_mime_types].sort()}}return s},async copy(e,t,n,s){for(let a of te(n,"buckets."))a.from===void 0?await F(s,`delete the bucket ${a.name}`,async()=>{await u(e,t,"DELETE",Gt(a.name))}):await F(s,`declare the bucket ${a.name}`,async()=>{await u(e,t,"PUT",Gt(a.name),a.from)})}};var Bt=[Vi,Do,ro,vo,Po,To],ss=[Co,Eo],Io={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: ${Bt.map(e=>e.name).join(",")} (the default), and ${ss.map(e=>e.name).join(",")} when named`}};async function No(e){if(e.global.env!==void 0)throw m("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 m("name both environments: --from <env> --to <env>");let s=await We(e.studio),a;if(e.global.project!==void 0)a=St(s,e.global.project);else{let l=await e.state.targetFor(e.io.cwd);if(!l)throw new h("no_target","which project? none is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});a=s.filter(p=>p.project_id===l.target.project_id)}let r=l=>{let p=a.find(d=>d.slug===l||d.ref===l);if(!p)throw new h("no_such_environment",`the project has no environment ${JSON.stringify(l)}`,{hint:`its environments: ${a.map(d=>d.slug).join(", ")}`});return be(p)},i=r(t),o=r(n);if(i.env_ref===o.env_ref)throw m("--from and --to name the same environment");return{from:i,to:o}}function Mo(e){let t=Q(e.flags.only);if(t===void 0)return Bt;let n=[...Bt,...ss];for(let s of t)if(!n.some(a=>a.name===s))throw m(`no module ${JSON.stringify(s)}`,`the modules: ${n.map(a=>a.name).join(", ")}`);return n.filter(s=>t.includes(s.name))}function ch(e,t){let n=[...new Set([...Object.keys(e),...Object.keys(t)])].sort(),s=[];for(let a of n){let r=a in e,i=a in t;r&&!i?s.push({key:a,change:"only_in_from",from:e[a]}):!r&&i?s.push({key:a,change:"only_in_to",to:t[a]}):Le(e[a],t[a])||s.push({key:a,change:"differs",from:e[a],to:t[a]})}return s}async function za(e,t,n,s){let a=l=>Promise.all(t.map(p=>p.read(e,l))),[r,i]=await Promise.all([a(n),a(s)]),o=Object.fromEntries(t.map((l,p)=>[l.name,ch(r[p],i[p])]));return{from:n,to:s,modules:o,count:Object.values(o).reduce((l,p)=>l+p.length,0)}}function Ga(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 ns(e){let t=JSON.stringify(e)??"undefined";return t.length>140?`${t.slice(0,139)}\u2026`:t}function Ba(e,t){let n=t.from.env_slug,s=t.to.env_slug;for(let[r,i]of Object.entries(t.modules))if(i.length!==0){e.out.line(`${r} (${i.length})`);for(let o of i)o.change==="differs"?e.out.line(` ~ ${o.key}: ${n} ${ns(o.from)} \xB7 ${s} ${ns(o.to)}`):o.change==="only_in_from"?e.out.line(` - ${o.key}: only in ${n}: ${ns(o.from)}`):e.out.line(` + ${o.key}: only in ${s}: ${ns(o.to)}`)}let a=Object.keys(t.modules).join(", ");e.out.line(t.count===0?`${n} and ${s} have the same settings (${a}).`:`${t.count} difference(s) between ${n} and ${s} (${a}).`)}var uh={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:Io,scope:"environment",async run(e){let{from:t,to:n}=await No(e),s=await za(e,Mo(e),t,n);return e.out.result(Ga(s),()=>Ba(e,s)),s.count===0?0:1}},mh={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:Io,scope:"environment",async run(e){let{from:t,to:n}=await No(e);return Ja(e,Mo(e),t,n)}};async function Ja(e,t,n,s,a={}){let r=await za(e,t,n,s);if(r.count===0)return e.out.result({...a,copied:[],left:[],shown_once:[],after:Ga(r)},()=>e.out.line(`Nothing to copy: ${n.env_slug} and ${s.env_slug} have the same settings.`)),0;e.out.json||Ba(e,r);let i=t.filter(c=>r.modules[c.name].length>0).map(c=>c.name);await w(e,s,`make its ${i.join(", ")} settings equal to ${n.env_slug}'s (${r.count} difference(s))`);let o=[],l=[],p=[];e.out.json||e.out.line();for(let c of t){let g=r.modules[c.name];if(g.length===0)continue;let f={done(b){o.push(`${c.name}: ${b}`),e.out.json||e.out.line(`copied ${c.name}: ${b}`)},left(b){l.push(`${c.name}: ${b}`),e.out.json||e.out.line(`left ${c.name}: ${b}`)},shown(b,P){p.push({what:`${c.name}: ${b}`,value:P}),e.out.json||e.out.line(` ${c.name}: ${b} ${P} (shown this once)`)}};await c.copy(e,s,g,f)}let d=await za(e,t,n,s);return e.out.result({...a,copied:o,left:l,shown_once:p,after:Ga(d)},()=>{e.out.line(),e.out.line("Compared again:"),Ba(e,d)}),d.count===0?0:1}var qo=[ji,Oi,xi,Li,Ii,Di,Ni,Mi,qi,Ui,zi,Gi,Bi,Ji,Fi,Wi,jo,Oo,xo,Lo,Zi,Qi,eo,to,so,ao,no,oo,lo,po,co,uo,mo,ho,go,fo,yo,bo,wo,_o,ko,So,$o,Ao,Ro,uh,mh];async function Fa(e){return(await e.studio.get("/orgs")).organizations}async function gt(e,t){let n=await Fa(e);if(t===void 0){if(n.length===1)return n[0];throw m(n.length===0?"you are in no organization yet: make one with palbase orgs:create <name>":"which organization? pass --org <name>",`yours: ${n.map(a=>a.name).join(", ")}`)}let s=n.filter(a=>a.id===t||a.name.localeCompare(t,void 0,{sensitivity:"accent"})===0);if(s.length===0)throw new h("no_such_org",`no organization ${JSON.stringify(t)} among yours`,{hint:"palbase orgs:list shows them"});if(s.length>1)throw new h("ambiguous_org",`${JSON.stringify(t)} names more than one of your organizations`,{hint:"pass its id"});return s[0]}var Ko={name:"orgs:list",summary:"your organizations and your role in each",scope:"account",async run(e){let t=await Fa(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}])})}},Uo={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 ${W(t.name)} (${t.id}).`))}},zo={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 gt(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}]))})}},Go={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 gt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--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 ${W(s.email)} to ${W(t.name)} as ${s.role}.`))}},Bo={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 gt(e,e.flags.org),n=e.args[0],s=await e.studio.get(`/orgs/${t.id}/members`),a=s.members.find(i=>i.user_id===n||i.email!==null&&i.email.toLowerCase()===n.toLowerCase());if(a){await e.studio.request("DELETE",`/orgs/${t.id}/members/${encodeURIComponent(a.user_id)}`),e.out.result({removed:a},()=>e.out.line(`${W(a.email??a.user_id)} is no longer in ${W(t.name)}.`));return}let r=s.invitations.find(i=>i.id===n||i.email.toLowerCase()===n.toLowerCase());if(!r)throw new h("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(r.id)}`),e.out.result({withdrawn:r},()=>e.out.line(`The invitation of ${W(r.email)} is withdrawn.`))}},Jo={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 gt(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(`${W(t.name)} has no access token yet: palbase tokens:create <name> makes one.`):e.out.table(n,[{header:"name",value:s=>W(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}])})}},Fo={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 gt(e,e.flags.org),n=e.flags.limit===void 0?50:Number(e.flags.limit);if(!Number.isInteger(n)||n<1||n>1e4)throw m("--limit is a whole number from 1 to 10000");let s=[],a=null;do{let r=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{before:a}:{}}),i=await e.studio.get(`/orgs/${t.id}/audit?${r}`);s.push(...i.entries),a=i.next_before}while(a&&s.length<n);e.out.result({entries:s},()=>{s.length===0?e.out.line(`Nothing changed at ${W(t.name)}'s environments yet.`):e.out.table(s,[{header:"when",value:r=>r.at.slice(0,16).replace("T"," ")},{header:"who",value:r=>W(r.actor_name??r.actor)},{header:"environment",value:r=>W(r.environment_ref)},{header:"what",value:r=>W(`${r.method} ${r.path}`)},{header:"answer",value:r=>String(r.status)}])})}},Wo={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 gt(e,e.flags.org),n=e.flags.role??"member";if(n!=="admin"&&n!=="member")throw m("--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 m("--days is a whole number from 1 to 365");let a=await e.studio.post(`/orgs/${t.id}/tokens`,{name:e.args[0],role:n,expires_in_days:s});e.out.result(a,()=>{e.out.line(`Made the access token ${W(a.name)} of ${W(t.name)} (${a.role}, until ${a.expires_at.slice(0,10)}).`),e.out.line(`token (shown this once \u2014 keep it as the CI's secret PALBASE_TOKEN): ${a.token}`)})}},Vo={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 gt(e,e.flags.org),{tokens:n}=await e.studio.get(`/orgs/${t.id}/tokens`),s=n.filter(a=>a.id===e.args[0]||a.name===e.args[0]);if(s.length===0)throw new h("no_such_token",`${W(t.name)} has no access token ${JSON.stringify(e.args[0])}`,{hint:"palbase tokens:list shows them"});if(s.length>1)throw new h("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 ${W(s[0].name)} of ${W(t.name)}.`))}},Ho={name:"projects:list",summary:"every project of your organizations, with its environments",scope:"account",async run(e){let t=await We(e.studio),n=await e.state.targetFor(e.io.cwd),s=(await e.studio.get("/projects")).projects,a=new Map(t.map(r=>[r.organization_id,r.org_name]));for(let r of await Fa(e))a.set(r.id,r.name);e.out.result({projects:s.map(r=>({...r,organization_name:a.get(r.organization_id)??null}))},()=>{s.length===0&&e.out.line("No project yet: palbase projects:create <name> makes one.");let r=s.flatMap(i=>i.environments.length===0?[{org:a.get(i.organization_id)??"",project:i.name,env:"(none)",ref:"",here:""}]:i.environments.map(o=>({org:a.get(i.organization_id)??"",project:i.name,env:o.slug,ref:o.ref,here:n?.target.env_ref===o.ref?"*":""})));e.out.table(r,[{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}])})}},Xo={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 gt(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 ${W(t.name)} \u203A ${W(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.`)})}},Yo={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=St(await We(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 h("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 h("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 ${W(n.project_name)}.`))}},Zo={name:"envs:list",summary:"a project's environments (the project chosen here, or -P)",scope:"environment",async run(e){let t=await nt(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}]))}},Qo={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 hh(e),n=e.args[0],s=e.flags.from;if(e.flags["with-cloud"]===!0&&s===void 0)throw m("--with-cloud goes with --from <env>");let a=s===void 0?[]:(await We(e.studio)).filter(p=>p.project_id===t.project_id),r=s===void 0?void 0:a.find(p=>p.slug===s||p.ref===s);if(s!==void 0&&!r)throw new h("no_such_environment",`the project has no environment ${JSON.stringify(s)}`,{hint:`its environments: ${a.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 ${W(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(!r){e.out.result(i,o);return}e.out.json||o();let l=be({...r,ref:i.environment.ref,slug:i.environment.slug,name:i.environment.name});return e.out.json||e.out.line(),Ja(e,e.flags["with-cloud"]===!0?[...Bt,...ss]:Bt,be(r),l,{made:i})}},el={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 nt(e)).find(r=>r.slug===e.args[0]||r.ref===e.args[0]);if(!n)throw new h("no_such_environment",`the project has no environment ${JSON.stringify(e.args[0])}`);let s=be(n);if(e.out.note(`${B(s)}: delete the environment, its users, data, machines and databases`),!e.global.yes){if(!e.interactive)throw new h("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 h("cancelled","nothing was deleted")}await e.studio.request("DELETE",`/environments/${n.ref}`);let a=await e.state.targetFor(e.io.cwd);a?.target.env_ref===n.ref&&await e.state.clearTarget(a.dir),e.out.result({deleted:n.ref},()=>e.out.line(`Deleted ${B(s)}.`))}},tl={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 y(e);else{let n=await We(e.studio);t=be(gh(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 ${B(t)}.`))}},nl={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 y(e),n=await e.studio.get(`/environments/${t.env_ref}`),[s,a]=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"})]),r=s.status===200?s.body.machines??[]:[],i=a.status===200?a.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:r,databases:i,chosen_for:o?.dir??null},()=>{e.out.line(B(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: ${r.length===0?"none":r.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(", ")}`)})}},sl={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 y(e),n=e.args[0]?`/${e.args[0].replace(/^\/+/,"")}`:"",s=`${e.cloud.studioWeb}/projects/${t.project_id}/environments/${t.env_ref}${n}`,a=e.interactive&&await e.io.openBrowser(s);e.out.result({url:s,opened:a},()=>e.out.line(a?`Opened ${s}`:s))}},al={name:"keys:list",summary:"the environment's API keys (never their values)",scope:"environment",async run(e){let t=await y(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}]))}},rl={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 m("--kind is publishable or secret");let n=await y(e);await w(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)})}},il={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 y(e);await w(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 hh(e){return(await nt(e))[0]}async function nt(e){let t=await We(e.studio);if(e.global.project!==void 0)return St(t,e.global.project);let n=await e.state.targetFor(e.io.cwd);if(!n)throw new h("no_target","no project is chosen for this directory",{hint:"pass -P <project>, or choose one here with palbase use"});let s=t.filter(a=>a.project_id===n.target.project_id);if(s.length===0)throw new h("no_such_project",`the project chosen here (${n.target.project_name}) is not among yours any more`,{hint:"palbase use another"});return s}function gh(e,t){let n=t.split("/");if(n.length>3||n.some(r=>r===""))throw m(`${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}],a;for(let r of s){let i;try{i=St(e,r.project)}catch(o){a=o;continue}if(r.env!==void 0){let o=i.find(l=>l.slug===r.env||l.ref===r.env);if(o)return o;a=new h("no_such_environment",`${i[0].project_name} has no environment ${JSON.stringify(r.env)}`,{hint:`its environments: ${i.map(l=>l.slug).join(", ")}`});continue}if(i.length===1)return i[0];throw new h("which_environment",`${i[0].project_name} has more than one environment`,{hint:`name one: ${i.map(o=>`${o.project_name}/${o.slug}`).join(", ")}`})}throw a}var fh=/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/;async function q(e,t,n,s,a,r){return(await Oe(e.studio,t.env_ref,n,s,a,r)).body}function Z(e){if(!fh.test(e))throw m(`${JSON.stringify(e)} is not a name: lowercase letters, digits and dashes, starting with a letter`);return encodeURIComponent(e)}async function De(e,t,n){if(n!==void 0)return n;let{machines:s}=await q(e,t,"GET","/machines?limit=100");if(s.length===1)return s[0].name;let a=await Sa(e.io,e.io.cwd),r=a?.repository?s.filter(i=>i.source.repository.toLowerCase()===a.repository?.toLowerCase()&&(i.source.path??"")===a.path):[];if(r.length===1)return r[0].name;throw s.length===0?new h("no_machine","the environment has no machine yet",{hint:"make one with palbase machines:create"}):m("which machine? name it",`the environment's machines: ${s.map(i=>i.name).join(", ")}`)}var ol={name:"machines:sizes",summary:"the machine sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await y(e),{sizes:n}=await q(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??""}`}]))}},ll={name:"databases:sizes",summary:"the database sizes, and which of them this environment may open now",scope:"environment",async run(e){let t=await y(e),{sizes:n}=await q(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??""}`}]))}},pl={name:"machines:list",summary:"the environment's machines: size, state, the version serving, address",scope:"environment",async run(e){let t=await y(e),{machines:n}=await q(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}])})}},dl={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 y(e),n=await De(e,t,e.args[0]),s=await q(e,t,"GET",`/machines/${Z(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(", ")}`)})}},cl={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 ul(e,t){e.flags["test-command"]!==void 0&&(t.test_command=e.flags["test-command"]),e.flags["test-users"]!==void 0&&(t.test_users=Oa(e.flags["test-users"]))}function ml(e){if(!e)return;let t={};for(let n of e){let s=n.indexOf("=");if(s<=0)throw m(`a setting is K=V, not ${JSON.stringify(n)}`);t[n.slice(0,s)]=n.slice(s+1)}return t}function $t(e,t){if(e===void 0)return;let n=Number(e);if(!Number.isInteger(n)||n<0)throw m(`--${t} is a whole number`);return n}var hl={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:{...cl,wait:{type:"boolean",description:"follow the first build until the machine serves"}},scope:"environment",async run(e){let t=await y(e),n=await Sa(e.io,e.io.cwd),s=e.flags.repo??n?.repository,a=e.flags.branch??n?.branch;if(!s)throw m("which repository? this directory's origin is not a GitHub repository","pass --repo owner/name");if(!a)throw m("which branch? none is checked out here","pass --branch <branch>");let r=e.flags.size;if(r===void 0){let{sizes:g}=await q(e,t,"GET","/machines/sizes");if(r=g.find(f=>f.available)?.name,!r)throw new h("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:a,...i!==""?{path:i}:{},...e.flags.context?{context:e.flags.context}:{}},size:r},l=$t(e.flags.replicas,"replicas"),p=$t(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 d=ml(e.flags.var);if(d&&(o.env=d),e.flags.db!==void 0)o.database={name:e.flags.db,migrations:e.flags.migrations===!0};else if(e.flags.migrations===!0)throw m("--migrations goes with --db <database>");ul(e,o),await w(e,t,`make a machine from ${s}@${a}${i?`/${i}`:""} (${r})`);let c=await q(e,t,"POST","/machines",o);e.flags.wait===!0&&(c=await bh(e,t,c.name)),e.out.result(c,()=>e.out.line(`Made the machine ${c.name} (${c.size}) \u2014 ${c.state}; it answers at ${c.url}`))}},gl={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:cl,scope:"environment",async run(e){let t=await y(e),n=await De(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 q(e,t,"GET",`/machines/${Z(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 a=$t(e.flags.replicas,"replicas"),r=$t(e.flags.port,"port");if(a!==void 0&&(s.replicas=a),r!==void 0&&(s.port=r),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 m("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}),ul(e,s),Object.keys(s).length===0)throw m("nothing to change: give a flag","palbase help machines:update");await w(e,t,`change the machine ${n}: ${Object.keys(s).join(", ")}`);let i=await q(e,t,"PATCH",`/machines/${Z(n)}`,s);e.out.result(i,()=>e.out.line(`${i.name}: version ${i.version} rolls out beside ${i.running_version}.`))}},fl={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 y(e),n=await De(e,t,e.args[0]),s=await q(e,t,"GET",`/machines/${Z(n)}`),a=s.secrets?await q(e,t,"GET",`/secrets/${encodeURIComponent(s.secrets)}`).then(o=>(o.items??o.secrets??[]).map(l=>l.name),()=>[]):[],r=["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=r.map(o=>({name:o,from:"platform",note:a.includes(o)||o in s.env?`in place of the ${o} set by hand`:""}));for(let o of Object.keys(s.env).sort())r.includes(o)||i.push({name:o,from:"machine",note:a.includes(o)?`shadows the secret ${o}`:""});for(let o of a.sort())r.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 yl(e,t){let n=await y(e),s=await De(e,n,e.flags.machine);for(let a=1;;a++){let r=await Oe(e.studio,n.env_ref,"GET",`/machines/${Z(s)}`),i={...r.body.env},o=t(i);if(a===1){if(r.body.secrets){let l=await q(e,n,"GET",`/secrets/${encodeURIComponent(r.body.secrets)}`).catch(()=>({items:[]}));for(let p of Object.keys(i))(l.items??[]).some(d=>d.name===p)&&e.out.warn(`${p} in the machine's env shadows the secret of that name`)}await w(e,n,`${o.join(", ")} on the machine ${s}`)}try{let l=await q(e,n,"PATCH",`/machines/${Z(s)}`,{env:i},r.etag?{"if-match":r.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 x&&l.status===412)||a===3)throw l;e.out.note(`${s} changed meanwhile: making the change again on what it is now\u2026`)}}}var bl={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=ml(e.args);await yl(e,n=>(Object.assign(n,t),Object.keys(t).map(s=>`set ${s}`)))}},wl={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 yl(e,t=>{for(let n of e.args){if(!(n in t))throw new h("no_such_setting",`the machine is not started with ${n}`);delete t[n]}return e.args.map(n=>`unset ${n}`)})}},vl={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 y(e),n=await De(e,t,e.args[0]),{builds:s}=await q(e,t,"GET",`/machines/${Z(n)}/builds?limit=20`);e.out.result({builds:s},()=>e.out.table(s,[{header:"build",value:a=>a.id},{header:"commit",value:a=>a.commit.slice(0,7)},{header:"state",value:a=>`${a.state}${a.running?" (serving)":""}${a.reason?` \u2014 ${a.reason}`:""}`},{header:"message",value:a=>a.message.split(`
30
+ `)[0].slice(0,60)},{header:"created",value:a=>a.created_at}]))}},_l={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 y(e),n=await De(e,t,e.flags.machine),s=e.args[0];if(!s){let{builds:i}=await q(e,t,"GET",`/machines/${Z(n)}/builds?limit=1`);if(i.length===0)throw new h("no_build",`${n} has no build yet`);s=i[0].id}let a=await q(e,t,"GET",`/machines/${Z(n)}/builds/${encodeURIComponent(s)}/log`),r=typeof a=="string"?a:JSON.stringify(a);e.out.result({build:s,log:r},()=>{for(let i of r.split(`
31
+ `))e.out.line(i)})}},kl={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 y(e),n=await De(e,t,e.args[0]),s=$t(e.flags.lines,"lines")??200,a=e.flags.since?wh(e.flags.since,e.io.now()):void 0,r=e.flags.search,i=new Set,o=async p=>{let d=new URLSearchParams({lines:String(s),order:p,...a?{since:a}:{},...r?{q:r}:{}}),c=await q(e,t,"GET",`/machines/${Z(n)}/logs?${d}`),g=c!==null&&typeof c=="object"?[c]:String(c??"").split(`
32
+ `).filter(f=>f.trim()!=="").map(f=>JSON.parse(f));return p==="desc"?g.reverse():g},l=p=>{for(let d of p)i.has(d.id)||(i.add(d.id),e.global.json?e.io.stdout(`${JSON.stringify(d)}
33
+ `):e.out.line(`${d.ts} ${d.pod?`[${d.pod.slice(-5)}] `:""}${d.line}`));p.length>0&&(a=p[p.length-1].ts)};for(l(await o("desc"));e.flags.follow===!0;)await e.io.sleep(2e3),l(await o("asc"))}},Sl={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 y(e),n=await De(e,t,e.args[0]);await w(e,t,`build and roll out ${n} from the head of its branch`);let s=await q(e,t,"POST",`/machines/${Z(n)}/builds`);e.flags.wait===!0&&(s=await yh(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 yh(e,t,n){let s="";for(let a=0;a<900;a++){let r=await q(e,t,"GET",`/machines/${Z(n)}`);if(!r.build_requested){let{builds:i}=await q(e,t,"GET",`/machines/${Z(n)}/builds?limit=1`),o=i[0];if(!o)throw new h("build_failed",`${n} has no build${r.state_reason?`: ${r.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 h("build_failed",`the build ${o.state}${o.reason?`: ${o.reason}`:""}`,{hint:`palbase machines:builds:log ${o.id} --machine ${n}`});if(o.state==="succeeded"&&r.state==="failed")throw new h("build_failed",`${n}: ${r.state_reason??"failed"}`,{hint:`palbase machines:get ${n}`});if(o.running&&r.running_version===r.version)return r}await e.io.sleep(2e3)}throw new h("timeout",`${n} was not serving the build after 30 minutes`,{hint:`palbase machines:builds ${n}`})}var $l={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 y(e),n=await De(e,t,e.args[0]),s=e.flags.to;if(!s){let{builds:r}=await q(e,t,"GET",`/machines/${Z(n)}/builds?limit=50`),i=r.findIndex(l=>l.running),o=r.slice(i+1).find(l=>l.state==="succeeded");if(i<0||!o)throw new h("no_earlier_build",`${n} has no earlier build that succeeded`,{hint:"name one with --to (palbase machines:builds lists them)"});s=o.id}await w(e,t,`roll ${n} back to build ${s}`);let a=await q(e,t,"POST",`/machines/${Z(n)}/rollback`,{build:s});e.out.result(a,()=>e.out.line(`${a.name} rolls back to build ${s} (version ${a.version}).`))}},Pl={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 y(e),n=await De(e,t,e.args[0]),s=await q(e,t,"GET",`/machines/${Z(n)}/jobs`),a=s.jobs??[];e.out.result(s,()=>e.out.table(a,[{header:"job",value:r=>r.name},{header:"schedule",value:r=>r.schedule},{header:"next",value:r=>r.next_at??""},{header:"last run",value:r=>r.last_run?`${r.last_run.state} ${r.last_run.started_at??""}`:""}]))}},Al={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 y(e),n=await De(e,t,e.flags.machine),s=await q(e,t,"GET",`/machines/${Z(n)}/jobs/${encodeURIComponent(e.args[0])}/runs`),a=s.runs??[];e.out.result(s,()=>e.out.table(a,[{header:"run",value:r=>r.id},{header:"state",value:r=>r.state},{header:"started",value:r=>r.started_at??""},{header:"finished",value:r=>r.finished_at??""}]))}},Rl={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 y(e),n=await De(e,t,e.args[0]),s=await q(e,t,"GET",`/machines/${Z(n)}/openapi`);e.io.stdout(`${JSON.stringify(s,null,2)}
34
+ `)}},Tl={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 y(e),n=e.args[0];if(!e.global.yes){if(!e.interactive)throw new h("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 h("cancelled","nothing was deleted")}await q(e,t,"DELETE",`/machines/${Z(n)}`),e.out.result({deleted:n},()=>e.out.line(`Deleted the machine ${n}.`))}};async function bh(e,t,n){e.out.note(`Following ${n}'s first build\u2026`);let s="";for(let a=0;a<900;a++){let r=await q(e,t,"GET",`/machines/${Z(n)}`),i=`${r.state}${r.build?` (${r.build.commit.slice(0,7)})`:""}`;if(i!==s&&e.out.note(` ${i}${r.state_reason?` \u2014 ${r.state_reason}`:""}`),s=i,r.running_version>=1&&r.running_version===r.version&&(r.state==="awake"||r.state==="asleep"))return r;if(r.state==="failed")throw new h("build_failed",`${n}'s build failed${r.state_reason?`: ${r.state_reason}`:""}`,{hint:`palbase machines:builds:log --machine ${n}`});await e.io.sleep(2e3)}throw new h("timeout",`${n} was not serving after 30 minutes`,{hint:`palbase machines:get ${n}`})}function wh(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 m(`--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 El={name:"databases:list",summary:"the environment's databases: size, state, how full",scope:"environment",async run(e){let t=await y(e),{databases:n}=await q(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}])})}},Cl={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 y(e),n=await q(e,t,"GET",`/databases/${Z(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 jl(e){if(e!==void 0)return String(e).split(",").map(t=>t.trim()).filter(t=>t!=="")}var Ol={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 y(e),n={};e.args[0]&&(n.name=e.args[0]),e.flags.size!==void 0&&(n.size=e.flags.size);let s=jl(e.flags.allow);s&&(n.allow=s);let a=$t(e.flags["backup-days"],"backup-days");a!==void 0&&(n.backup_days=a),await w(e,t,`open a database ${e.args[0]??"main"}${n.size?` (${n.size})`:""}`);let r=await q(e,t,"POST","/databases",n);await e.state.keepDatabasePasswords(t.env_ref,r.name,{owner:r.credentials.owner.password,app:r.credentials.app.password});let i=e.flags["show-passwords"]===!0,{credentials:o,...l}=r;e.out.result(i?r:{...l,credentials:{owner:Va(o.owner),app:Va(o.app)}},()=>{e.out.line(`Opened the database ${r.name} (${r.size}) at ${r.host}.`),e.out.line("Its passwords are kept on this computer: palbase db:url and db:shell use them."),i&&(e.out.line(`owner: ${r.credentials.owner.password??""}`),e.out.line(`app: ${r.credentials.app.password??""}`))})}},Wa="[YOUR-PASSWORD]";function Va(e){let{password:t,...n}=e,s={};for(let[a,r]of Object.entries(n))s[a]=typeof r=="string"?vh(r,t):r;return s}function vh(e,t){let n=e.replace(/(postgres(?:ql)?:\/\/[^:/@\s'"]+:)[^@\s'"]*(@)/gi,`$1${Wa}$2`).replace(/([?&;]password=)[^&;\s'"]*/gi,`$1${Wa}`);if(t)for(let s of new Set([t,encodeURIComponent(t),new URLSearchParams({p:t}).toString().slice(2)]))n=n.split(s).join(Wa);return n}var xl={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 y(e),n=e.args[0],s={},a=e.flags.size;if(a!==void 0){if(a==="")throw m("--size is one of palbase databases:sizes");s.size=a}let r=jl(e.flags.allow);r&&(s.allow=r);let i=$t(e.flags["backup-days"],"backup-days");if(i!==void 0&&(s.backup_days=i),Object.keys(s).length===0)throw m("nothing to change: --size, --allow or --backup-days");await w(e,t,`change the database ${n}: ${Object.keys(s).join(", ")}`);let o=await q(e,t,"PATCH",`/databases/${Z(n)}`,s);a!==void 0&&e.flags.wait===!0&&(o=await _h(e,t,n,a)),e.out.result(o,()=>{a!==void 0&&e.out.line(o.state==="resizing"?`${o.name} is moving to ${a}, 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}).`),(r||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 _h(e,t,n,s){e.out.note(`Following ${n}'s move to ${s}\u2026`);let a="";for(let r=0;r<900;r++){let i=await q(e,t,"GET",`/databases/${Z(n)}`),o=`${i.state}${i.state_reason?` \u2014 ${i.state_reason}`:""}`;if(o!==a&&e.out.note(` ${o}`),a=o,i.size===s&&i.state!=="resizing"&&i.state!=="starting"){if(i.state==="failed")throw new h("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 h("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 h("timeout",`${n} was still moving to ${s} after 30 minutes; it serves on meanwhile`,{hint:`palbase databases:get ${n}`})}var Ll={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 m("--role is owner or app");let n=await y(e);await w(e,n,`make the ${t} password of ${e.args[0]} new`);let a=(await q(e,n,"POST",`/databases/${Z(e.args[0])}/password`,{role:t}))[t];if(!a?.password)throw new h("no_password","the platform answered no password");await e.state.keepDatabasePasswords(n.env_ref,e.args[0],{[t]:a.password}),await kh(e,n,e.args[0]),e.out.result(e.flags["show-password"]===!0?a:Va(a),()=>{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(a.password??"")})}};async function kh(e,t,n){for(let s=0;s<60;s++){if((await q(e,t,"GET",`/databases/${Z(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 h("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 Dl={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 m("--to is the moment to go back to, as an RFC 3339 time");let n=await y(e);await w(e,n,`bring ${e.args[0]} back to ${t} (what was written since goes)`);let s=await q(e,n,"POST",`/databases/${Z(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 Ha(e,t,n,s,a,r,i=!1){return{name:e,summary:t,usage:"<name>",args:{min:1,max:1},scope:"environment",async run(o){let l=await y(o),p=o.args[0];if(n==="DELETE"||s==="/purge"){if(!o.global.yes){if(!o.interactive)throw new h("confirmation_needed",`${a(p)} is confirmed`,{hint:"confirm with -y"});if((await o.io.prompt("Type the database's name to go ahead: ")).trim()!==p)throw new h("cancelled","nothing was changed")}}else await w(o,l,a(p));await q(o,l,n,`/databases/${Z(p)}${s}`),i&&await o.state.forgetDatabase(l.env_ref,p),o.out.result({database:p,done:e},()=>o.out.line(r(p)))}}}var Il=Ha("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.`),Nl=Ha("databases:undelete","take back a database deleted in the last 7 days","POST","/undelete",e=>`take back the database ${e}`,e=>`${e} is back.`),Ml=Ha("databases:purge","delete a deleted database for good, now","POST","/purge",e=>`purging the database ${e} for good`,e=>`${e} is purged.`,!0),ql={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 y(e),n=e.flags["all-envs"]===!0?(await nt(e)).map(be):[t],s=await q(e,t,"POST","/machines/github/connect"),a=e.flags["github-token-stdin"]===!0;if(!a&&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 r=a?await $e(e,"a GitHub user's token"):await Sh(e,s.client_id),i=await $h(e,r),o=[];for(let l of n){await w(e,l,`link the GitHub account ${i.account}`);let p=await q(e,l,"POST","/machines/github/installations",{installation_id:i.id,user_token:r});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 Sh(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 h("github_unavailable",`GitHub did not start the sign-in: ${s.status}`);let a=await s.json();e.out.note(""),e.out.note(` Enter this code at ${a.verification_uri}: ${a.user_code}`),e.out.note("");let r=Math.max(1,a.interval??5),i=e.io.now()+(a.expires_in??900)*1e3;for(;e.io.now()<i;){await e.io.sleep(r*1e3);let o;try{o=await e.io.fetch("https://github.com/login/oauth/access_token",n({client_id:t,device_code:a.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"){r+=5;continue}if(l.error==="expired_token")break;throw l.error==="access_denied"?new h("access_denied","the sign-in was denied on GitHub"):new h("github_denied",`GitHub's sign-in ended: ${l.error??o.status}`)}}throw new h("expired_code","GitHub's code expired before it was entered",{hint:"run the command again"})}async function $h(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 h("github_unavailable",`GitHub did not list your installations: ${n.status}`);let s=(await n.json()).installations.map(r=>({id:r.id,account:r.account.login}));if(s.length===0)throw new h("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 a=e.flags.installation;if(a!==void 0){let r=s.find(i=>String(i.id)===a||i.account.toLowerCase()===a.toLowerCase());if(!r)throw new h("no_such_installation",`you reach no installation ${JSON.stringify(a)}: ${s.map(i=>i.account).join(", ")}`);return r}if(s.length===1)return s[0];if(!e.interactive)throw m("which installation? --installation <account>",`yours: ${s.map(r=>r.account).join(", ")}`);for(s.forEach((r,i)=>e.out.note(` ${i+1}) ${r.account}`));;){let r=Number((await e.io.prompt(`Which account? [1-${s.length}] `)).trim());if(Number.isInteger(r)&&r>=1&&r<=s.length)return s[r-1]}}var Kl={name:"github:list",summary:"the GitHub accounts linked to the environment, and who linked each",scope:"environment",async run(e){let t=await y(e),{installations:n}=await q(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}])})}},Ul={name:"github:repos",summary:"the repositories machines of the environment may be built from",scope:"environment",async run(e){let t=await y(e),{repositories:n}=await q(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":""}]))}},zl={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 y(e),{installations:n}=await q(e,t,"GET","/machines/github/installations"),s=n.find(a=>String(a.installation_id)===e.args[0]||a.account.toLowerCase()===e.args[0].toLowerCase());if(!s)throw new h("no_such_installation",`no linked GitHub account ${JSON.stringify(e.args[0])}`);await w(e,t,`unlink the GitHub account ${s.account}`),await q(e,t,"DELETE",`/machines/github/installations/${s.installation_id}`),e.out.result({unlinked:s},()=>e.out.line(`Unlinked ${s.account}.`))}};import{createHash as Mf}from"node:crypto";import{lstat as qf,mkdir as Kf,mkdtemp as Uf,readdir as pr,readFile as dr,rm as ad,rmdir as zf,writeFile as Gf}from"node:fs/promises";import{tmpdir as Bf}from"node:os";import{dirname as Jf,join as qe,relative as As,resolve as td,sep as Ps}from"node:path";function Rh(e){return e.replace(/\s+/g," ").trim()}var zP=String.raw`
35
35
  import Foundation
36
36
 
37
37
  /// Any JSON value: what a contract leaves open.
@@ -287,10 +287,10 @@ public enum PalbaseJSON {
287
287
  date.formatted(Date.ISO8601FormatStyle(includingFractionalSeconds: true))
288
288
  }
289
289
  }
290
- `;function Xo(e){if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(e.environment))throw new Error(`environment ${JSON.stringify(e.environment)} is not an environment's slug`);let t=Gc(e.document),n=[];n.push(`// Code generated by palbase apps:sync for the environment ${JSON.stringify(e.environment)} \u2014 DO NOT EDIT.`),n.push("import Foundation"),n.push("");for(let s of e.backends)n.push(s);n.push("#if canImport(Palbase)"),n.push("extension PalbaseConfiguration {"),n.push(` /// The environment this build was made for: ${Uc(e.environment)}.`),n.push(" public static let environment: PalbaseConfiguration = {"),n.push(` let document = ${t}"""`);for(let s of e.document.replace(/\s+$/,"").split(`
290
+ `;function Bl(e){if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(e.environment))throw new Error(`environment ${JSON.stringify(e.environment)} is not an environment's slug`);let t=Th(e.document),n=[];n.push(`// Code generated by palbase apps:sync for the environment ${JSON.stringify(e.environment)} \u2014 DO NOT EDIT.`),n.push("import Foundation"),n.push("");for(let s of e.backends)n.push(s);n.push("#if canImport(Palbase)"),n.push("extension PalbaseConfiguration {"),n.push(` /// The environment this build was made for: ${Rh(e.environment)}.`),n.push(" public nonisolated static let environment: PalbaseConfiguration = {"),n.push(` let document = ${t}"""`);for(let s of e.document.replace(/\s+$/,"").split(`
291
291
  `))n.push(s===""?"":` ${s}`);return n.push(` """${t}`),n.push(" do {"),n.push(" return try PalbaseConfiguration(clientDocument: Data(document.utf8))"),n.push(" } catch {"),n.push(` fatalError("palbase/environments/${e.environment.replace(/[^A-Za-z0-9._-]/g,"_")}: \\(error)")`),n.push(" }"),n.push(" }()"),n.push("}"),n.push("#endif"),n.push(""),n.join(`
292
- `)}function Gc(e){let t=1;for(;e.includes(`"${"#".repeat(t)}`)||e.includes(`\\${"#".repeat(t)}`);)t++;return"#".repeat(t)}function Yo(e){let t='"';for(let n of e){let s=n.codePointAt(0);n==="\\"?t+="\\\\":n==='"'?t+='\\"':n==="$"?t+="\\$":n===`
293
- `?t+="\\n":n==="\r"?t+="\\r":n===" "?t+="\\t":s<32||s===127||s===8232||s===8233||s>=55296&&s<=57343?t+=`\\u${s.toString(16).padStart(4,"0")}`:t+=n}return`${t}"`}var fS=String.raw`
292
+ `)}function Th(e){let t=1;for(;e.includes(`"${"#".repeat(t)}`)||e.includes(`\\${"#".repeat(t)}`);)t++;return"#".repeat(t)}function Jl(e){let t='"';for(let n of e){let s=n.codePointAt(0);n==="\\"?t+="\\\\":n==='"'?t+='\\"':n==="$"?t+="\\$":n===`
293
+ `?t+="\\n":n==="\r"?t+="\\r":n===" "?t+="\\t":s<32||s===127||s===8232||s===8233||s>=55296&&s<=57343?t+=`\\u${s.toString(16).padStart(4,"0")}`:t+=n}return`${t}"`}var FP=String.raw`
294
294
  @file:Suppress("unused")
295
295
  @file:OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
296
296
 
@@ -521,84 +521,84 @@ 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 Me=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",Ue=class extends Error{kind;constructor(t,n){super(n),this.kind=t,this.name=`CodegenError.${t}`}},Jc=()=>new Ue("InvalidRoot","OpenAPI root must be an object"),zc=()=>new Ue("MissingPaths","OpenAPI has no object-valued `paths`"),hr=e=>new Ue("InvalidRoles",`Roles artifact is not valid JSON: ${e}`),ll=e=>new Ue("SchemaLoss",`Schema cannot be generated without loss: ${e}`);function K(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 xt(e,t){return e<t?-1:e>t?1:0}function yt(e,t){return[...e].sort((n,s)=>xt(t(n),t(s)))}function Xc(e,t=new Me){if(!K(e))throw Jc();let n=I(e,"paths");if(!K(n))throw zc();let s=Object.entries(n).flatMap(([r,a])=>K(a)?Object.entries(a).flatMap(([i,o])=>{if(!K(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:rg(r),input:tg(o,p),output:ng(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:sg(o)}]}):[]);return yt(s,r=>r.operationId)}function Yc(e,t){let n=I(e,"x-palbase-errors");if(!K(n))return[];let s=I(e,"responses"),r=Object.entries(n).flatMap(([a,i])=>{if(!K(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?eg(K(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(!K(t))return;let n=ke(t,"bucket")??"",s=ke(t,"pathTemplate")??"";return n===""||s===""?void 0:{bucket:n,pathTemplate:s}}function eg(e,t,n,s){let r=e===void 0?void 0:I(e,String(t));if(!K(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(K).find(l=>{let p=I(l,"properties"),u=K(p)?I(p,"error"):void 0;return K(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(!K(n))return;let s=I(n,"data");return K(s)?ft(s,t):void 0}function tg(e,t){let n=I(e,"requestBody");return K(n)?pl(I(n,"content"),t):void 0}function ng(e,t){let n=I(e,"responses");if(!K(n))return;let s=["200","201",...Object.keys(n).filter(r=>r.startsWith("2")&&r!=="200"&&r!=="201").sort(xt)];for(let r of s){let a=I(n,r),i=K(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(!K(e))return;let t=I(e,"application/json");if(!K(t))return;let n=I(t,"schema");return K(n)?n:void 0}function ul(e){return e.toLowerCase()==="idempotency-key"}function sg(e){let t=I(e,"parameters");return Array.isArray(t)&&t.some(n=>K(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(!K(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=K(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 rg(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(K(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:K(d)?ft(d,t,n+1,s):{kind:"any",nullable:!1},nullable:o}}case"object":return sl(e,o,t,n,s);default:return K(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=K(a)?a:{},o=Object.keys(i),l=I(e,"additionalProperties"),p=K(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(xt).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(xt).map(y=>{let v=i[y];return{name:y,schema:K(v)?ft(v,n,s+1,r+y.length):{kind:"any",nullable:!1},required:d.has(y)}}),nullable:t}}function ag(e,t=new Me){if(!K(e))return[];let n=I(e,"x-palbase-room");if(!K(n))return[];let s=Object.entries(n).flatMap(([r,a])=>K(a)?[{pattern:r,params:ig(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 ig(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(!K(e))return[];let s=Object.entries(e).flatMap(([r,a])=>{if(!K(a))return[];let i=og(a,t)??a;return[{name:r,schema:ft(i,{...n,through:n.through+r.length})}]});return yt(s,r=>r.name)}function og(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(!K(s)||!Object.hasOwn(s,a))return;s=s[a]}return K(s)?s:void 0}var lg=/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;function pg(e,t){if(!lg.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 gl(e,t){let n=t.package??Bc;pg(t,n);let s=new Me,r=dg(n,t,Xc(e,s),ag(e,s)),a=$g(e);return a===void 0?r:r+Rg(a)}function dg(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)=>{cg(o,p),u!==n.length-1&&o.push("")}),n.length>0&&o.push(""),o.push(`// Namespaced calls (pb.${t.client}.*)`),o.push("");let l=ug(o,t);if(mg(o,n,l),s.length>0){o.push(""),o.push("// Rooms (pb.join)");for(let p of s)o.push(""),bg(o,p)}return Ag(o.join(`
524
+ `;var He=class{spent=0;charge(t,n,s,a){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+a,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 Eh="studio.palbase.generated",Xe=class extends Error{kind;constructor(t,n){super(n),this.kind=t,this.name=`CodegenError.${t}`}},Ch=()=>new Xe("InvalidRoot","OpenAPI root must be an object"),jh=()=>new Xe("MissingPaths","OpenAPI has no object-valued `paths`"),Xa=e=>new Xe("InvalidRoles",`Roles artifact is not valid JSON: ${e}`),np=e=>new Xe("SchemaLoss",`Schema cannot be generated without loss: ${e}`);function J(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Wt(e){return e===null||typeof e=="string"||typeof e=="number"||typeof e=="boolean"}function K(e,t){return Object.hasOwn(e,t)?e[t]:void 0}function Ee(e,t){let n=K(e,t);return typeof n=="string"?n:void 0}function Pn(e){return typeof e=="boolean"?e:typeof e!="string"?null:Fl(e,"true")?!0:Fl(e,"false")?!1:null}function Fl(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++){let s=e.charAt(n),a=t.charAt(n);if(s!==a&&s.toUpperCase()!==a.toUpperCase()&&s.toLowerCase()!==a.toLowerCase())return!1}return!0}function Oh(e){return typeof e=="number"?e:typeof e!="string"?null:Lh(e)}var xh=/^[\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 Lh(e){let t=xh.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[a,r=""]=t[5].split("."),i=BigInt(`0x${a}${r}`);s=i===0n?0:Dh(i,Number(t[6])-4*r.length)}return n?-s:s}function Dh(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),a=s-t;if(a<=0)return Number(e)*2**t;let r=e>>BigInt(a),i=e-(r<<BigInt(a)),o=1n<<BigInt(a-1);return(i>o||i===o&&(r&1n)===1n)&&(r+=1n),Number(r)*2**s}function Vt(e,t){return e<t?-1:e>t?1:0}function Rt(e,t){return[...e].sort((n,s)=>Vt(t(n),t(s)))}function Ih(e,t=new He){if(!J(e))throw Ch();let n=K(e,"paths");if(!J(n))throw jh();let s=Object.entries(n).flatMap(([a,r])=>J(r)?Object.entries(r).flatMap(([i,o])=>{if(!J(o))return[];let l=Ee(o,"operationId");if(l===void 0||l==="")return[];let p={where:`operation ${Te(l)}`,budget:t,through:l.length};return[{operationId:l,method:i.toUpperCase(),path:a,pathParameters:Bh(a),input:Uh(o,p),output:zh(o,p),headers:Vl(o,"header",p),query:Vl(o,"query",p),errors:Nh(o,p),upload:qh(o),sse:Object.hasOwn(o,"x-palbase-sse"),idempotent:Gh(o)}]}):[]);return Rt(s,a=>a.operationId)}function Nh(e,t){let n=K(e,"x-palbase-errors");if(!J(n))return[];let s=K(e,"responses"),a=Object.entries(n).flatMap(([r,i])=>{if(!J(i))return[];let o=K(i,"status"),l=(Wt(o)?Oh(o):null)??0,p=Ee(i,"code")??"";if(l===0||Number.isNaN(l)||p==="")return[];let d=Mh(l),c=K(i,"hasData"),g=(Wt(c)?Pn(c):null)??!1;return[{name:r,code:p,status:d,description:Ee(i,"description")??"",data:g?Kh(J(s)?s:void 0,d,p,{...t,through:t.through+r.length}):void 0}]});return Rt(a,r=>r.name)}function Mh(e){return Number.isNaN(e)?0:e>=2147483647?2147483647:e<=-2147483648?-2147483648:Math.trunc(e)+0}function qh(e){let t=K(e,"x-palbase-upload");if(!J(t))return;let n=Ee(t,"bucket")??"",s=Ee(t,"pathTemplate")??"";return n===""||s===""?void 0:{bucket:n,pathTemplate:s}}function Kh(e,t,n,s){let a=e===void 0?void 0:K(e,String(t));if(!J(a))return;let r=ap(K(a,"content"));if(r===void 0)return;let i=K(r,"oneOf");if(Array.isArray(i)){let o=i.filter(J).find(l=>{let p=K(l,"properties"),d=J(p)?K(p,"error"):void 0;return J(d)&&Ee(d,"const")===n});return o===void 0?void 0:Wl(o,s)}return Wl(r,s)}function Wl(e,t){let n=K(e,"properties");if(!J(n))return;let s=K(n,"data");return J(s)?At(s,t):void 0}function Uh(e,t){let n=K(e,"requestBody");return J(n)?sp(K(n,"content"),t):void 0}function zh(e,t){let n=K(e,"responses");if(!J(n))return;let s=["200","201",...Object.keys(n).filter(a=>a.startsWith("2")&&a!=="200"&&a!=="201").sort(Vt)];for(let a of s){let r=K(n,a),i=J(r)?sp(K(r,"content"),t):void 0;if(i!==void 0)return i}}function sp(e,t){let n=ap(e);if(!(n===void 0||Object.hasOwn(n,"$ref")))return At(n,t)}function ap(e){if(!J(e))return;let t=K(e,"application/json");if(!J(t))return;let n=K(t,"schema");return J(n)?n:void 0}function rp(e){return e.toLowerCase()==="idempotency-key"}function Gh(e){let t=K(e,"parameters");return Array.isArray(t)&&t.some(n=>J(n)&&Ee(n,"in")==="header"&&rp(Ee(n,"name")??""))}function Vl(e,t,n){let s=K(e,"parameters");if(!Array.isArray(s))return;let a=s.flatMap(r=>{if(!J(r))return[];if(Ee(r,"in")!==t)return[];let i=Ee(r,"name");if(i===void 0||i==="")return[];if(t==="header"&&rp(i))return[];let o=K(r,"schema"),l=J(o)?At(o,{...n,through:n.through+i.length}):{kind:"string",nullable:!1},p=K(r,"required"),d=(Wt(p)?Pn(p):null)??!1;return[{name:i,schema:l,required:d}]});return a.length===0?void 0:{kind:"object",properties:Rt(a,r=>r.name),nullable:!1}}function Bh(e){return[...e.matchAll(/\{([^{}]+)\}/g)].map(t=>t[1]).filter(t=>t!=="")}function Hl(e){let t=K(e,"type");return Array.isArray(t)?t.filter(n=>typeof n=="string"):void 0}function At(e,t,n=0,s=t.through){let a=K(e,"properties"),r=K(e,"enum");t.budget.charge(t.where,n,s,Xl(J(a)?Object.keys(a):[])+Xl(Array.isArray(r)?r:[]));let i=K(e,"nullable"),o=(Wt(i)?Pn(i):null)??!1;Hl(e)?.includes("null")&&(o=!0);let l=K(e,"enum");if(Array.isArray(l)){let c=l.filter(f=>typeof f=="string"),g=l.filter(f=>f===null).length;if(c.length>0&&c.length+g===l.length)return{kind:"enum",values:c,nullable:o||g>0}}let p=K(e,"type"),d=typeof p=="string"?p:"";if(d==="")for(let c of Hl(e)??[])c==="null"?o=!0:d===""&&(d=c);switch(d){case"string":case"number":case"integer":case"boolean":return{kind:d,nullable:o};case"array":{let c=K(e,"items");return{kind:"array",element:J(c)?At(c,t,n+1,s):{kind:"any",nullable:!1},nullable:o}}case"object":return Yl(e,o,t,n,s);default:return J(K(e,"properties"))||Object.hasOwn(e,"additionalProperties")?Yl(e,o,t,n,s):{kind:"any",nullable:o}}}function Xl(e){return e.reduce((t,n)=>t+(typeof n=="string"?n.length:0),0)}function Yl(e,t,n,s,a){let r=K(e,"properties"),i=J(r)?r:{},o=Object.keys(i),l=K(e,"additionalProperties"),p=J(l)&&Object.keys(l).length>0?l:void 0;if(o.length===0)return Wt(l)&&Pn(l)===!1?{kind:"object",properties:[],nullable:t}:p!==void 0?{kind:"map",values:At(p,n,s+1,a),nullable:t}:{kind:"map",values:{kind:"any",nullable:!1},nullable:t};if(p!==void 0)throw np(`an object with declared properties (${[...o].sort(Vt).join(", ")}) also declares a typed additionalProperties`);let d=K(e,"required"),c=new Set(Array.isArray(d)?d.filter(f=>typeof f=="string"):[]);return{kind:"object",properties:[...o].sort(Vt).map(f=>{let b=i[f];return{name:f,schema:J(b)?At(b,n,s+1,a+f.length):{kind:"any",nullable:!1},required:c.has(f)}}),nullable:t}}function Jh(e,t=new He){if(!J(e))return[];let n=K(e,"x-palbase-room");if(!J(n))return[];let s=Object.entries(n).flatMap(([a,r])=>J(r)?[{pattern:a,params:Fh(a),events:Zl(K(r,"events"),e,{where:`room ${Te(a)}`,budget:t,through:a.length}),messages:Zl(K(r,"messages"),e,{where:`room ${Te(a)}`,budget:t,through:a.length})}]:[]);return Rt(s,a=>a.pattern)}function Fh(e){return e.split(":").filter(t=>t.length>2&&t.startsWith("{")&&t.endsWith("}")).map(t=>t.slice(1,-1))}function Zl(e,t,n){if(!J(e))return[];let s=Object.entries(e).flatMap(([a,r])=>{if(!J(r))return[];let i=Wh(r,t)??r;return[{name:a,schema:At(i,{...n,through:n.through+a.length})}]});return Rt(s,a=>a.name)}function Wh(e,t){let n=Ee(e,"$ref");if(n===void 0||!n.startsWith("#/"))return;let s=t;for(let a of n.slice(2).split("/")){let r=a.replaceAll("~1","/").replaceAll("~0","~");if(!J(s)||!Object.hasOwn(s,r))return;s=s[r]}return J(s)?s:void 0}var Vh=/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;function Hh(e,t){if(!Vh.test(t)||t.split(".").some(n=>fp.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(ip.test(n))throw new Error(`header line ${JSON.stringify(n)} breaks the comment`)}var ip=/[\x00-\x08\x0a-\x1f\x7f\x85\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/;function op(e,t){let n=t.package??Eh;Hh(t,n);let s=new He,a=Xh(n,t,Ih(e,s),Jh(e,s)),r=ug(e);return r===void 0?a:a+mg(r)}function Xh(e,t,n,s){let a=n.some(p=>p.sse&&p.output!==void 0),r=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"),r&&o.push("import studio.palbase.realtime.PBRoomError"),a&&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,d)=>{Zh(o,p),d!==n.length-1&&o.push("")}),n.length>0&&o.push(""),o.push(`// Namespaced calls (pb.${t.client}.*)`),o.push("");let l=Yh(o,t);if(eg(o,n,l),s.length>0){o.push(""),o.push("// Rooms (pb.join)");for(let p of s)o.push(""),ag(o,p)}return cg(o.join(`
525
525
  `))+`
526
- `}function ug(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 cg(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(""),gg(e,t)}function It(e,t,n){switch(n.kind){case"object":ml(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 ml(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":ml(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 gg(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 == ${_g(r.status)} && response.code == ${te(r.code)}`;if(r.data===void 0)e.push(` ${a} -> ${ye(r.name)}`);else{let i=Sg(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(xt).map(t=>e.children.get(t))}function mg(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)=>{hg(e,p),u!==l.length-1&&e.push("")}),e.length>0&&e[e.length-1]===""&&e.pop(),e.push("}")}}function hg(e,t){if(t.upload!==void 0){yg(e,t);return}let n=Bn(t),s=t.method===""?"POST":t.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Ue("InvalidName",`operation ${Se(t.operationId)}: the method ${Se(s)} is not an HTTP method's name`);for(let p of t.headers?.properties??[])if(!vg.test(p.name))throw new Ue("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 fg=["file","filename","contentType","onProgress"];function yg(e,t){let n=Bn(t);for(let i of t.pathParameters)if(fg.includes(_e(i)))throw new Ue("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 bg(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() = ${wg(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 wg(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 vg=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function _g(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 Ue("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 Sg(e,t){return e.endsWith(t)?e.slice(0,-t.length):e}var kg=/[\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(kg,_l)}function Pg(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 Ag(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 $g(e){if(!K(e))throw hr("the contract is not a JSON object");return Object.hasOwn(e,"x-palbase-roles")?e["x-palbase-roles"]:void 0}function Rg(e){if(!K(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(!K(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(xt);return ol(r,a.map(i=>({key:i,doc:void 0}))),r.push("}"),r.join(`
526
+ `}function Yh(e,t){let n=`PB${Pe(t.client)}Backend`;return e.push(`val PalbaseClient.${Re(t.client)}: ${n}`),e.push(` get() = ${n}(palbe(${ae(t.machine)}))`),e.push(""),e.push(`class ${n} internal constructor(override val _pb: PalBackendClient) : PBBackend`),n}function Zh(e,t){let n=rs(t),s=!1;t.input!==void 0&&(Ft(e,`${n}Request`,t.input),s=!0),t.output!==void 0&&(s&&e.push(""),Ft(e,`${n}Response`,t.output),s=!0),t.headers!==void 0&&t.upload===void 0&&(s&&e.push(""),Ft(e,`${n}Headers`,t.headers),s=!0),t.query!==void 0&&(s&&e.push(""),Ft(e,`${n}Query`,t.query),s=!0),s&&e.push(""),Qh(e,t)}function Ft(e,t,n){switch(n.kind){case"object":lp(e,t,n,0);return;case"array":{let s=[],a=Pt(n.element,`${t}Item`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = List<${a}>`);return}case"enum":pp(e,t,n,0);return;case"map":{let s=[],a=Pt(n.values,`${t}Value`,s);e.push(...s),s.length>0&&e.push(""),e.push(`typealias ${t} = Map<String, ${a}>${n.nullable?"?":""}`);return}default:e.push(`typealias ${t} = ${Pt(n,t,[])}`)}}function lp(e,t,n,s){let a=" ".repeat(s),r=[],i=up(n.properties).map(o=>{let l=Pt(o.property.schema,t+Pe(o.property.name),r,!o.property.required);return{field:o,type:l,defaultNull:!o.property.required||o.property.schema.nullable}});r.length>0&&(e.push(...r.map(o=>a+o)),e.push("")),e.push(`${a}@Serializable`),e.push(`${a}data class ${t}(`);for(let{field:o,type:l,defaultNull:p}of i)e.push(`${a} @SerialName(${ae(o.property.name)})`),e.push(`${a} val ${o.identifier}: ${l}${p?" = null":""},`);e.push(`${a})`)}function Pt(e,t,n,s=!1){let a;switch(e.kind){case"string":a="String";break;case"number":a="Double";break;case"integer":a="Int";break;case"boolean":a="Boolean";break;case"any":a="kotlinx.serialization.json.JsonElement";break;case"enum":pp(n,`${t}Value`,e,0),a=`${t}Value`;break;case"object":lp(n,t,e,0),a=t;break;case"array":a=`List<${Pt(e.element,`${t}Item`,n)}>`;break;case"map":a=`Map<String, ${Pt(e.values,`${t}Value`,n)}>`;break}return(e.nullable||s)&&!a.endsWith("?")?`${a}?`:a}function pp(e,t,n,s){let a=" ".repeat(s);e.push(`${a}@Serializable`),e.push(`${a}enum class ${t}(val wireValue: String) {`);let r=new Map;for(let i of n.values){let o=mp(i),l=(r.get(o)??0)+1;r.set(o,l);let p=l===1?o:`${o}_${l}`;e.push(`${a} @SerialName(${ae(i)})`),e.push(`${a} ${p}(${ae(i)}),`)}e.push(`${a}}`)}function Qh(e,t){let n=`${rs(t)}Error`;e.push(`sealed class ${n}(message: String, cause: Throwable? = null) : Exception(message, cause) {`);let s=new Map;for(let a of t.errors){if(a.data===void 0)continue;let r=[];s.set(a.name,Pt(a.data,`${Pe(a.name)}Data`,r)),e.push(...r.map(i=>` ${i}`)),e.push("")}for(let a of t.errors){let r=Pe(a.name);a.data===void 0?e.push(` data object ${r} : ${n}(${ae(a.code)})`):e.push(` data class ${r}(val data: ${s.get(a.name)}) : ${n}(${ae(a.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 a of t.errors){let r=`response.status == ${og(a.status)} && response.code == ${ae(a.code)}`;if(a.data===void 0)e.push(` ${r} -> ${Pe(a.name)}`);else{let i=lg(s.get(a.name),"?");e.push(` ${r} ->`),e.push(` response.decodeData(${$n(a.data,i)})?.let(::${Pe(a.name)}) ?: Other(backend)`)}}e.push(" else -> Other(backend)"),e.push(" }"),e.push(" }")}e.push(" }"),e.push("}")}function Ql(e){return[...e.children.keys()].sort(Vt).map(t=>e.children.get(t))}function eg(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 as(o).slice(0,-1)){let d=l.children.get(p);d===void 0&&(d={path:[...l.path,p],children:new Map,operations:[]},l.children.set(p,d)),l=d}l.operations.push(o)}e.push("");let a=Ql(s);a.forEach((o,l)=>{let p=Re(o.path[o.path.length-1]);e.push(`val ${n}.${p}: ${Sn(o.path)}`),e.push(` get() = ${Sn(o.path)}(_pb)`),l!==a.length-1&&e.push("")}),e.push("");let r=[...a],i=!0;for(;r.length>0;){let o=r.shift();i||e.push(""),i=!1,e.push(`class ${Sn(o.path)} internal constructor(private val client: PalBackendClient) {`);for(let p of Ql(o)){let d=Re(p.path[p.path.length-1]);e.push(` val ${d}: ${Sn(p.path)}`),e.push(` get() = ${Sn(p.path)}(client)`),e.push(""),r.push(p)}let l=Rt(o.operations,p=>p.operationId);l.forEach((p,d)=>{tg(e,p),d!==l.length-1&&e.push("")}),e.length>0&&e[e.length-1]===""&&e.pop(),e.push("}")}}function tg(e,t){if(t.upload!==void 0){sg(e,t);return}let n=rs(t),s=t.method===""?"POST":t.method;if(!/^[a-z][a-z0-9]*$/.test(s.toLowerCase()))throw new Xe("InvalidName",`operation ${Te(t.operationId)}: the method ${Te(s)} is not an HTTP method's name`);for(let p of t.headers?.properties??[])if(!ig.test(p.name))throw new Xe("InvalidName",`operation ${Te(t.operationId)}: the header ${Te(p.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);let a=[];for(let p of t.pathParameters)a.push(`${Re(p)}: String`);t.input!==void 0&&a.push(`input: ${n}Request`),t.query!==void 0&&a.push(`query: ${n}Query`),t.headers!==void 0&&a.push(`headers: ${n}Headers`);let r=t.sse&&t.output!==void 0,i=r?`: Flow<${n}Response>`:t.output!==void 0?`: ${n}Response`:"",o=r?"streamGenerated":"invokeGenerated",l=as(t);e.push(` suspend fun ${Re(l[l.length-1])}(${a.join(", ")})${i} = try {`),e.push(` client.${o}(`),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${ae(s)},`),e.push(` path = ${cp(t)},`),t.query!==void 0&&e.push(...Ya("query",t.query,"query",4)),t.headers!==void 0&&e.push(...Ya("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(` ${$n(t.output,`${n}Response`)},`),e.push(" )"),e.push(" } catch (error: BackendError) {"),e.push(` throw ${n}Error.from(error)`),e.push(" }")}var ng=["file","filename","contentType","onProgress"];function sg(e,t){let n=rs(t);for(let i of t.pathParameters)if(ng.includes(Re(i)))throw new Xe("InvalidName",`operation ${Te(t.operationId)}: the path parameter ${Te(i)} is the name of the upload's ${Re(i)}: rename it in the backend`);let s=[];for(let i of t.pathParameters)s.push(`${Re(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 a=t.output!==void 0?`: ${n}Response`:"",r=as(t);e.push(` suspend fun ${Re(r[r.length-1])}(${s.join(", ")})${a} = try {`),e.push(" client.uploadGenerated("),e.push(` GeneratedRequest${t.input===void 0?"<Unit>":""}(`),e.push(` method = ${ae("POST")},`),e.push(` path = ${cp(t)},`),t.query!==void 0&&e.push(...Ya("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(` ${$n(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 ag(e,t){let n=dp(t),s=t.events.length===0?"Nothing":`${n}RoomEvent`,a=t.messages.length===0?"Nothing":`${n}RoomSend`;for(let i of t.events)Ft(e,Jt(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 ${Pe(i.name)}(val payload: ${Jt(t,i,"Event")}) : ${n}RoomEvent`);e.push("}"),e.push("")}for(let i of t.messages)Ft(e,Jt(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 ${Pe(i.name)}(val payload: ${Jt(t,i,"Message")}) : ${n}RoomSend`);e.push("}"),e.push("")}let r=t.params.map(i=>` private val ${Re(i)}: String,`);if(r.length===0?e.push(`class ${n}Room : PBRoom<${s}, ${a}> {`):(e.push(`class ${n}Room(`),e.push(...r),e.push(`) : PBRoom<${s}, ${a}> {`)),e.push(` override val pattern: String = ${ae(t.pattern)}`),e.push(` override val eventNames: List<String> = listOf(${t.events.map(i=>ae(i.name)).join(", ")})`),e.push(` override val topic: String get() = ${rg(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(` ${ae(i.name)} -> ${n}RoomEvent.${Pe(i.name)}(`),e.push(` palbaseGeneratedJson.decodeFromJsonElement(${Jt(t,i,"Event")}.serializer(), payload),`),e.push(" )");e.push(" else -> null"),e.push(" }")}if(e.push(""),e.push(` override fun encodeMessage(message: ${a}): 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.${Pe(i.name)} -> ${ae(i.name)} to`),e.push(` palbaseGeneratedJson.encodeToJsonElement(${Jt(t,i,"Message")}.serializer(), message.payload).jsonObject`);e.push(" }")}e.push("}")}function dp(e){return e.pattern.split(":").filter(t=>!(t.startsWith("{")&&t.endsWith("}"))).filter(t=>t!=="").map(Pe).join("")}function Jt(e,t,n){return dp(e)+Pe(t.name)+n}function rg(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?ae(n):Re(s)}).join(` + ${ae(":")} + `)}function cp(e){let t=new Set(e.pathParameters);return`listOf(${e.path.split("/").filter(s=>s!=="").map(s=>{let a=s.startsWith("{")&&s.endsWith("}")?s.slice(1,-1):void 0,r=a!==void 0&&t.has(a)?a:void 0;return r===void 0?`PathPart.Literal(${ae(s)})`:`PathPart.Parameter(${Re(r)})`}).join(", ")})`}function Ya(e,t,n,s){let a=" ".repeat(s),r=[`${a}${e} = buildMap {`];for(let i of up(t.properties)){let o=`${n}.${i.identifier}`;!i.property.required||i.property.schema.nullable?r.push(`${a} ${o}?.let { value -> put(${ae(i.property.name)}, ${ep("value",i.property.schema)}) }`):r.push(`${a} put(${ae(i.property.name)}, ${ep(o,i.property.schema)})`)}return r.push(`${a}},`),r}var ig=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;function og(e){return e===-2147483648?"(-2147483647 - 1)":String(e)}function ep(e,t){switch(t.kind){case"string":return e;case"enum":return`${e}.wireValue`;case"array":return`${e}.joinToString(",")`;default:return`${e}.toString()`}}function $n(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(${$n(e.element,`${t}Item`)})`;break;case"map":n=`MapSerializer(String.serializer(), ${$n(e.values,`${t}Value`)})`;break}return e.nullable?`${n}.nullable`:n}function up(e){let t=new Map;return e.map(n=>{let s=Re(n.name),a=s.length>=2&&s.startsWith("`")&&s.endsWith("`")?s.slice(1,-1):s,r=t.get(a);if(r!==void 0)throw np(`wire keys ${Te(r)} and ${Te(n.name)} both become the Kotlin property \`${a}\``);return t.set(a,n.name),{property:n,identifier:s}})}function as(e){let t=e.operationId.split(".").filter(n=>n!=="");return t.length===0?["op"]:t}function rs(e){return as(e).map(Pe).join("")}function Sn(e){return`PB${e.map(Pe).join("")}Namespace`}function Pe(e){return Za(hp(e,!0),e)}function Re(e){let t=Za(hp(e,!1),e);return fp.has(t)?`\`${t}\``:t}function mp(e){let t=e.replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"").toUpperCase()||"VALUE";return Za(/^[0-9]/.test(t)?`_${t}`:t,e)}function Za(e,t){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)||/^_+$/.test(e))throw new Xe("InvalidName",`${Te(t)} gives ${Te(e)}, which is no Kotlin identifier`);return e}function hp(e,t){let n=e.match(/[A-Za-z0-9]+/g)??[];if(n.length===0)return t?"Op":"op";let s=n.map((a,r)=>(r===0&&!t?a.charAt(0).toLowerCase():a.charAt(0).toUpperCase())+a.slice(1)).join("");return/^[0-9]/.test(s)?`_${s}`:s}function lg(e,t){return e.endsWith(t)?e.slice(0,-t.length):e}var pg=/[\x85\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g,gp=e=>`\\u${e.codePointAt(0).toString(16).padStart(4,"0")}`;function ae(e){return Jl(e).replace(pg,gp)}function dg(e){return e.replace(/\r\n|[\r\n\x85\u2028\u2029]/g," ").replace(new RegExp(ip.source,"g"),gp).replaceAll("*/","* /").replaceAll("/*","/ *").replace(/^(\s*)@/,"$1\\@")}function Te(e){return`"${e.replace(/[\\"\n\r\t]/g,t=>({"\\":"\\\\",'"':'\\"',"\n":"\\n","\r":"\\r"," ":"\\t"})[t])}"`}function cg(e){return e.replace(/[\t\n\v\f\r\x1c-\x1f\p{Zs}\u2028\u2029]+$/u,"")}var fp=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 ug(e){if(!J(e))throw Xa("the contract is not a JSON object");return Object.hasOwn(e,"x-palbase-roles")?e["x-palbase-roles"]:void 0}function mg(e){if(!J(e))throw Xa("root must be an object");let t=K(e,"roles");if(!Array.isArray(t))throw Xa("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(!J(i))return[];let o=Ee(i,"name");if(o===void 0||o==="")return[];let l=K(i,"isDefault"),p=K(i,"permissions");return[{name:o,description:Ee(i,"description"),isDefault:(Wt(l)?Pn(l):null)??!1,permissions:Array.isArray(p)?p.filter(d=>typeof d=="string"&&d!==""):[]}]}),s=Rt(n,i=>i.name);if(s.length===0)return"";let a=[];a.push(""),a.push("// Roles and permissions (generated \u2014 do not edit)"),a.push("//"),a.push("// The roles this environment defines and the permissions they grant. A name"),a.push("// typed by hand is a string the server answers 403 to; a name typed here is a"),a.push("// compile error. Regenerated by `palbase spec` \u2014 a role withdrawn upstream"),a.push("// disappears from this file, so code still naming it stops building rather"),a.push("// than silently losing access."),a.push("enum class PalbaseRole(val key: String) {"),tp(a,s.map(i=>({key:i.name,doc:i.description??(i.isDefault?"Assigned to every new user at signup.":void 0)}))),a.push("}"),a.push(""),a.push("enum class PalbasePermission(val key: String) {");let r=[...new Set(s.flatMap(i=>i.permissions))].sort(Vt);return tp(a,r.map(i=>({key:i,doc:void 0}))),a.push("}"),a.join(`
527
527
  `)+`
528
- `}function ol(e,t){let n=new Map;for(let{key:s,doc:r}of t){let a=Tg(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(` /** ${Pg(r)} */`),e.push(` ${a}(${te(s)}),`)}}function Tg(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){Cg(t);let n=new Me,s=xg(e,n),r=Fg(e,n),a="PB"+Ee(t.client)+"Backend";jg(s,t.client,a);let i="";for(let o of t.header)i+="// "+o+`
528
+ `}function tp(e,t){let n=new Map;for(let{key:s,doc:a}of t){let r=hg(s),i=n.get(r);if(i!==void 0){e.push(` // codegen: skipped duplicate key ${ae(s)} \u2014 collides with ${ae(i)}`);continue}n.set(r,s),a!==void 0&&e.push(` /** ${dg(a)} */`),e.push(` ${r}(${ae(s)}),`)}}function hg(e){return mp(e)}var yp=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 Pp(e,t){fg(t);let n=new He,s=kg(e,n),a=xg(e,n),r="PB"+Ie(t.client)+"Backend";yg(s,t.client,r);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
532
532
 
533
533
  `,i+=`// MARK: - Request / response types
534
534
 
535
- `,i+=sm(s),i+=`
535
+ `,i+=Gg(s),i+=`
536
536
  // MARK: - Namespaced calls (pb.`+t.client+`.*)
537
537
 
538
538
  `,i+=`public extension PalbaseClient {
539
- `,i+=" nonisolated var "+t.client+": "+a+" { "+a+"(_pb: palbe("+Og(t.machine)+`)) }
539
+ `,i+=" nonisolated var "+t.client+": "+r+" { "+r+"(_pb: palbe("+bg(t.machine)+`)) }
540
540
  `,i+=`}
541
541
 
542
- `,i+="public nonisolated struct "+a+`: PBBackend {
542
+ `,i+="public nonisolated struct "+r+`: PBBackend {
543
543
  `,i+=` public let _pb: PalBackendClient
544
544
  `,i+=`}
545
545
 
546
- `,i+=lm(s,a),r.length>0){i+=`
546
+ `,i+=Vg(s,r),a.length>0){i+=`
547
547
  // MARK: - Rooms (@Room)
548
548
 
549
- `;let o=new Map;for(let l of r)i+=um(l,o)+`
550
- `}return i}var Eg=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 Cg(e){if(!/^[a-z][A-Za-z0-9]*$/.test(e.client)||Eg.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 jg(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 Og(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 Dg=new Intl.Segmenter("en",{granularity:"grapheme"});function Le(e){return Array.from(Dg.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 qt(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 Pl=new WeakMap;function wr(e){let t=Pl.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]]));Pl.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 x(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 Ig(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 Lg(e){return Number.isNaN(e)?0n:e>=2**63?Dl:e<=-(2**63)?Il:BigInt(Math.trunc(e))}function xg(e,t=new Me){let n=x(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=x(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=x(i);if(o!==void 0)for(let[l,p]of He(o)){let u=x(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:Gg(a),input:Mg(u,f),output:Bg(u,f),headers:$l(u,"header",f),query:$l(u,"query",f),errors:Ng(u,f),upload:qg(u),sse:ot(u,"x-palbase-sse"),idempotent:Ug(u)})}}return r.sort((a,i)=>Ve(a.operationID,i.operationID)),r}function Ng(e,t){let n=x(E(e,"x-palbase-errors"));if(n===void 0)return[];let s=x(E(e,"responses")),r=[];for(let[a,i]of He(n)){let o=x(i);if(o===void 0)continue;let l=Ig(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:Lg(l),description:u};d&&(f.data=Kg(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 qg(e){let t=x(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 Kg(e,t,n,s){if(e===void 0)return;let r=x(E(e,t.toString()));if(r===void 0)return;let a=x(E(r,"content"));if(a===void 0)return;let i=x(E(a,"application/json"));if(i===void 0)return;let o=x(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=x(p);if(u===void 0)continue;let d=x(E(u,"properties")),f=d===void 0?void 0:x(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=x(E(e,"properties"));if(n===void 0)return;let s=x(E(n,"data"));if(s!==void 0)return lt(s,s,t)}function Mg(e,t){let n=x(E(e,"requestBody"));if(n!==void 0)return xl(E(n,"content"),t)}var Ll=e=>ee(e).toLowerCase()==="idempotency-key";function Ug(e){return(Ie(E(e,"parameters"))??[]).some(t=>{let n=x(t);return n!==void 0&&ne(pe(E(n,"in"))??"","header")&&Ll(pe(E(n,"name"))??"")})}function $l(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=x(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=x(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 Gg(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 Bg(e,t){let n=x(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=x(E(n,r));if(a===void 0)continue;let i=xl(E(a,"content"),t);if(i!==void 0)return i}}function xl(e,t){let n=x(e);if(n===void 0)return;let s=x(E(n,"application/json"));if(s===void 0)return;let r=x(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=Wg(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=x(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 Jg(a,t,n,s,r)}finally{n.path.delete(a)}}var Rl=e=>e.reduce((t,n)=>t+(pe(n)?.length??0),0);function Jg(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(x).filter(f=>f!==void 0),d=u.filter(f=>!zg(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=x(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=x(E(e,"properties"))??{},o=E(e,"additionalProperties"),l,p=x(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=x(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 zg(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 Wg(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=qt(qt(a,"~1","/"),"~0","~"),o=x(r),l=Ie(r),p=Xg(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 x(r)}function Fg(e,t=new Me){let n=x(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=x(E(n,"x-palbase-room"));if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=x(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:Vg(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 Nl(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 Vg(e){return wt(e,":",!1).flatMap(t=>Nl(t)??[])}function El(e,t,n){let s=x(e);if(s===void 0)return[];let r=[];for(let[a,i]of He(s)){let o=x(i);if(o===void 0)continue;let l=Hg(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 Hg(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=qt(qt(a,"~1","/"),"~0","~"),o=x(r);if(o===void 0||!ot(o,i))return;r=E(o,i)}return x(r)}function Xg(e){if(!/^[+-]?[0-9]+$/.test(e))return;let t=BigInt(e);return t>Dl||t<Il?void 0:t}function me(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||Yg.has(s)?t+="\\u{"+s.toString(16)+"}":t+=n}}return t+'"'}var Yg=new Set([1564,8206,8207,8234,8235,8236,8237,8238,8294,8295,8296,8297]),Hn=e=>wt(e,".",!0),Ee=e=>ql(e,!0);function W(e){let t=ql(e,!1);if(em.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 nm(t)}var Mt=e=>Hn(e).map(Ee).join(""),Xn=e=>Mt(e)+"Request",Kt=e=>Mt(e)+"Response",Yn=e=>Mt(e)+"Error",_r=e=>Mt(e)+"Headers",mn=e=>Mt(e)+"Query";function Zg(e){let t=(n,s)=>Ve(e,n)>=0&&Ve(e,s)<=0;return t("a","z")||t("A","Z")||t("0","9")}function ql(e,t){let n=[],s=[];for(let i of Le(e))Zg(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 Qg=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"]),em=new Set(["break","continue","fallthrough","fileprivate","inout","precedencegroup","rethrows","static","subscript","super","typealias"]),tm=new Set(["Any","Self","Type","Protocol"]);function nm(e){return Qg.has(ee(e))?"`"+e+"`":e}var it=e=>qt(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 sm(e){let t=[];for(let n of e){let s=t.length,r=!1;if(n.input!==void 0&&(De(t,gn(Xn(n.operationID),n.input,!0)),r=!0),n.output!==void 0?(r&&t.push(""),De(t,gn(Kt(n.operationID),n.output)),r=!0):n.upload!==void 0&&(r&&t.push(""),t.push("public nonisolated struct "+Kt(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,im(_r(n.operationID),n.headers)),r=!0}n.query!==void 0&&(r&&t.push(""),De(t,Qn(mn(n.operationID),n.query.props,0)),r=!0),r&&t.push(""),De(t,om(Yn(n.operationID),n.errors)),t.push(""),De(t,rm(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=>Mt(e)+"Endpoint";function Kl(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(me(n))}return"["+t.join(", ")+"]"}function Ml(e){return e.path===""?"/"+qt(e.operationID,".","/"):e.path}function rm(e){if(e.upload!==void 0)return am(e);let t=Zn(e.operationID),n=[];e.sse?(n.push("public nonisolated struct "+t+": PBSseEndpoint {"),n.push(w(1)+"public typealias Element = "+Kt(e.operationID))):e.output!==void 0?(n.push("public nonisolated struct "+t+": PBEndpoint {"),n.push(w(1)+"public typealias Response = "+Kt(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: "+mn(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(),Kl(Ml(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 am(e){let t=[];t.push("public nonisolated struct "+Zn(e.operationID)+": PBUploadEndpoint {"),t.push(w(1)+"public typealias Response = "+Kt(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: "+mn(e.operationID));let n=[".post",Kl(Ml(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 gn(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 im(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=me(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 om(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("+me("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,gn(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 "+me(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 "+me(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("+me("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["+me(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+" = "+me(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)+" = "+me(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("+me("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(tm.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 lm(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(...Ul(a).map(([,i])=>i))}return s.map(a=>pm(a,t)+`
553
- `).join("")}var Ul=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 pm(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 Ul(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=Kt(i.operationID),d="throws("+Yn(i.operationID)+")";if(i.upload!==void 0){De(s,cm(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: "+mn(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(`
549
+ `;let o=new Map;for(let l of a)i+=Yg(l,o)+`
550
+ `}return i}var gg=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 fg(e){if(!/^[a-z][A-Za-z0-9]*$/.test(e.client)||gg.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 os(e,t){let n=yp.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 yg(e,t,n){os(`client ${JSON.stringify(t)}`,n);for(let s of e){let a=cs(s.operationID);for(let r=1;r<a.length;r++)os(`operation ${JSON.stringify(s.operationID)}, in namespace ${JSON.stringify(a.slice(0,r).join("."))},`,Qa(a.slice(0,r),n))}}function bg(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 wg=new Intl.Segmenter("en",{granularity:"grapheme"});function ze(e){return Array.from(wg.segment(e),t=>t.segment)}function se(e){return e.normalize("NFC")}function re(e,t){return se(e)===se(t)}function st(e,t){let n=Array.from(se(e)),s=Array.from(se(t));for(let a=0;a<n.length&&a<s.length;a++){let r=n[a].codePointAt(0)-s[a].codePointAt(0);if(r!==0)return r}return n.length-s.length}var Ap=e=>[...e].sort(st);function Et(e,t,n){let s=[],a="";for(let r of ze(e))re(r,t)?((!n||a!=="")&&s.push(a),a=""):a+=r;return(!n||a!=="")&&s.push(a),s}function Xt(e,t,n){let s=ze(e),a=ze(t),r="";for(let i=0;i<s.length;)i+a.length<=s.length&&a.every((o,l)=>re(s[i+l],o))?(r+=n,i+=a.length):r+=s[i++];return r}var bp=new WeakMap;function er(e){let t=bp.get(e);if(t===void 0){t=new Map;for(let n of Object.keys(e))e[n]!==void 0&&(t.delete(se(n)),t.set(se(n),[n,e[n]]));bp.set(e,t)}return t}var C=(e,t)=>er(e).get(se(t))?.[1],yt=(e,t)=>er(e).has(se(t)),at=e=>[...er(e).values()],tr=e=>at(e).map(([t])=>t);function z(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0}function Ue(e){return Array.isArray(e)?e:void 0}function ue(e){return typeof e=="string"?e:void 0}function ds(e){if(typeof e=="boolean")return e;if(e===1)return!0;if(e===0)return!1}function vg(e){if(typeof e=="number")return e;if(typeof e=="boolean")return e?1:0}var Tt=(e,t=!1)=>({kind:e,nullable:t,props:[],enumVals:[]}),Rp=2n**63n-1n,Tp=-(2n**63n);function _g(e){return Number.isNaN(e)?0n:e>=2**63?Rp:e<=-(2**63)?Tp:BigInt(Math.trunc(e))}function kg(e,t=new He){let n=z(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=z(C(n,"paths"));if(s===void 0)throw new Error("openapi.json has no `paths`");let a=[];for(let[r,i]of at(s)){let o=z(i);if(o!==void 0)for(let[l,p]of at(o)){let d=z(p);if(d===void 0)continue;let c=ue(C(d,"operationId"))??"";if(c==="")continue;let g={where:`operation ${JSON.stringify(c)}`,budget:t,through:c.length,path:new Set};a.push({operationID:c,method:l.toUpperCase(),path:r,pathParams:Tg(r),input:Ag(d,g),output:Eg(d,g),headers:vp(d,"header",g),query:vp(d,"query",g),errors:Sg(d,g),upload:$g(d),sse:yt(d,"x-palbase-sse"),idempotent:Rg(d)})}}return a.sort((r,i)=>st(r.operationID,i.operationID)),a}function Sg(e,t){let n=z(C(e,"x-palbase-errors"));if(n===void 0)return[];let s=z(C(e,"responses")),a=[];for(let[r,i]of at(n)){let o=z(i);if(o===void 0)continue;let l=vg(C(o,"status"))??0,p=ue(C(o,"code"))??"",d=ue(C(o,"description"))??"",c=ds(C(o,"hasData"))??!1;if(p===""||l===0)continue;let g={name:r,code:p,status:_g(l),description:d};c&&(g.data=Pg(s,g.status,p,{...t,through:t.through+r.length})),a.push(g)}return a.sort((r,i)=>st(r.name,i.name)),a}function $g(e){let t=z(C(e,"x-palbase-upload"));if(t===void 0||at(t).length===0)return;let n=ue(C(t,"bucket"))??"",s=ue(C(t,"pathTemplate"))??"";if(!(n===""||s===""))return{bucket:n,pathTemplate:s}}function Pg(e,t,n,s){if(e===void 0)return;let a=z(C(e,t.toString()));if(a===void 0)return;let r=z(C(a,"content"));if(r===void 0)return;let i=z(C(r,"application/json"));if(i===void 0)return;let o=z(C(i,"schema"));if(o===void 0)return;let l=Ue(C(o,"oneOf"));if(l!==void 0){for(let p of l){let d=z(p);if(d===void 0)continue;let c=z(C(d,"properties")),g=c===void 0?void 0:z(C(c,"error"));if(g===void 0)continue;let f=ue(C(g,"const"));if(f!==void 0&&re(f,n))return wp(d,s)}return}return wp(o,s)}function wp(e,t){let n=z(C(e,"properties"));if(n===void 0)return;let s=z(C(n,"data"));if(s!==void 0)return bt(s,s,t)}function Ag(e,t){let n=z(C(e,"requestBody"));if(n!==void 0)return Cp(C(n,"content"),t)}var Ep=e=>se(e).toLowerCase()==="idempotency-key";function Rg(e){return(Ue(C(e,"parameters"))??[]).some(t=>{let n=z(t);return n!==void 0&&re(ue(C(n,"in"))??"","header")&&Ep(ue(C(n,"name"))??"")})}function vp(e,t,n){let s=Ue(C(e,"parameters"));if(s===void 0||s.length===0)return;let a=[];for(let r of s){let i=z(r);if(i===void 0||!re(ue(C(i,"in"))??"",t))continue;let o=ue(C(i,"name"))??"";if(o===""||t==="header"&&Ep(o))continue;let l=ds(C(i,"required"))??!1,p=z(C(i,"schema"));a.push({name:o,schema:p===void 0?Tt("string"):bt(p,p,{...n,through:n.through+o.length}),required:l})}if(a.length!==0)return a.sort((r,i)=>st(r.name,i.name)),{kind:"object",nullable:!1,props:a,enumVals:[]}}function Tg(e){let t=[],n=e;for(;;){let s=n.indexOf("{");if(s<0)break;let a=n.indexOf("}",s);if(a<0)break;let r=n.slice(s+1,a);r!==""&&t.push(r),n=n.slice(a+1)}return t}function Eg(e,t){let n=z(C(e,"responses"));if(n===void 0)return;let s=tr(n).filter(a=>re(ze(a)[0]??"","2")&&!re(a,"200")&&!re(a,"201"));for(let a of["200","201",...Ap(s)]){let r=z(C(n,a));if(r===void 0)continue;let i=Cp(C(r,"content"),t);if(i!==void 0)return i}}function Cp(e,t){let n=z(e);if(n===void 0)return;let s=z(C(n,"application/json"));if(s===void 0)return;let a=z(C(s,"schema"));if(a!==void 0&&!yt(a,"$ref"))return bt(a,a,t)}function bt(e,t,n,s=0,a=n.through){let r=e,i=Og(r,t);if(i!==void 0&&s<16){if(n.path.has(i))throw new Error(`${n.where}: the $ref ${JSON.stringify(ue(C(r,"$ref")))} points back into a schema it is part of, a recursive schema, which no Swift type is written for: refused`);r=i}else if(yt(r,"$ref"))return Tt("any");let o=z(C(r,"properties")),l=(o===void 0?0:_p(tr(o)))+_p(Ue(C(r,"enum"))??[]);n.budget.charge(n.where,s,a,l),n.path.add(r);try{return Cg(r,t,n,s,a)}finally{n.path.delete(r)}}var _p=e=>e.reduce((t,n)=>t+(ue(n)?.length??0),0);function Cg(e,t,n,s,a){let r=ds(C(e,"nullable"))??!1,i=Ue(C(e,"type"));if(i!==void 0)for(let d of i)ue(d)==="null"&&(r=!0);let o=Ue(C(e,"anyOf"))??Ue(C(e,"oneOf"));if(o!==void 0){let d=o.map(z).filter(g=>g!==void 0),c=d.filter(g=>!jg(g));if(d.length===o.length&&c.length===1&&d.length>c.length){let g=bt(c[0],t,n,s+1,a);return g.nullable=!0,g}}let l=Ue(C(e,"enum"));if(l!==void 0){let d=[],c=!0;for(let g of l){let f=ue(g);if(f===void 0){c=!1;break}d.push(f)}if(c&&d.length>0)return{kind:"enum",nullable:r,props:[],enumVals:d}}let p=ue(C(e,"type"))??"";if(p===""&&i!==void 0)for(let d of i){let c=ue(d);c!==void 0&&(c==="null"?r=!0:p===""&&(p=c))}switch(p){case"string":case"number":case"integer":case"boolean":return Tt(p,r);case"array":{let d=z(C(e,"items"));return{kind:"array",nullable:r,props:[],elem:d===void 0?Tt("any"):bt(d,t,n,s+1,a),enumVals:[]}}case"object":return kp(e,r,t,n,s,a);default:return yt(e,"properties")||yt(e,"additionalProperties")?kp(e,r,t,n,s,a):Tt("any",r)}}function kp(e,t,n,s,a,r){let i=z(C(e,"properties"))??{},o=C(e,"additionalProperties"),l,p=z(o);if(p!==void 0?l=bt(p,n,s,a+1,r):(ds(o)===!0||at(i).length===0&&!yt(e,"additionalProperties"))&&(l=Tt("any")),l!==void 0)return{kind:at(i).length===0?"map":"mixedObject",nullable:t,props:[],elem:l,enumVals:[]};let d=new Set;for(let g of Ue(C(e,"required"))??[]){let f=ue(g);f!==void 0&&d.add(se(f))}let c=[];for(let g of Ap(tr(i))){let f=z(C(i,g));c.push({name:g,schema:f===void 0?Tt("any"):bt(f,n,s,a+1,r+g.length),required:d.has(se(g))})}return{kind:"object",nullable:t,props:c,enumVals:[]}}function jg(e){let t=C(e,"type");if(typeof t=="string")return t==="null";let n=Ue(t);if(n!==void 0){let s=n.filter(a=>typeof a=="string");return s.length===1&&s[0]==="null"}return!1}function Og(e,t){let n=ue(C(e,"$ref"));if(n===void 0)return;let s=ze(n);if(!re(s[0]??"","#")||!re(s[1]??"","/"))return;let a=t;for(let r of Et(s.slice(2).join(""),"/",!0)){let i=Xt(Xt(r,"~1","/"),"~0","~"),o=z(a),l=Ue(a),p=Ig(i);if(o!==void 0&&yt(o,i))a=C(o,i);else if(l!==void 0&&p!==void 0&&p>=0n&&p<BigInt(l.length))a=l[Number(p)];else return}return z(a)}function xg(e,t=new He){let n=z(e);if(n===void 0)throw new Error("openapi.json is not valid JSON: root is not an object");let s=z(C(n,"x-palbase-room"));if(s===void 0)return[];let a=[];for(let[r,i]of at(s)){let o=z(i);if(o===void 0)continue;let l={where:`room ${JSON.stringify(r)}`,budget:t,through:r.length,path:new Set};a.push({pattern:r,params:Lg(r),events:Sp(C(o,"events"),n,l),messages:Sp(C(o,"messages"),n,l)})}return a.sort((r,i)=>st(r.pattern,i.pattern)),a}function jp(e){let t=ze(e);return t.length>2&&re(t[0],"{")&&re(t[t.length-1],"}")?t.slice(1,-1).join(""):void 0}function Lg(e){return Et(e,":",!1).flatMap(t=>jp(t)??[])}function Sp(e,t,n){let s=z(e);if(s===void 0)return[];let a=[];for(let[r,i]of at(s)){let o=z(i);if(o===void 0)continue;let l=Dg(o,t)??o;a.push({name:r,schema:bt(l,l,{...n,through:n.through+r.length})})}return a.sort((r,i)=>st(r.name,i.name)),a}function Dg(e,t){let n=ue(C(e,"$ref"));if(n===void 0)return;let s=ze(n);if(!re(s[0]??"","#")||!re(s[1]??"","/"))return;let a=t;for(let r of Et(s.slice(2).join(""),"/",!0)){let i=Xt(Xt(r,"~1","/"),"~0","~"),o=z(a);if(o===void 0||!yt(o,i))return;a=C(o,i)}return z(a)}function Ig(e){if(!/^[+-]?[0-9]+$/.test(e))return;let t=BigInt(e);return t>Rp||t<Tp?void 0:t}function ke(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||Ng.has(s)?t+="\\u{"+s.toString(16)+"}":t+=n}}return t+'"'}var Ng=new Set([1564,8206,8207,8234,8235,8236,8237,8238,8294,8295,8296,8297]),cs=e=>Et(e,".",!0),Ie=e=>Op(e,!0);function Y(e){let t=Op(e,!1);if(Kg.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 zg(t)}var Zt=e=>cs(e).map(Ie).join(""),us=e=>Zt(e)+"Request",Yt=e=>Zt(e)+"Response",ms=e=>Zt(e)+"Error",nr=e=>Zt(e)+"Headers",Rn=e=>Zt(e)+"Query";function Mg(e){let t=(n,s)=>st(e,n)>=0&&st(e,s)<=0;return t("a","z")||t("A","Z")||t("0","9")}function Op(e,t){let n=[],s=[];for(let i of ze(e))Mg(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 a="";n.forEach((i,o)=>{a+=(o===0&&!t?i[0].toLowerCase():i[0].toUpperCase())+i.slice(1).join("")});let r=a.codePointAt(0);if(r>=48&&r<=57&&(a="_"+a),!/^[A-Za-z0-9_]+$/.test(a))throw new Error(`the name ${JSON.stringify(e)} becomes ${JSON.stringify(a)} in Swift, which is more than A-Z, a-z, 0-9 and _: rename it in the backend`);return a}var qg=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"]),Kg=new Set(["break","continue","fallthrough","fileprivate","inout","precedencegroup","rethrows","static","subscript","super","typealias"]),Ug=new Set(["Any","Self","Type","Protocol"]);function zg(e){return qg.has(se(e))?"`"+e+"`":e}var ft=e=>Xt(e,"`","");function $p(e,t){if(re(e,t))return!0;let n=ze(t);if(!n.some(l=>re(l,"_")))return!1;let s=0;for(;s<n.length&&re(n[s],"_");)s++;let a=0;for(;a<n.length-s&&re(n[n.length-1-a],"_");)a++;let r=n.slice(s,n.length-a).join("");if(r==="")return!1;let i=n.slice(0,s).join(""),o=Et(r,"_",!1);for(let l=0;l<o.length;l++){if(o[l]==="")return!1;if(l===0)i+=o[l].toLowerCase();else{let p=ze(o[l]);i+=p[0].toUpperCase()+p.slice(1).join("").toLowerCase()}}return i+=n.slice(n.length-a).join(""),re(i,e)}var _=e=>" ".repeat(e);function Ke(e,t){for(let n of t)e.push(n)}function Gg(e){let t=[];for(let n of e){let s=t.length,a=!1;if(n.input!==void 0&&(Ke(t,An(us(n.operationID),n.input,!0)),a=!0),n.output!==void 0?(a&&t.push(""),Ke(t,An(Yt(n.operationID),n.output)),a=!0):n.upload!==void 0&&(a&&t.push(""),t.push("public nonisolated struct "+Yt(n.operationID)+": Codable, Sendable {"),t.push("}"),a=!0),n.headers!==void 0){for(let r of n.headers.props)if(!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(r.name))throw new Error(`operation ${JSON.stringify(n.operationID)}: the header ${JSON.stringify(r.name)} is no HTTP field name (RFC 9110's token): rename it in the backend`);a&&t.push(""),Ke(t,Fg(nr(n.operationID),n.headers)),a=!0}n.query!==void 0&&(a&&t.push(""),Ke(t,gs(Rn(n.operationID),n.query.props,0)),a=!0),a&&t.push(""),Ke(t,Wg(ms(n.operationID),n.errors)),t.push(""),Ke(t,Bg(n)),t.push("");for(let r of t.slice(s)){let i=/^public (?:nonisolated )?(?:struct|enum|typealias) ([A-Za-z0-9_]+)/.exec(r);i!==null&&os(`operation ${JSON.stringify(n.operationID)}`,i[1])}}return t.join(`
552
+ `)}var hs=e=>Zt(e)+"Endpoint";function xp(e){let t=[];for(let n of Et(e,"/",!1)){if(n==="")continue;let s=ze(n);if(s.length>=2&&re(s[0],"{")&&re(s[s.length-1],"}")){let a=s.slice(1,s.length-1).join("");if(a==="")continue;t.push(".param("+Y(a)+")")}else t.push(ke(n))}return"["+t.join(", ")+"]"}function Lp(e){return e.path===""?"/"+Xt(e.operationID,".","/"):e.path}function Bg(e){if(e.upload!==void 0)return Jg(e);let t=hs(e.operationID),n=[];e.sse?(n.push("public nonisolated struct "+t+": PBSseEndpoint {"),n.push(_(1)+"public typealias Element = "+Yt(e.operationID))):e.output!==void 0?(n.push("public nonisolated struct "+t+": PBEndpoint {"),n.push(_(1)+"public typealias Response = "+Yt(e.operationID))):n.push("public nonisolated struct "+t+": PBVoidEndpoint {"),n.push(_(1)+"public typealias Failure = "+ms(e.operationID));for(let r of e.pathParams)n.push(_(1)+"let "+Y(r)+": String");e.input!==void 0&&n.push(_(1)+"let input: "+us(e.operationID)),e.query!==void 0&&n.push(_(1)+"let query: "+Rn(e.operationID)),e.headers!==void 0&&n.push(_(1)+"let headers: "+nr(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 a=["."+s.toLowerCase(),xp(Lp(e))];if(e.input!==void 0&&a.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&a.push("query: query"),e.headers!==void 0&&a.push("headers: headers.asHeaderDict()"),n.push(_(1)+"public var pbRequest: PBRequest { PBRequest("+a.join(", ")+") }"),e.idempotent&&!e.sse){if(e.pathParams.some(r=>ft(Y(r))==="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(_(1)+"public var idempotent: Bool { true }")}return n.push("}"),n}function Jg(e){let t=[];t.push("public nonisolated struct "+hs(e.operationID)+": PBUploadEndpoint {"),t.push(_(1)+"public typealias Response = "+Yt(e.operationID)),t.push(_(1)+"public typealias Failure = "+ms(e.operationID));for(let s of e.pathParams)t.push(_(1)+"let "+Y(s)+": String");e.input!==void 0&&t.push(_(1)+"let input: "+us(e.operationID)),e.query!==void 0&&t.push(_(1)+"let query: "+Rn(e.operationID));let n=[".post",xp(Lp(e))];return e.input!==void 0&&n.push("body: Palbe.PBWireValue(input)"),e.query!==void 0&&n.push("query: query"),t.push(_(1)+"public var authorizeRequest: PBRequest { PBRequest("+n.join(", ")+") }"),t.push("}"),t}function An(e,t,n=!1,s=0){if(t.kind==="object")return gs(e,t.props,s,n);let[a,r]=t.kind==="enum"?["String",[]]:ps(t,e,s,n),i=[...r];return i.length>0&&i.push(""),i.push(_(s)+"public typealias "+e+" = "+a+(t.nullable?"?":"")),i}function Fg(e,t){let n=gs(e,t.props,0);n[n.length-1].trim()==="}"&&n.pop(),n.push(_(1)+"public func asHeaderDict() -> [String: String] {"),n.push(_(2)+"var out: [String: String] = [:]");for(let s of t.props){let a=Y(s.name),r=ke(s.name),i=s.schema.kind==="enum";!s.required||s.schema.nullable?n.push(_(2)+"if let v = "+a+" { out["+r+"] = "+(i?"v.rawValue":"v")+" }"):n.push(_(2)+"out["+r+"] = "+a+(i?".rawValue":""))}return n.push(_(2)+"return out"),n.push(_(1)+"}"),n.push("}"),n}function Wg(e,t){let n=["public nonisolated enum "+e+": PBError {"],s=1,a=new Map([["other","the other(BackendError) fallback"]]),r=[];for(let i of t){let o=ft(Y(i.name)),l=a.get(se(o));if(l!==void 0){n.push(_(s)+"#error("+ke("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}a.set(se(o),i.name+" ("+i.code+")"),r.push(i)}for(let i of r)i.data!==void 0&&Ke(n,An(Ie(i.name)+"Data",i.data,!1,s));for(let i of r)n.push(_(s)+"case "+Y(i.name)+(i.data!==void 0?"("+Ie(i.name)+"Data)":""));if(n.push(_(s)+"case other(BackendError)"),r.length===0)return n.push(_(s)+"public nonisolated init(_ backend: BackendError) { self = .other(backend) }"),n.push("}"),n;n.push(_(s)+"public nonisolated init(_ backend: BackendError) {"),n.push(_(s+1)+"guard let f = backend.failure else { self = .other(backend); return }"),n.push(_(s+1)+"switch f.code {");for(let i of r){let o=Y(i.name);i.data!==void 0?(n.push(_(s+1)+"case "+ke(i.code)+":"),n.push(_(s+2)+"if let data = f.decodeData("+Ie(i.name)+"Data.self) { self = ."+o+"(data) } else { self = .other(backend) }")):n.push(_(s+1)+"case "+ke(i.code)+": self = ."+o)}return n.push(_(s+1)+"default: self = .other(backend)"),n.push(_(s+1)+"}"),n.push(_(s)+"}"),n.push("}"),n}function gs(e,t,n,s=!1){let a=[_(n)+"public nonisolated struct "+e+": Codable, Sendable {"],r=n+1,i=[],o=new ls,l=new Map;for(let d of t){let c=Y(d.name),g=l.get(se(c));if(g!==void 0){a.push(_(r)+"#error("+ke("palbase: "+e+" has colliding Swift property `"+ft(c)+"` for wire keys "+g+" and "+d.name+". This schema is unsupported; use distinct property names and regenerate the client.")+")");continue}l.set(se(c),d.name);let f=!d.required||d.schema.nullable,[b,P]=ps(d.schema,d.name,r,s,o);s&&!d.required&&d.schema.nullable&&(b="Nullable<"+b+">"),Ke(a,P),i.push({ident:c,key:d.name,typ:b,optional:f,required:d.required})}for(let d of i)a.push(_(r)+"public let "+d.ident+": "+d.typ+(d.optional?"?":""));let p=i.map(d=>d.ident+": "+d.typ+(d.optional?"? = nil":""));a.push(_(r)+"public init("+p.join(", ")+") {");for(let d of i)a.push(_(r+1)+"self."+d.ident+" = "+d.ident);if(a.push(_(r)+"}"),s){a.push(_(r)+"public func encode(to encoder: any Swift.Encoder) throws {"),a.push(_(r+1)+(i.length===0?"let":"var")+" fields: [Swift.String: Palbe.PBWireValue] = [:]");for(let d of i){let c="fields["+ke(d.key)+"] = Palbe.PBWireValue(";d.required?a.push(_(r+1)+c+"self."+d.ident+")"):a.push(_(r+1)+"if let value = self."+d.ident+" { "+c+"value) }")}a.push(_(r+1)+"var container = encoder.singleValueContainer()"),a.push(_(r+1)+"try container.encode(fields)"),a.push(_(r)+"}")}if(i.some(d=>!$p(ft(d.ident),d.key))){a.push(_(r)+"enum CodingKeys: String, CodingKey {");for(let d of i)$p(ft(d.ident),d.key)?a.push(_(r+1)+"case "+d.ident):a.push(_(r+1)+"case "+d.ident+" = "+ke(d.key));a.push(_(r)+"}")}return a.push(_(n)+"}"),a}var ls=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(se(n));s++)n=t+s;return this.used.add(se(n)),n}};function ps(e,t,n,s=!1,a=new ls){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 r=a.allocate(Ie(t)+"Value"),i=[_(n)+"public nonisolated enum "+r+": String, Codable, Sendable {"];for(let o of e.enumVals)i.push(_(n+1)+"case "+Y(o)+" = "+ke(o));return i.push(_(n)+"}"),[r,i]}case"array":{let r=e.elem,[i,o]=ps(r,t+"Item",n,s,a);return["["+i+(r.nullable?"?":"")+"]",o]}case"map":{let r=e.elem,[i,o]=ps(r,t+"Value",n,s,a);return["[String: "+i+(r.nullable?"?":"")+"]",o]}case"mixedObject":return["AnyCodableValue",[_(n)+"#error("+ke("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(Ug.has(Ie(t)))throw new Error(`the name ${JSON.stringify(t)} becomes the type ${Ie(t)}, a name Swift gives no type of a program's: rename it in the backend`);let r=a.allocate(Ie(t));return[r,gs(r,e.props,n,s)]}default:return["AnyCodableValue",[]]}}function Vg(e,t){let n={path:[],children:new Map,methods:[]};for(let r of e){let i=cs(r.operationID),o=n;for(let l of i.slice(0,Math.max(i.length-1,0))){let p=o.children.get(se(l));p===void 0&&(p=[l,{path:[...o.path,l],children:new Map,methods:[]}],o.children.set(se(l),p)),o=p[1]}o.methods.push(r)}let s=[],a=[n];for(;a.length>0;){let r=a.shift();s.push(r),a.push(...Dp(r).map(([,i])=>i))}return s.map(r=>Hg(r,t)+`
553
+ `).join("")}var Dp=e=>[...e.children.values()].sort((t,n)=>st(t[0],n[0]));function Qa(e,t){return e.length===0?t:"PB"+e.map(Ie).join("")+"Namespace"}function Hg(e,t){let n=e.path.length===0,s=[];n?s.push("public extension "+t+" {"):(s.push("public nonisolated struct "+Qa(e.path,t)+": Sendable {"),s.push(" let _pb: PalBackendClient"));let a=n?"":"public ",r="_pb";for(let[,i]of Dp(e)){let o=Qa(i.path,t);s.push(_(1)+a+"nonisolated var "+Y(i.path[i.path.length-1])+": "+o+" { "+o+"(_pb: "+r+") }")}for(let i of e.methods){let o=cs(i.operationID);if(o.length===0)throw new Error(`operationId ${JSON.stringify(i.operationID)} names no method`);let l=Y(o[o.length-1]),p=us(i.operationID),d=Yt(i.operationID),c="throws("+ms(i.operationID)+")";if(i.upload!==void 0){Ke(s,Zg(i,l,p,d,c,a,r));continue}let g=i.pathParams.map(P=>Y(P)+": String");i.input!==void 0&&g.push("_ input: "+p),i.query!==void 0&&g.push("query: "+Rn(i.operationID)),i.headers!==void 0&&g.push("headers: "+nr(i.operationID));let f=i.pathParams.map(P=>Y(P)+": "+Y(P));i.input!==void 0&&f.push("input: input"),i.query!==void 0&&f.push("query: query"),i.headers!==void 0&&f.push("headers: headers");let b=hs(i.operationID)+"("+f.join(", ")+")";i.sse?(s.push(_(1)+a+"func "+l+"("+g.join(", ")+") async throws -> AsyncThrowingStream<"+d+", Error> {"),s.push(_(2)+"return try await "+r+".stream("+b+")")):i.output!==void 0?(s.push(_(1)+"@discardableResult"),s.push(_(1)+a+"func "+l+"("+g.join(", ")+") async "+c+" -> "+d+" {"),s.push(_(2)+"return try await "+r+".call("+b+")")):(s.push(_(1)+a+"func "+l+"("+g.join(", ")+") async "+c+" {"),s.push(_(2)+"try await "+r+".call("+b+")")),s.push(_(1)+"}")}return s.push("}"),s.join(`
554
554
  `)+`
555
- `}var dm=e=>Ee(e)+"Room",Nt=(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 um(e,t){let n=dm(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,Nt(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 = "+me(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=Nl(o);i+=l!==void 0?"\\("+W(l)+")":me(o).slice(1,-1)}a.push(w(2)+'return "'+i+'"'),a.push(w(1)+"}"),a.push("");for(let o of e.events)De(a,gn(Nt(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)+"("+Nt(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=>me(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 "+me(o.name)+":"),a.push(w(3)+"return ."+W(o.name)+"(try JSONDecoder.palbaseDefault.decode("+Nt(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,gn(Nt(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)+"("+Nt(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 ("+me(o.name)+", try JSONEncoder.palbaseDefault.encode(value))");a.push(w(2)+"}")}return a.push(w(1)+"}"),a.push("}"),a.join(`
555
+ `}var Xg=e=>Ie(e)+"Room",Ht=(e,t)=>Ie(e)+t;function is(e,t,n){let s=e.get(se(t));if(s!==void 0)throw new Error(`${s} and ${n} both become ${t} in Swift: rename one in the backend`);e.set(se(t),n)}function Yg(e,t){let n=Xg(e.pattern),s=`room ${JSON.stringify(e.pattern)}`;is(t,n,s),os(s,n);let a=new Map([["topic","the room's topic"]]);for(let o of e.params){let l=ft(Y(o));if(is(a,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 d of l)is(p,ft(Y(d.name)),`${s}'s ${o.toLowerCase()} ${JSON.stringify(d.name)}`),is(a,Ht(d.name,o),`${s}'s ${o.toLowerCase()} ${JSON.stringify(d.name)}`)}let r=[];r.push("public nonisolated struct "+n+": PBRoom {"),r.push(_(1)+"public static let pattern = "+ke(e.pattern));for(let o of e.params)r.push(_(1)+"public let "+Y(o)+": String");if(e.params.length===0)r.push(_(1)+"public init() {}");else{r.push(_(1)+"public init("+e.params.map(o=>Y(o)+": String").join(", ")+") {");for(let o of e.params)r.push(_(2)+"self."+ft(Y(o))+" = "+Y(o));r.push(_(1)+"}")}r.push(_(1)+"public var topic: String {");let i="";for(let o of Et(e.pattern,":",!1)){i!==""&&(i+=":");let l=jp(o);i+=l!==void 0?"\\("+Y(l)+")":ke(o).slice(1,-1)}r.push(_(2)+'return "'+i+'"'),r.push(_(1)+"}"),r.push("");for(let o of e.events)Ke(r,An(Ht(o.name,"Event"),o.schema,!1,1));r.push(_(1)+"public enum Event: Sendable {");for(let o of e.events)r.push(_(2)+"case "+Y(o.name)+"("+Ht(o.name,"Event")+")");if(e.events.length===0&&r.push(_(2)+"// this room declares no events"),r.push(_(1)+"}"),r.push(""),r.push(_(1)+"public static let eventNames: [String] = ["+e.events.map(o=>ke(o.name)).join(", ")+"]"),r.push(""),r.push(_(1)+"public static func decode(event: String, data: Data) throws -> Event? {"),e.events.length===0)r.push(_(2)+"_ = (event, data)"),r.push(_(2)+"return nil");else{r.push(_(2)+"switch event {");for(let o of e.events)r.push(_(2)+"case "+ke(o.name)+":"),r.push(_(3)+"return ."+Y(o.name)+"(try JSONDecoder.palbaseDefault.decode("+Ht(o.name,"Event")+".self, from: data))");r.push(_(2)+"default:"),r.push(_(3)+"return nil"),r.push(_(2)+"}")}r.push(_(1)+"}"),r.push("");for(let o of e.messages)Ke(r,An(Ht(o.name,"Message"),o.schema,!0,1));r.push(_(1)+"public enum Send: Sendable {");for(let o of e.messages)r.push(_(2)+"case "+Y(o.name)+"("+Ht(o.name,"Message")+")");if(e.messages.length===0&&r.push(_(2)+"// this room accepts no client messages"),r.push(_(1)+"}"),r.push(""),r.push(_(1)+"public static func encode(_ message: Send) throws -> (event: String, data: Data) {"),e.messages.length===0)r.push(_(2)+"_ = message"),r.push(_(2)+"throw PBRoomError.noMessagesDeclared(pattern)");else{r.push(_(2)+"switch message {");for(let o of e.messages)r.push(_(2)+"case ."+Y(o.name)+"(let value):"),r.push(_(3)+"return ("+ke(o.name)+", try JSONEncoder.palbaseDefault.encode(value))");r.push(_(2)+"}")}return r.push(_(1)+"}"),r.push("}"),r.join(`
556
556
  `)+`
557
- `}function cm(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: "+mn(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__"]),gm=new Set(Sr),mm=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function ts(e){return e==="then"||e==="toJSON"||mm.has(e)}function Gl(e){return e==="call"||e==="join"||ts(e)}function ns(e){return typeof e=="string"&&es.test(e)&&!gm.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 N(e){if(typeof e=="object"&&e!==null&&!Array.isArray(e))return e}function je(e){return typeof e=="string"?e:""}function hm(e,t=new fn){let n=N(N(e)?.paths);if(!n)throw new Error("the contract has no `paths`");let s=[];for(let[r,a]of Object.entries(n)){let i=N(a);if(i)for(let[o,l]of Object.entries(i)){let p=N(l);if(!p)continue;let u=je(p.operationId);u!==""&&s.push({operationId:u,method:o.toUpperCase(),path:Zl(r),pathParams:_m(r),input:bm(p,t),output:Sm(p,t),headers:Fl(p,"header",t),query:Fl(p,"query",t),errors:fm(p,t),sse:p["x-palbase-sse"]!==void 0,idempotent:vm(p),upload:wm(p)})}}return s.sort((r,a)=>r.operationId<a.operationId?-1:r.operationId>a.operationId?1:0),s}function fm(e,t){let n=N(e["x-palbase-errors"]);if(!n)return[];let s=N(e.responses),r=[];for(let[a,i]of Object.entries(n)){let o=N(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=ym(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 ym(e,t,n,s){let r=N(e?.[String(t)]),a=N(N(r?.content)?.["application/json"]),i=N(a?.schema);if(!i)return;let o=i.oneOf;if(Array.isArray(o)){for(let l of o){let p=N(l);if(!p)continue;let u=N(N(p.properties)?.error);if(u&&je(u.const)===n)return Wl(p,s)}return}return Wl(i,s)}function Wl(e,t){let n=N(N(e.properties)?.data);if(n)return Ge(n,t)}function bm(e,t){let n=N(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=N(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=N(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 wm(e){let t=N(e["x-palbase-upload"]);return je(t?.bucket)!==""&&je(t?.pathTemplate)!==""}function vm(e){let t=e.parameters;return Array.isArray(t)&&t.some(n=>je(N(n)?.in)==="header"&&Yl(je(N(n)?.name)))}function Zl(e){return e.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g,"{$1}")}function _m(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 Sm(e,t){let n=N(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=N(n[r]);if(!a)continue;let i=Ql(a.content,t);if(i)return i}}function Ql(e,t){let n=N(N(e)?.["application/json"]),s=N(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(N(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=N(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=N(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=N(s[l]),u=p?Ge(p,n):be("any");i.push({name:l,schema:u,required:r.has(l)})}let o=N(e.additionalProperties);return{...be("object"),nullable:t,props:i,...o?{additional:Ge(o,n)}:e.additionalProperties===!1?{additional:!1}:{}}}function km(e,t=new fn){let n=N(e),s=N(n?.["x-palbase-room"]);if(!s||!n)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=N(i);o&&r.push({pattern:a,params:Pm(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 Pm(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=N(e);if(!s)return[];let r=[];for(let[a,i]of Object.entries(s)){let o=N(i);if(!o)continue;let l=Am(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 Am(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=N(s);if(!i||!Object.hasOwn(i,a))return;s=i[a]}return N(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(P){throw new Error(`the project's address ${JSON.stringify(t.project)}: ${P instanceof Error?P.message:String(P)}`)}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(P){throw new Error(`the machine's address ${JSON.stringify(t.url)}: ${P instanceof Error?P.message:String(P)}`)}let n=new fn,s=hm(e,n),r=km(e,n),a=$m(e),i=[],o=[],l=new Set;for(let P of s){let[U]=is(P.operationId);if(U!==void 0){if(Gl(U)){let X=U.toLowerCase();l.has(X)||(l.add(X),o.push(U));continue}i.push(P)}}o.sort();let[p,u]=Rm(i),d=new Pr,[f,y,v]=Em(p,d),T=p.filter(P=>!v.has(P.operationId)),[A,$]=Im(T,d),S="";for(let P of t.header)S+=P===""?`//
558
- `:`// ${se(P)}
559
- `;let C=["BackendBase"];$>0&&C.push("BackendError"),T.some(P=>!P.upload)&&C.push("CallOptions"),T.some(P=>P.upload)&&C.push("UploadOptions"),S+=`import type { ${C.join(", ")} } from 'palbase/web';
560
- `,S+=`import { __registerBackend } from 'palbase/web/palbe';
561
-
562
- `,S+=f,S+=A,S+=`// \u2500\u2500 Namespaces \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
563
-
564
- `;for(let P of o)S+=`// codegen: skipped reserved namespace "${se(P)}"
565
- `;for(let P of u)S+=`${P}
566
- `;for(let P of y)S+=`${P}
567
- `;if(S+=Lm(T,t),S+=`
568
- `,S+=Nm(T),r.length>0){S+=`
557
+ `}function Zg(e,t,n,s,a,r,i){let o=e.pathParams.map(d=>Y(d)+": String");o.push("file: PBFileSource"),e.input!==void 0&&o.push("input: "+n),e.query!==void 0&&o.push("query: "+Rn(e.operationID)),o.push("onProgress: (@Sendable (BackendUploadProgress) -> Void)? = nil");let l=e.pathParams.map(d=>Y(d)+": "+Y(d));e.input!==void 0&&l.push("input: input"),e.query!==void 0&&l.push("query: query");let p=hs(e.operationID)+"("+l.join(", ")+")";return[_(1)+"@discardableResult",_(1)+r+"func "+t+"("+o.join(", ")+") async "+a+" -> "+s+" {",_(2)+"return try await "+i+".call("+p+", file: file, onProgress: onProgress)",_(1)+"}"]}var fs=/^[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__"]),Qg=new Set(sr),ef=new Set(["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf","__proto__","__defineGetter__","__defineSetter__","__lookupGetter__","__lookupSetter__"]);function ys(e){return e==="then"||e==="toJSON"||ef.has(e)}function Ip(e){return e==="call"||e==="join"||ys(e)}function bs(e){return typeof e=="string"&&fs.test(e)&&!Qg.has(e)&&!ys(e)}function Np(e){return typeof e=="string"&&/^[a-z][a-z0-9-]{0,62}$/.test(e)}function Mp(e){ia(e)}function qp(e){return ia(e).origin}function G(e){if(typeof e=="object"&&e!==null&&!Array.isArray(e))return e}function Me(e){return typeof e=="string"?e:""}function tf(e,t=new En){let n=G(G(e)?.paths);if(!n)throw new Error("the contract has no `paths`");let s=[];for(let[a,r]of Object.entries(n)){let i=G(r);if(i)for(let[o,l]of Object.entries(i)){let p=G(l);if(!p)continue;let d=Me(p.operationId);d!==""&&s.push({operationId:d,method:o.toUpperCase(),path:Fp(a),pathParams:lf(a),input:af(p,t),output:pf(p,t),headers:Up(p,"header",t),query:Up(p,"query",t),errors:nf(p,t),sse:p["x-palbase-sse"]!==void 0,idempotent:of(p),upload:rf(p)})}}return s.sort((a,r)=>a.operationId<r.operationId?-1:a.operationId>r.operationId?1:0),s}function nf(e,t){let n=G(e["x-palbase-errors"]);if(!n)return[];let s=G(e.responses),a=[];for(let[r,i]of Object.entries(n)){let o=G(i);if(!o)continue;let l=typeof o.status=="number"&&Number.isFinite(o.status)?Math.trunc(o.status):0,p=Me(o.code),d=Me(o.description),c=o.hasData===!0;if(p===""||l===0)continue;let g={name:r,code:p,status:l,description:d};c&&(g.data=sf(s,l,p,t)),a.push(g)}return a.sort((r,i)=>r.name<i.name?-1:r.name>i.name?1:0),a}function sf(e,t,n,s){let a=G(e?.[String(t)]),r=G(G(a?.content)?.["application/json"]),i=G(r?.schema);if(!i)return;let o=i.oneOf;if(Array.isArray(o)){for(let l of o){let p=G(l);if(!p)continue;let d=G(G(p.properties)?.error);if(d&&Me(d.const)===n)return Kp(p,s)}return}return Kp(i,s)}function Kp(e,t){let n=G(G(e.properties)?.data);if(n)return Ye(n,t)}function af(e,t){let n=G(e.requestBody);if(n)return Wp(n.content,t)}function Up(e,t,n){let s=e.parameters;if(!Array.isArray(s)||s.length===0)return;let a=[];for(let r of s){let i=G(r);if(!i||Me(i.in)!==t)continue;let o=Me(i.name);if(o===""||t==="header"&&Jp(o))continue;let l=i.required===!0,p=G(i.schema),d=p?Ye(p,n):Ae("string");a.push({name:o,schema:d,required:l})}if(a.length!==0)return a.sort((r,i)=>r.name<i.name?-1:r.name>i.name?1:0),{...Ae("object"),props:a}}function Jp(e){return e.toLowerCase()==="idempotency-key"}function rf(e){let t=G(e["x-palbase-upload"]);return Me(t?.bucket)!==""&&Me(t?.pathTemplate)!==""}function of(e){let t=e.parameters;return Array.isArray(t)&&t.some(n=>Me(G(n)?.in)==="header"&&Jp(Me(G(n)?.name)))}function Fp(e){return e.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g,"{$1}")}function lf(e){let t=Fp(e),n=[],s=t;for(;;){let a=s.indexOf("{");if(a<0)break;let r=s.indexOf("}",a);if(r<0)break;let i=s.slice(a+1,r);i!==""&&n.push(i),s=s.slice(r+1)}return n}function pf(e,t){let n=G(e.responses);if(!n)return;let s=[];for(let a of Object.keys(n))a.startsWith("2")&&a!=="200"&&a!=="201"&&s.push(a);s.sort();for(let a of["200","201",...s]){let r=G(n[a]);if(!r)continue;let i=Wp(r.content,t);if(i)return i}}function Wp(e,t){let n=G(G(e)?.["application/json"]),s=G(n?.schema);if(s&&!("$ref"in s))return Ye(s,t)}function Ae(e){return{kind:e,nullable:!1,props:[],enumVals:[]}}var zp=2e5,En=class{left=zp;spend(){if(--this.left<0)throw new Error(`the contract's schemas make more than ${zp} 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 Ye(e,t){t.spend();let n=e.nullable===!0||Array.isArray(e.type)&&e.type.includes("null");if(e.const===null)return Ae("null");if(["string","number","boolean"].includes(typeof e.const))return{...Ae("literal"),nullable:n,literal:e.const};let s=e.anyOf??e.oneOf;if(Array.isArray(s)&&s.length>0){let i=s.map(l=>Ye(G(l)??{},t));n||=i.some(l=>l.kind==="null");let o=i.filter(l=>l.kind!=="null");return o.length===0?Ae("null"):o.length===1?{...o[0],nullable:n||o[0].nullable}:{...Ae("union"),nullable:n,members:o}}let a=e.enum;if(Array.isArray(a)){let i=[],o=!0;for(let l of a)if(typeof l=="string")i.push(l);else{o=!1;break}if(o&&i.length>0)return{...Ae("enum"),nullable:n,enumVals:i};if(a.length>0&&a.every(l=>l===null||["string","number","boolean"].includes(typeof l)))return Ye({anyOf:a.map(l=>({const:l})),nullable:n},t)}let r=Me(e.type);if(r===""&&Array.isArray(e.type)){let i=[...new Set(e.type)];if(i.filter(l=>typeof l=="string"&&l!=="null").length>1)return Ye({anyOf:i.map(l=>({...e,type:l})),nullable:n},t);for(let l of i)typeof l=="string"&&(l==="null"?n=!0:r===""&&(r=l))}switch(r){case"null":return Ae("null");case"string":case"number":case"integer":case"boolean":return{...Ae(r),nullable:n};case"array":{let i=G(e.items),o=i?Ye(i,t):Ae("any");return{...Ae("array"),nullable:n,elem:o,...e.maxItems===0?{empty:!0}:{}}}case"object":return Gp(e,n,t);default:return"properties"in e?Gp(e,n,t):{...Ae("any"),nullable:n}}}function Gp(e,t,n){let s=G(e.properties)??{},a=new Set;if(Array.isArray(e.required))for(let l of e.required)typeof l=="string"&&a.add(l);let r=Object.keys(s).sort(),i=[];for(let l of r){let p=G(s[l]),d=p?Ye(p,n):Ae("any");i.push({name:l,schema:d,required:a.has(l)})}let o=G(e.additionalProperties);return{...Ae("object"),nullable:t,props:i,...o?{additional:Ye(o,n)}:e.additionalProperties===!1?{additional:!1}:{}}}function df(e,t=new En){let n=G(e),s=G(n?.["x-palbase-room"]);if(!s||!n)return[];let a=[];for(let[r,i]of Object.entries(s)){let o=G(i);o&&a.push({pattern:r,params:cf(r),events:Bp(o.events,n,t),messages:Bp(o.messages,n,t)})}return a.sort((r,i)=>r.pattern<i.pattern?-1:r.pattern>i.pattern?1:0),a}function cf(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 Bp(e,t,n){let s=G(e);if(!s)return[];let a=[];for(let[r,i]of Object.entries(s)){let o=G(i);if(!o)continue;let l=uf(o,t)??o;a.push({name:r,schema:Ye(l,n)})}return a.sort((r,i)=>r.name<i.name?-1:r.name>i.name?1:0),a}function uf(e,t){let n=Me(e.$ref);if(!n.startsWith("#/"))return;let s=t;for(let a of n.slice(2).split("/")){let r=a.replace(/~1/g,"/").replace(/~0/g,"~"),i=G(s);if(!i||!Object.hasOwn(i,r))return;s=i[r]}return G(s)}function ks(e){return e.split(".").filter(t=>t!=="")}function Ne(e){return vs(e,!0)}function Ss(e){return ks(e).map(Ne).join("")}function vs(e,t){let n=[],s="";for(let r of e)/^[A-Za-z0-9]$/.test(r)?s+=r:s.length>0&&(n.push(s),s="");if(s.length>0&&n.push(s),n.length===0)return t?"Op":"op";let a="";if(n.forEach((r,i)=>{a+=i===0&&!t?r.charAt(0).toLowerCase()+r.slice(1):r.charAt(0).toUpperCase()+r.slice(1)}),a.charAt(0)>="0"&&a.charAt(0)<="9"&&(a=`_${a}`),!fs.test(a))throw new Error(`${JSON.stringify(e)} does not make an identifier`);return a}function Vp(e,t){if(!bs(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{qp(t.project)}catch(A){throw new Error(`the project's address ${JSON.stringify(t.project)}: ${A instanceof Error?A.message:String(A)}`)}if(!Np(t.machine))throw new Error(`${JSON.stringify(t.machine)} is not a machine's name: a lowercase letter, then lowercase letters, digits and -`);try{Mp(t.url)}catch(A){throw new Error(`the machine's address ${JSON.stringify(t.url)}: ${A instanceof Error?A.message:String(A)}`)}let n=new En,s=tf(e,n),a=df(e,n),r=mf(e),i=[],o=[],l=new Set;for(let A of s){let[L]=ks(A.operationId);if(L!==void 0){if(Ip(L)){let X=L.toLowerCase();l.has(X)||(l.add(X),o.push(L));continue}i.push(A)}}o.sort();let[p,d]=hf(i),c=new rr,[g,f,b]=ff(p,c),P=p.filter(A=>!b.has(A.operationId)),[E,k]=_f(P,c),$="";for(let A of t.header)$+=A===""?`//
558
+ `:`// ${ie(A)}
559
+ `;let O=["BackendBase"];k>0&&O.push("BackendError"),P.some(A=>!A.upload)&&O.push("CallOptions"),P.some(A=>A.upload)&&O.push("UploadOptions"),$+=`import type { ${O.join(", ")} } from 'palbase/web';
560
+ `,$+=`import { __registerBackend } from 'palbase/web/palbe';
561
+
562
+ `,$+=g,$+=E,$+=`// \u2500\u2500 Namespaces \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
563
+
564
+ `;for(let A of o)$+=`// codegen: skipped reserved namespace "${ie(A)}"
565
+ `;for(let A of d)$+=`${A}
566
+ `;for(let A of f)$+=`${A}
567
+ `;if($+=kf(P,t),$+=`
568
+ `,$+=$f(P),a.length>0){$+=`
569
569
  // \u2500\u2500 Rooms (@Room) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
570
570
 
571
- `;for(let P of r)S+=zm(P,d)}return a!==void 0&&(S+=Fm(a)),S}var Pr=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 $m(e){let t=N(e)?.["x-palbase-roles"];if(t===void 0)return;let n=N(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 Rm(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=Tm(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 Tm(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 Em(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(...Ut(t.claim(`${l}Request`,ss(o)),o.input)),o.query&&a.push(...Ut(t.claim(`${l}Query`,ss(o)),o.query)),o.output&&a.push(...Cm(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(...Ut(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
571
+ `;for(let A of a)$+=Of(A,c)}return r!==void 0&&($+=Lf(r)),$}var rr=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}},ws=e=>`operation ${JSON.stringify(e.operationId)}`,Hp=(e,t)=>`${ws(e)}'s error ${JSON.stringify(t.name)}`,ar=e=>`room ${JSON.stringify(e.pattern)}`;function mf(e){let t=G(e)?.["x-palbase-roles"];if(t===void 0)return;let n=G(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 hf(e){let t=[],n=[],s=new _s;for(let a of e){let r=ks(a.operationId),i=r[r.length-1];if(i===void 0)continue;let o=!1;for(let p of r.slice(1))if(ys(p)){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (reserved segment "${ie(p)}")`),o=!0;break}if(o)continue;if(a.input&&a.query){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (both body and query declared \u2014 unsupported)`);continue}if(a.sse&&a.query){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (streaming with query parameters \u2014 unsupported)`);continue}if(a.upload&&(a.method!=="POST"||a.sse)){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (an upload that is not one POST answered once \u2014 unsupported)`);continue}if(a.query){let p=gf(a.query);if(p!==void 0){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (non-primitive query parameter "${ie(p)}")`);continue}}let l=s;for(let p of r.slice(0,-1))if(l=l.childNode(p),!l)break;if(!l?.addMethod(i,a)){n.push(`// codegen: skipped operation "${ie(a.operationId)}" (key collides with existing namespace/method)`);continue}t.push(a)}return[t,n]}function gf(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 ff(e,t){let n=new Set,s=new Map,a=[],r=[];for(let o of e){let l=Ss(o.operationId),p=s.get(l);if(p!==void 0){a.push(`// codegen: skipped type "${ie(l)}" (collides with operation "${ie(p)}")`),n.add(o.operationId);continue}s.set(l,o.operationId),o.input&&r.push(...Qt(t.claim(`${l}Request`,ws(o)),o.input)),o.query&&r.push(...Qt(t.claim(`${l}Query`,ws(o)),o.query)),o.output&&r.push(...yf(l,o.output,c=>t.claim(c,ws(o))));let d=ir(o);for(let c of d){if(!c.data)continue;let g=t.claim(`${l}${Ne(c.name)}Data`,Hp(o,c));r.push(`/** ${ie(c.code)} (${c.status}): ${ie(c.description)} */`),r.push(...Qt(g,c.data))}}if(r.length===0)return["",a,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
- `;for(let o of a)i+=`${o}
574
- `;return[i,r,n]}function Ut(e,t){return t.kind==="object"&&!t.nullable&&t.props.length>0&&!t.additional?np(e,t):[`export type ${e} = ${vt(t)};`,""]}function Cm(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":""};`,""]:Ut(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=jm(e);return e.nullable?`${t} | null`:t}default:return e.nullable?"unknown | null":"unknown"}}function jm(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 Om=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"]),Dm=/^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'),(Om.has(a.code)||Dm.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 Im(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
573
+ `;for(let o of r)i+=`${o}
574
+ `;return[i,a,n]}function Qt(e,t){return t.kind==="object"&&!t.nullable&&t.props.length>0&&!t.additional?Xp(e,t):[`export type ${e} = ${Ct(t)};`,""]}function yf(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?[...Xp(n(`${e}ResponseItem`),t.elem),`export type ${n(`${e}Response`)} = ${e}ResponseItem[]${t.nullable?" | null":""};`,""]:Qt(n(`${e}Response`),t)}function Xp(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 a=s.required?"":"?";n.push(` ${Ze(s.name)}${a}: ${Ct(s.schema)};`)}return n.push("}",""),n}function Ct(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"?oe(e.literal):JSON.stringify(e.literal);return e.nullable?`${t} | null`:t}case"union":{let t=(e.members??[]).map(Ct);return e.nullable&&t.push("null"),t.join(" | ")}case"enum":{let t=e.enumVals.map(oe).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=Ct(e.elem);(t.includes(" | ")||t.startsWith("("))&&(t=`(${t})`);let n=`${t}[]`;return e.nullable?`${n} | null`:n}case"object":{let t=bf(e);return e.nullable?`${t} | null`:t}default:return e.nullable?"unknown | null":"unknown"}}function bf(e){let t=[];for(let n of e.props){if(n.name==="__proto__")continue;let s=n.required?"":"?";t.push(`${Ze(n.name)}${s}: ${Ct(n.schema)}`)}if(e.additional){let n=`Record<string, ${Ct(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 wf=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"]),vf=/^http_\d+$/;function ir(e){if(e.errors.length===0)return[];let t=r=>{throw new Error(`${e.operationId}: ${r} \u2014 rename the error in the backend and push again`)},n=[...e.errors].sort((r,i)=>r.code<i.code?-1:r.code>i.code?1:0),s=new Set,a=new Map;for(let r of n){r.code==="__proto__"&&t('declares the error code "__proto__", which no lookup can hold'),(wf.has(r.code)||vf.test(r.code))&&t(`declares the error "${r.code}", a code the web client produces itself \u2014 the two could not be told apart`),s.has(r.code)&&t(`declares the error code "${r.code}" twice`);let i=Ne(r.name),o=a.get(i);o!==void 0&&t(`declares errors "${o}" and "${r.name}", which both become the class ${i}`),s.add(r.code),a.set(i,r.name)}return n}function _f(e,t){let n=0,s=[];for(let r of e){let i=Ss(r.operationId),o=ir(r);for(let l of o){let p=t.claim(`${i}${Ne(l.name)}Error`,Hp(r,l)),d=l.data!==void 0;s.push(`export class ${p} extends Error {`),s.push(` readonly name = ${oe(p)};`),s.push(` readonly code = ${oe(l.code)};`),s.push(` readonly status = ${l.status};`),d&&s.push(` readonly data: ${i}${Ne(l.name)}Data;`),s.push(" readonly cause: BackendError;"),s.push(" constructor(cause: BackendError) {"),s.push(" super(cause.message);"),s.push(" this.cause = cause;"),d&&s.push(` this.data = cause.data as ${i}${Ne(l.name)}Data;`),s.push(" }"),s.push("}"),s.push(""),n++}}if(n===0)return["",0];let a=`// \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
- `;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 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 Lm(e,t){let n=`__registerBackend(${re(t.project)}, ${re(t.client)}, ${re(t.machine)}, ${re(t.url)}, `;if(e.length===0)return`${n}{});
576
+ `;for(let r of s)a+=`${r}
577
+ `;return[a,n]}var _s=class e{entries=[];index=new Map;childNode(t){let n=this.index.get(t);if(n)return n.child;let s=new e,a={key:t,child:s};return this.entries.push(a),this.index.set(t,a),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 Yp(e){let t=new _s;for(let n of e){let s=ks(n.operationId),a=s[s.length-1];if(a===void 0)continue;let r=t;for(let i of s.slice(0,-1))if(r=r.childNode(i),!r)break;r&&r.addMethod(a,n)}return t}function kf(e,t){let n=`__registerBackend(${oe(t.project)}, ${oe(t.client)}, ${oe(t.machine)}, ${oe(t.url)}, `;if(e.length===0)return`${n}{});
578
578
  `;let s=`${n}{
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+=xm(r.op,r.key,t))}return s}function xm(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 Nm(e){if(e.length===0)return`export interface Backend extends BackendBase {}
579
+ `;return s+=Zp(Yp(e),2),s+=`});
580
+ `,s}function Zp(e,t){let n=" ".repeat(t),s="";for(let a of e.entries){if(a.child){s+=`${n}${Ze(a.key)}: {
581
+ `,s+=Zp(a.child,t+2),s+=`${n}},
582
+ `;continue}a.op&&(s+=Sf(a.op,a.key,t))}return s}function Sf(e,t,n){let s=" ".repeat(n),a=Ze(t),r=ir(e),i=r.length>0,o=e.pathParams.length>0,l=e.input!==void 0,p=e.query!==void 0,d="";!l&&!p?d="none":p&&(d="query");let c="";if(!i){let f=[`method: ${oe(e.method)}`,`path: ${oe(e.path)}`];return o&&f.push(`pathParams: [${Tn(e.pathParams)}]`),d!==""&&f.push(`input: ${oe(d)}`),e.upload&&f.push("upload: true"),e.sse&&f.push("sse: true"),e.idempotent&&!e.upload&&f.push("idempotent: true"),c+=`${s}${a}: { ${f.join(", ")} },
583
+ `,c}let g=" ".repeat(n+2);return c+=`${s}${a}: {
584
+ `,c+=`${g}method: ${oe(e.method)},
585
+ `,c+=`${g}path: ${oe(e.path)},
586
+ `,o&&(c+=`${g}pathParams: [${Tn(e.pathParams)}],
587
+ `),d!==""&&(c+=`${g}input: ${oe(d)},
588
+ `),e.upload&&(c+=`${g}upload: true,
589
+ `),e.sse&&(c+=`${g}sse: true,
590
+ `),e.idempotent&&!e.upload&&(c+=`${g}idempotent: true,
591
+ `),c+=`${g}errors: { `,r.forEach((f,b)=>{b>0&&(c+=", ");let P=`${Ss(e.operationId)}${Ne(f.name)}Error`;c+=`${Ze(f.code)}: (e) => new ${P}(e)`}),c+=` },
592
+ `,c+=`${s}},
593
+ `,c}function Tn(e){return e.map(oe).join(", ")}function $f(e){if(e.length===0)return`export interface Backend extends BackendBase {}
594
594
  `;let t=`export interface Backend extends BackendBase {
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}${Gm(r.key)}${qm(r.op)};
599
- `)}return s}function qm(e){let t=os(e.operationId),n=[],s=new Set;for(let a of e.pathParams)n.push(`${Mm(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(Km(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 Km(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 Mm(e,t){let n=rs(e,!1);(Um.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 Um=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 Gm(e){return e==="new"?re(e):Be(e)}var Bm=/[\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&&Bm.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 Jm(e){return`${Ce(e)}Room`}function zm(e,t){let n=Jm(e.pattern),s=[];for(let a of e.events)s.push(...Ut(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(...Ut(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 Wm(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 Fm(e){let t=Wm(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 = {
595
+ `;return t+=Qp(Yp(e),2),t+=`}
596
+ `,t}function Qp(e,t){let n=" ".repeat(t),s="";for(let a of e.entries){if(a.child){s+=`${n}${Ze(a.key)}: {
597
+ `,s+=Qp(a.child,t+2),s+=`${n}};
598
+ `;continue}a.op&&(s+=`${n}${Ef(a.key)}${Pf(a.op)};
599
+ `)}return s}function Pf(e){let t=Ss(e.operationId),n=[],s=new Set;for(let r of e.pathParams)n.push(`${Rf(r,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(Af(e.headers));let a=e.sse?`Promise<AsyncIterable<${e.output?`${t}Response`:"unknown"}>>`:e.output?`Promise<${t}Response>`:"Promise<void>";return`(${n.join(", ")}): ${a}`}function Af(e){if(!e||e.props.length===0)return"options?: CallOptions";let t=e.props.some(a=>a.required),s=`{ ${e.props.map(a=>{let r=a.required?"":"?";return`${Ze(a.name)}${r}: ${Ct(a.schema)}`}).join("; ")} }`;return t?`options: CallOptions & { headers: ${s} }`:`options?: CallOptions & { headers?: ${s} }`}function Rf(e,t){let n=vs(e,!1);(Tf.has(n)||n==="input"||n==="query"||n==="options")&&(n+="_");let s=n;for(let a=2;t.has(s);a++)s=n+a;return t.add(s),s}var Tf=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 Ze(e){return fs.test(e)?e:oe(e)}function Ef(e){return e==="new"?oe(e):Ze(e)}var Cf=/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ud800-\udfff]/;function ed(e,t){switch(e){case"\\":return"\\\\";case`
600
+ `:return"\\n";case"\r":return"\\r";case"'":return t?"\\'":e}return e.length===1&&Cf.test(e)?`\\u${e.charCodeAt(0).toString(16).padStart(4,"0")}`:e}function oe(e){let t="'";for(let n of e)t+=ed(n,!0);return`${t}'`}function ie(e){let t="";for(let n of e)t+=n==="@"?"\\u0040":n==="#"?"\\u0023":ed(n,!1);return t.replaceAll("*/","*\\/")}function jf(e){return`${Ne(e)}Room`}function Of(e,t){let n=jf(e.pattern),s=[];for(let r of e.events)s.push(...Qt(t.claim(`${n}${Ne(r.name)}Event`,`${ar(e)}'s event ${JSON.stringify(r.name)}`),r.schema));for(let r of e.messages)s.push(...Qt(t.claim(`${n}${Ne(r.name)}Message`,`${ar(e)}'s message ${JSON.stringify(r.name)}`),r.schema));for(let r of[`${n}Event`,`${n}Params`,n])t.claim(r,ar(e));if(e.events.length>0){s.push(`export type ${n}Event =`);for(let r of e.events)s.push(` | { event: ${oe(r.name)}; payload: ${n}${Ne(r.name)}Event }`);s[s.length-1]+=";"}else s.push(`export type ${n}Event = never;`);s.push("");let a=e.params.map(r=>`${Ze(r)}: string`).join("; ");return s.push(`export interface ${n}Params { ${a} }`),s.push(`export const ${n} = {`),s.push(` pattern: ${oe(e.pattern)},`),s.push(` params: [${Tn(e.params)}] as const,`),s.push(` events: [${Tn(e.events.map(r=>r.name))}] as const,`),s.push(` messages: [${Tn(e.messages.map(r=>r.name))}] as const,`),s.push(` send: {} as { ${e.messages.map(r=>`${Ze(r.name)}: ${n}${Ne(r.name)}Message`).join("; ")} },`),s.push(` event: {} as ${n}Event,`),s.push("} as const;"),s.push(""),s.join(`
601
+ `)}function xf(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 a=[];for(let i of t.permissions){if(typeof i!="string")throw new Error(`role "${s}" lists a permission that is not a name`);a.push(i)}let r={name:s,isDefault:t.isDefault,permissions:a};return"description"in t&&typeof t.description=="string"&&t.description!==""&&(r.description=t.description),r})}function Lf(e){let t=xf(e).sort((p,d)=>p.name<d.name?-1:p.name>d.name?1:0),n=[...new Set(t.flatMap(p=>p.permissions))].sort(),s=[],a=new Map;for(let p of t){let d=vs(p.name,!1),c=a.get(d);if(c!==void 0){s.push(` // codegen: skipped duplicate role "${ie(p.name)}" \u2014 collides with "${ie(c)}" (both map to \`${ie(d)}\`)`);continue}a.set(d,p.name),p.description!==void 0&&s.push(` /** ${ie(p.description)} */`),p.isDefault&&s.push(" /** Assigned to every new user at signup. */"),s.push(` ${d}: ${oe(p.name)},`)}let r=[],i=new Map;for(let p of n){let d=vs(p,!1),c=i.get(d);if(c!==void 0){r.push(` // codegen: skipped duplicate permission "${ie(p)}" \u2014 collides with "${ie(c)}" (both map to \`${ie(d)}\`)`);continue}i.set(d,p),r.push(` ${d}: ${oe(p)},`)}let o="";if(a.size>0&&(o+=`export const Roles = {
602
602
  `,o+=`${s.join(`
603
603
  `)}
604
604
  `,o+=`} as const;
@@ -607,7 +607,7 @@ public typealias JSONValue = JsonElement
607
607
  `,o+=`export type Role = (typeof Roles)[keyof typeof Roles];
608
608
  `),i.size>0&&(o!==""&&(o+=`
609
609
  `),o+=`export const Permissions = {
610
- `,o+=`${a.join(`
610
+ `,o+=`${r.join(`
611
611
  `)}
612
612
  `,o+=`} as const;
613
613
 
@@ -621,53 +621,74 @@ public typealias JSONValue = JsonElement
621
621
  `,l+="// compile error. Regenerated by `palbase spec` \u2014 a role withdrawn upstream\n",l+=`// disappears from this file, so code still naming it stops building rather
622
622
  `,l+=`// than silently losing access.
623
623
  `,l+=`
624
- `,l+o}var Vm=/^[a-z][A-Za-z0-9]{0,62}$/,Hm=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 $r(e){return e.replace(/-([a-z0-9])/g,(t,n)=>n.toUpperCase())}function Xm(e){if(!Vm.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(Hm.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??$r(r),o=Xm(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 g("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
- `)}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 g("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 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 g("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 Pe(e,n.id),r=[...new Set([...t?.apps.map(k=>k.id)??[],...e.args])];if(r.length===0)throw m("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=xe(s,k);if(yn("app",V.slug),V.deleted_at)throw new g("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 g("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 Pt=u.get(Z)??new Map;Pt.set(de.machine,{url:de.url,digest:de.contract_digest??""}),u.set(Z,Pt)}}}let A=new Set([...u.values()].flatMap(k=>[...k.keys()]));if(d&&d.machine===void 0){if(A.size!==1)throw m(`--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 m(`--backend: ${d.machine} is not a backend this checkout's apps reach`,`they reach ${[...A].join(", ")||"none"}`);let $=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 m(`--as ${k}: it is <machine>=<name>`,"palbase apps:sync --as shop-api=shop");if(!A.has(O))throw m(`--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===$r(O)?delete S[O]:S[O]=z}let C;try{C=ls(A,S)}catch(k){throw new g("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 P=new Map,U=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(([Pt],[At])=>Pt.localeCompare(At))){let Pt=de.url,At=C.get(ie),$s=$!==void 0&&ie===d?.machine,en=$s?$:await e.studio.get(`/environments/${O}/machines/${encodeURIComponent(ie)}/openapi`),Jd=$s?Ym("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 g("unusable_contract",`${ie}'s contract in ${k} makes no ${ut} client: ${Wd.message}`,{hint:`fix the backend, deploy it, and sync again${$s?"":` (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:Pt,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"),U.set(k,[...U.get(k)??[],ut])}if(z.has("kotlin")){let ut=Ts("Kotlin",()=>gl(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)}P.set(k,[...P.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,P.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:U.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 m(`--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
- ${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
- ${z}`;for(let[O,z]of v){z.has(F)?i.set(`${B}/${O}.ts`,k(`export * from './environments/${F}/${O}';
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
- `))}for(let{machine:O}of P.get(F)??[])i.set(`${B}/palbe/${O}.ts`,k(`export * from '../environments/${F}/palbe/${O}';
632
- `));for(let O of L)for(let{machine:z}of P.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 pp(e,t,n,s,r){for(let a of t){let i=e.get(a);if(i!==void 0&&i!==n)throw new g("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 gh(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 g("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 gh(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 Qm(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 g("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 Zm(a)).isSymbolicLink())throw new g("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 Pe(e,t){return(await e.studio.get(`/projects/${t}/apps`)).apps}function xe(e,t){let n=e.find(s=>s.slug===t||s.id===t);if(!n)throw new g("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 gp={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 Pe(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"}])})}},mp={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 m("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=xe(await Pe(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=xe(await Pe(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 m("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=xe(await Pe(e,t.id),e.args[0]);if(!e.global.yes){if(!e.interactive)throw new g("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 g("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=xe(await Pe(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)`))}},mh=/^([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=xe(await Pe(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 m(`${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 m(`${n.name} is an Android app: give its package here (--package)`);let i=(e.flags.sha256??[]).map(o=>{let l=mh.exec(o.trim());if(!l)throw m(`--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 m(`${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=xe(await Pe(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=xe(await Pe(e,t.project_id),e.args[0]),s=e.flags.format??"json";if(s!=="json"&&s!=="env")throw m("--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 gs}from"node:fs/promises";import{dirname as hh,extname as fh,join as Ne,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 Ph(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=Ne(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(Ne(e,r));return[...new Set(n)].sort()}async function Pp(e,t,n,s,r=new Map){if(s!==void 0&&!kp.test(s))throw new g("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 g("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 g("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 g("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 g("unexpected_answer",`${JSON.stringify(d)} is not a machine's name`);let l=[],p=[],u=0;for(let d of await Ph(e,t)){let f=await gs(Ne(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 $=jr(hh(Ne(e,d)),Ne(e,"palbase",...A)).split(yh).join("/");return $.startsWith(".")?$:`./${$}`},T=v(s);y=y.replace(/import\s*\{\s*pb\s*\}\s*from\s*(['"])@palbase\/web\1/g,(A,$)=>`import { pb } from ${$}${T}${$}`),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,$,S,C,P,U)=>{if(n.size<=1)return`${$}${S}${C}${P}${T}${P}`;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 ${U} 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):`${$}${S}${C}${P}${v("palbe",L[0])}${P}`})}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
- `),r=(s?n.before.slice(0,-1):n.before).split(`
635
- `),a=(s?n.after.slice(0,-1):n.after).split(`
636
- `);if(r.length!==a.length||s!==n.after.endsWith(`
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(`
624
+ `,l+o}var Df=/^[a-z][A-Za-z0-9]{0,62}$/,If=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 or(e){return e.replace(/-([a-z0-9])/g,(t,n)=>n.toUpperCase())}function Nf(e){if(!Df.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(If.has(e))return`${e} is a word TypeScript, Swift or Kotlin keeps`;if(!bs(e))return`${e} is a name every object has (pb.${e}) \u2014 the backend is named another way`}function $s(e,t){let n=new Map,s=new Map;for(let a of[...new Set(e)].sort()){let r=Object.hasOwn(t,a)?t[a]:void 0,i=r??or(a),o=Nf(i);if(o)throw new Error(r!==void 0?`--as ${a}=${i}: ${o}`:`${a}'s name in the apps, ${i}: ${o} \u2014 name it (palbase apps:sync --as ${a}=<name>)`);let l=s.get(i);if(l!==void 0)throw new Error(`${l} and ${a} would both be pb.${i}: name one of them (palbase apps:sync --as ${a}=<name>)`);s.set(i,a),n.set(a,i)}return n}var U="palbase",jn="// Code generated by palbase apps:sync",en=e=>`${JSON.stringify(e,null,2)}
625
+ `,Ff={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 Cn(e,t){if(typeof t!="string"||!Ff[e].test(t))throw new h("unexpected_answer",`the project answered ${e} ${JSON.stringify(t)}, which is not one`);return t}function lr(e){return String(e).replace(/[\u0000-\u001f\u007f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g," ")}function Wf(e){return e==="web"?"ts":e==="ios"?"swift":e==="android"?"kotlin":null}var nd=e=>e.charAt(0).toUpperCase()+e.slice(1);function Vf(e,t,n=[]){let s={url:t.url,publishableKey:t.publishable_key,environment:t.environment.slug,app:e.slug},a=[...n].sort((r,i)=>r.client.localeCompare(i.client));return[`${jn} \u2014 DO NOT EDIT.`,`// The settings of ${e.slug} (${lr(e.name)}) in ${t.environment.slug} (${lr(t.environment.name)}): what it is built with. Digest: ${lr(t.digest)}`,"import { createClient, type ClientOptions, type PalbaseClient } from 'palbase/web';",...a.flatMap(r=>[`import './palbe/${r.machine}';`,`import type { Backend as ${nd(r.client)}Backend } from './palbe/${r.machine}';`]),...a.length===1?[`export * from './palbe/${a[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${a.map(r=>`, pb.${r.client}`).join("")}. */`,`export type Client = PalbaseClient${a.length>0?` & { ${a.map(r=>`${r.client}: ${nd(r.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
+ `)}async function Hf(e,t){let n=await Uf(qe(Bf(),"palbase-contract-")),s=qe(n,"openapi.json");try{let a=await e.io.run("bun",["--no-env-file","run","src/main.ts"],{cwd:t,env:{PALBASE_CONTRACT_OUT:s},envOnly:!0}),r;try{r=await dr(s,"utf8")}catch{throw new h("no_contract",`the backend in ${t} wrote no contract (bun run src/main.ts exited ${a.code})`,{hint:`${(a.stderr||a.stdout).trim().split(`
627
+ `).slice(-3).join(" ").slice(0,300)||"is it a palbase/backend app, started from src/main.ts?"}`})}return JSON.parse(r)}finally{await ad(n,{recursive:!0,force:!0})}}function Xf(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:td(t,e.slice(n+1))}:{dir:td(t,e)}}async function Yf(e,t){let n=await wt(e);if(t&&t.project.id!==n.id)throw new h("another_project",`this checkout's ${U}/ 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 Ce(e,n.id),a=[...new Set([...t?.apps.map(T=>T.id)??[],...e.args])];if(a.length===0)throw m("name the app(s) this checkout builds: palbase apps:sync <app>",`the project's apps: ${s.map(T=>T.slug).join(", ")||"none \u2014 palbase apps:create"}`);let r=[];for(let T of a){let V=Ge(s,T);if(Cn("app",V.slug),V.deleted_at)throw new h("app_deleted",`${V.name} is deleted (restorable until ${V.purge_after})`,{hint:`palbase apps:restore ${V.slug} \u2014 the sync writes nothing meanwhile`});r.some(M=>M.id===V.id)||r.push(V)}let i=new Map,o=[],l=new Map,p=new Map,d=new Map,c=Xf(e.flags.backend,e.io.cwd),g=new Map,f=new Map,b=new Map,P=new Map;for(let T of r){let V=Wf(T.platform);T.environments.length===0&&o.push(`${T.name} is in no environment yet: palbase apps:env:set ${T.slug} -E <env> \u2026`);for(let M of T.environments){if(M.suspended)continue;let H=await e.studio.get(`/environments/${M.ref}/apps/${T.id}/config`),ee=Cn("environment",H.environment.slug),pe=[...l.keys()].find(he=>he!==ee&&he.toLowerCase()===ee.toLowerCase());if(pe!==void 0)throw new h("environments_collide",`${pe} and ${ee} differ only in case: their files under ${U}/environments/ would be one directory's on macOS and Windows`,{hint:"rename one of the environments"});l.set(ee,H.environment),p.set(ee,H.url),i.set(`${U}/environments/${ee}/${T.slug}.json`,en(H)),T.platform==="ios"&&P.set(T,(P.get(T)??new Map).set(ee,en(H))),T.platform==="web"&&(f.set(T,(f.get(T)??new Map).set(ee,H)),b.set(T.slug,(b.get(T.slug)??new Set).add(ee))),V&&g.set(ee,(g.get(ee)??new Set).add(V));for(let he of H.backends){if(Cn("machine",he.machine),he.contract_digest!==void 0&&Cn("digest",he.contract_digest),!he.contract_digest&&!(c&&(c.machine??he.machine)===he.machine)){o.push(`${he.machine} in ${ee} 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 It=d.get(ee)??new Map;It.set(he.machine,{url:he.url,digest:he.contract_digest??""}),d.set(ee,It)}}}let E=new Set([...d.values()].flatMap(T=>[...T.keys()]));if(c&&c.machine===void 0){if(E.size!==1)throw m(`--backend ${String(e.flags.backend)}: name the machine it is (--backend <machine>=<dir>)`,`this checkout's apps reach ${[...E].join(", ")||"no backend"}`);c.machine=[...E][0]}if(c&&!E.has(c.machine))throw m(`--backend: ${c.machine} is not a backend this checkout's apps reach`,`they reach ${[...E].join(", ")||"none"}`);let k=c?await Hf(e,c.dir):void 0;c&&o.push(`${c.machine}'s Palbe is made from the backend in ${c.dir}, not from what its machine serves: sync again without --backend once it is deployed`);let $={...t?.clients??{}};for(let T of e.flags.as??[]){let V=T.indexOf("="),M=V>0?T.slice(0,V):"";if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(M))throw m(`--as ${T}: it is <machine>=<name>`,"palbase apps:sync --as shop-api=shop");if(!E.has(M))throw m(`--as ${T}: ${M} is not a backend this checkout's apps reach`,`they reach ${[...E].join(", ")||"none"}`);let H=T.slice(V+1);H===""||H===or(M)?delete $[M]:$[M]=H}let O;try{O=$s(E,$)}catch(T){throw new h("invalid_backend_name",T.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 T of Object.keys($))E.has(T)||delete $[T];let A=new Map,L=new Map,X=(T,V,M)=>[`${jn.slice(3)} \u2014 DO NOT EDIT.`,`${T}'s client in ${V}, from its contract (contracts/${T}.openapi.json, sha256 ${M||"of this checkout"}): palbase apps:sync makes it again`];for(let[T,V]of[...d].sort(([M],[H])=>M.localeCompare(H))){let M=l.get(T)?.ref,H=g.get(T)??new Set,ee=new Map([["swift",new Map],["kotlin",new Map]]);for(let[pe,he]of[...V].sort(([It],[Nt])=>It.localeCompare(Nt))){let It=he.url,Nt=O.get(pe),na=k!==void 0&&pe===c?.machine,yn=na?k:await e.studio.get(`/environments/${M}/machines/${encodeURIComponent(pe)}/openapi`),Ku=na?Mf("sha256").update(en(yn)).digest("hex"):he.digest;i.set(`${U}/environments/${T}/contracts/${pe}.openapi.json`,en(yn));let sa=X(pe,T,Ku),aa=(kt,Uu)=>{try{return Uu()}catch(zu){throw new h("unusable_contract",`${pe}'s contract in ${T} makes no ${kt} client: ${zu.message}`,{hint:`fix the backend, deploy it, and sync again${na?"":` (palbase machines:builds ${pe} says what its build wrote)`}`})}};if(H.has("ts")&&i.set(`${U}/environments/${T}/palbe/${pe}.ts`,aa("TypeScript",()=>Vp(yn,{project:p.get(T),client:Nt,machine:pe,url:It,header:sa}))),H.has("swift")){let kt=aa("Swift",()=>Pp(yn,{client:Nt,machine:pe,header:sa}));sd(ee.get("swift"),Zf(kt),pe,T,"Swift"),L.set(T,[...L.get(T)??[],kt])}if(H.has("kotlin")){let kt=aa("Kotlin",()=>op(yn,{client:Nt,machine:pe,header:sa}));sd(ee.get("kotlin"),Qf(kt),pe,T,"Kotlin"),i.set(`${U}/environments/${T}/palbe/${pe}.kt`,kt)}A.set(T,[...A.get(T)??[],{machine:pe,client:Nt}])}}for(let[T,V]of f)for(let[M,H]of V)i.set(`${U}/environments/${M}/${T.slug}.ts`,Vf(T,H,A.get(M)??[]));for(let[T,V]of P){let M={};for(let[H,ee]of[...V].sort(([pe],[he])=>pe.localeCompare(he)))M[H]={swift:Bl({environment:H,document:ee,backends:L.get(H)??[]})};i.set(`${U}/${T.slug}.palbase`,en({schema:1,app:T.slug,environments:M}))}let I=[...new Set([...b.values()].flatMap(T=>[...T]))].sort(),j=e.flags.types??t?.types_environment;if(j!==void 0&&Cn("environment",j),j!==void 0&&I.length>0&&!I.includes(j))throw m(`--types ${j}: this checkout's web apps are set in ${I.join(", ")}`);if(j===void 0&&I.length>0&&(j=I.find(T=>!l.get(T)?.production)??I[0]),j!==void 0&&I.length>0){let T=M=>`${jn} \u2014 DO NOT EDIT. The editor's types are ${j}'s; a build imports its mode's environment (palbase() in vite.config).
628
+ ${M}`,V=(M,H)=>`${jn} \u2014 DO NOT EDIT. ${M}'s, which a build made for ${M} type-checks the app against (palbase() in vite.config).
629
+ ${H}`;for(let[M,H]of b){H.has(j)?i.set(`${U}/${M}.ts`,T(`export * from './environments/${j}/${M}';
630
+ `)):o.push(`${M} is not in ${j}: the editor has no types for it (palbase apps:sync --types <env>)`);for(let ee of H)i.set(`${U}/${M}.${ee}.ts`,V(ee,`export * from './environments/${ee}/${M}';
631
+ `))}for(let{machine:M}of A.get(j)??[])i.set(`${U}/palbe/${M}.ts`,T(`export * from '../environments/${j}/palbe/${M}';
632
+ `));for(let M of I)for(let{machine:H}of A.get(M)??[])i.set(`${U}/palbe/${H}.${M}.ts`,V(M,`export * from '../environments/${M}/palbe/${H}';
633
+ `))}let ne={schema:1,project:{id:n.id,name:n.name},apps:r.map(T=>({id:T.id,slug:T.slug,name:T.name,platform:T.platform})),...j!==void 0&&I.length>0?{types_environment:j}:{},...Object.keys($).length>0?{clients:Object.fromEntries(Object.entries($).sort(([T],[V])=>T.localeCompare(V)))}:{}};return i.set(`${U}/project.json`,en(ne)),{files:i,notes:o,project:ne}}function Zf(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 Qf(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 sd(e,t,n,s,a){for(let r of t){let i=e.get(r);if(i!==void 0&&i!==n)throw new h("backends_collide",`${i} and ${n} both declare ${r} in ${s}'s ${a} 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(r,n)}}async function ey(e){let t=new Map,n=async s=>{let a;try{a=await pr(qe(e,s),{withFileTypes:!0})}catch(r){if(r.code==="ENOENT")return;throw r}for(let r of a){let i=`${s}/${r.name}`;if(r.isDirectory()){(s!==U||r.name==="environments"||r.name==="palbe")&&await n(i);continue}if(!r.isFile()||r.name.endsWith(".storekit"))continue;let o=await dr(qe(e,i),"utf8");(s!==U||r.name==="project.json"||r.name.endsWith(".palbase")||o.startsWith(jn))&&t.set(i,o)}};return await n(U),t}async function cr(e){try{return JSON.parse(await dr(qe(e,U,"project.json"),"utf8"))}catch(t){if(t.code==="ENOENT")return null;throw new h("unreadable_project_file",`${U}/project.json is not what palbase apps:sync writes: ${t.message}`)}}async function rd(e){let t=e.io.cwd,n=e.flags.check===!0,s=await cr(t),a=await Yf(e,s),r=await ey(t),i=[],o=[],l=[];for(let[c,g]of a.files){let f=r.get(c);f===void 0?i.push(c):f!==g&&o.push(c)}for(let c of r.keys())a.files.has(c)||l.push(c);let p=i.length+o.length+l.length>0;if(!n){for(let c of[...i,...o]){let g=await ye(t,c);await Kf(Jf(g),{recursive:!0}),await ye(t,c),await Gf(g,a.files.get(c))}for(let c of l)await ad(await ye(t,c));await id(t,qe(t,U))}let d={project:a.project.project.name,apps:a.project.apps.map(c=>c.slug),added:i,changed:o,removed:l,unchanged:a.files.size-i.length-o.length,check:n,differs:p};for(let c of a.notes)e.out.note(c);return e.out.result(d,()=>{let c=(g,f)=>f.forEach(b=>e.out.line(` ${g} ${b}`));if(!p){e.out.line(`${U}/ is what ${a.project.project.name} gives now (${a.files.size} files)`);return}e.out.line(n?`${U}/ differs from what ${a.project.project.name} gives now:`:`${U}/ written for ${a.project.apps.map(g=>g.slug).join(", ")}:`),c(n?"missing":"added",i),c(n?"differs":"changed",o),c(n?"not given":"removed",l),n&&e.out.note("palbase apps:sync brings it up to date")}),n&&p?1:void 0}async function ye(e,t){let n=qe(e,U),s=qe(e,...t.split("/")),a=As(n,s);if(a===""||a.startsWith(`..${Ps}`)||a===".."||a.split(Ps).some(i=>i===".."||i===""))throw new h("outside_palbase",`${t} is not under ${U}/`);let r=e;for(let i of[U,...a.split(Ps)]){r=qe(r,i);try{if((await qf(r)).isSymbolicLink())throw new h("linked_path",`${As(e,r)} is a link: palbase writes only into ${U}/ itself`,{hint:"remove the link, and run it again"})}catch(o){if(o.code==="ENOENT")break;throw o}}return s}async function id(e,t){let n;try{n=await pr(t,{withFileTypes:!0})}catch{return}for(let s of n)s.isDirectory()&&await id(e,qe(t,s.name));As(qe(e,U),t)===""||As(qe(e,U),t).startsWith(`..${Ps}`)||(await pr(t)).length===0&&await zf(t)}async function wt(e){let t=await nt(e);return{id:t[0].project_id,name:t[0].project_name,org:t[0].org_name}}async function Ce(e,t){return(await e.studio.get(`/projects/${t}/apps`)).apps}function Ge(e,t){let n=e.find(s=>s.slug===t||s.id===t);if(!n)throw new h("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 Rs(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 od={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 wt(e),n=await Ce(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(a=>`${a.slug}: ${Rs(a)}`).join("; ")||"\u2014"}])})}},ld={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 m("the platform is ios, android or web");let s=await wt(e),a=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(a,()=>{e.out.line(`${s.name} \u203A ${a.name} (${a.platform}, ${a.slug})`),e.out.note(`set it in each environment: palbase apps:env:set ${a.slug} -E <env> ${t==="ios"?"--bundle <bundle id>":t==="android"?"--package <application id> --sha256 <label>=<fingerprint>":"--origin <https://\u2026>"}`)})}},pd={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 wt(e),n=Ge(await Ce(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=>Rs(s)},{header:"publishable key",value:s=>s.key?.key??"\u2014"}])})}},dd={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 wt(e),n=Ge(await Ce(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 m("nothing to change: --name, --team-id or --app-id-prefix");let a=await e.studio.request("PATCH",`/projects/${t.id}/apps/${n.id}`,s);e.out.result(a,()=>e.out.line(`${a.name} (${a.slug}) \u2014 told to ${a.environments.length} environment(s)`))}},cd={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 wt(e),n=Ge(await Ce(e,t.id),e.args[0]);if(!e.global.yes){if(!e.interactive)throw new h("confirmation_needed",`deleting ${n.name} stops every build of it reaching the project`,{hint:"confirm with -y"});let a=(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(a!=="y"&&a!=="yes")throw new h("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}`))}},ud={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 wt(e),n=Ge(await Ce(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)`))}},ty=/^([a-z][a-z0-9_-]{0,31})=(.+)$/,md={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 y(e),n=Ge(await Ce(e,t.project_id),e.args[0]),s=i=>(e.flags[i]??[]).flatMap(o=>o.split(",")).map(o=>o.trim()).filter(Boolean),a;if(n.platform==="ios"){if(!e.flags.bundle)throw m(`${n.name} is an iOS app: give its bundle here (--bundle)`);a={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 m(`${n.name} is an Android app: give its package here (--package)`);let i=(e.flags.sha256??[]).map(o=>{let l=ty.exec(o.trim());if(!l)throw m(`--sha256 ${JSON.stringify(o)}: label=fingerprint, like play_signing=AB:CD:\u2026`);return{label:l[1],sha256:l[2]}});a={android:{package:e.flags.package,fingerprints:i}}}else{let i=s("origin");if(i.length===0)throw m(`${n.name} is a web app: give its origins here (--origin)`);let o=s("redirect");a={web:{origins:i,...o.length?{redirects:o}:{}}}}await w(e,t,`set ${n.name} (${n.slug}) here: ${Rs(a)}`);let r=await e.studio.request("PUT",`/environments/${t.env_ref}/apps/${n.id}`,a);for(let i of r.warnings)e.out.warn(i);e.out.result(r,()=>{e.out.line(`${n.name} in ${t.env_name}: ${Rs(r)}`),r.key&&e.out.line(`its publishable key here: ${r.key.key}`),e.out.note("palbase apps:sync writes its settings into this checkout")})}},hd={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 y(e),n=Ge(await Ce(e,t.project_id),e.args[0]);await w(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}`))}},gd={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 y(e),n=Ge(await Ce(e,t.project_id),e.args[0]),s=e.flags.format??"json";if(s!=="json"&&s!=="env")throw m("--format is json or env");let a=await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`);e.out.result(a,()=>{s==="env"?(e.out.line(`PALBASE_URL=${a.url}`),e.out.line(`PALBASE_PUBLISHABLE_KEY=${a.publishable_key}`)):e.out.line(JSON.stringify(a,null,2))})}},fd={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 rd(e)}};import{readdir as Ts,readFile as Es}from"node:fs/promises";import{dirname as ny,extname as sy,join as Be,relative as ur,sep as ay}from"node:path";var ry=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".swift",".kt"]),iy=new Set(["node_modules","dist","build",".build",".git","Pods","DerivedData","xcuserdata",".gradle","palbase"]),oy=/^[^\u0000-\u001f\u007f"\\]+$/,yd=/^[a-z][a-z0-9-]{0,38}[a-z0-9]$/,ly=["call","stream","upload","join"],py=new Set(["auth","flags","realtime","analytics","calls","messaging","perf","storage","notify","configure"]);function dy(e){let t=new Set,n=e?.paths??{};for(let s of Object.values(n))for(let a of Object.values(s??{})){let i=(typeof a?.operationId=="string"?a.operationId:"").split(".")[0];/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&!py.has(i)&&t.add(i)}return t}async function cy(e,t){let n=[],s=async a=>{let r;try{r=await Ts(a,{withFileTypes:!0})}catch(i){if(i.code==="ENOENT")return;throw i}for(let i of r){if(i.isSymbolicLink())continue;let o=Be(a,i.name);i.isDirectory()?!iy.has(i.name)&&!i.name.startsWith(".")&&await s(o):i.isFile()&&ry.has(sy(i.name))&&oy.test(ur(e,o))&&n.push(ur(e,o))}};for(let a of t)await s(Be(e,a));return[...new Set(n)].sort()}async function bd(e,t,n,s,a=new Map){if(s!==void 0&&!yd.test(s))throw new h("unexpected_answer",`${JSON.stringify(s)} is not a web app's slug`);for(let[c,g]of n){if(!/^[a-z][A-Za-z0-9]{0,62}$/.test(c))throw new h("unexpected_answer",`${JSON.stringify(c)} is not a backend's name in an app`);for(let f of g)if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(f))throw new h("unexpected_answer",`${JSON.stringify(f)} is not a backend's top-level name`)}let r=new Map;for(let[c,g]of n)for(let f of g){let b=r.get(f);if(b!==void 0)throw new h("ambiguous_name",`pb.${f} is ${b}'s and ${c}'s: a v2 call of it cannot be told which \u2014 move those calls by hand (pb.${b}.${f} or pb.${c}.${f})`);r.set(f,c)}if(n.size===1)for(let c of ly)r.set(c,[...n.keys()][0]);let i=[...r.keys()].sort((c,g)=>g.length-c.length).map(c=>c.replace(/\$/g,"\\$")).join("|"),o=i?new RegExp(`(?<![\\w.$])pb(\\s*\\.\\s*)(${i})(?![\\w$])`,"g"):void 0;for(let c of a.keys())if(!/^[a-z]([a-z0-9-]{0,30}[a-z0-9])?$/.test(c))throw new h("unexpected_answer",`${JSON.stringify(c)} is not a machine's name`);let l=[],p=[],d=0;for(let c of await cy(e,t)){let g=await Es(Be(e,c),"utf8"),f=o?g.replace(o,(b,P,E)=>(d++,`pb${P}${r.get(E)}.${E}`)):g;if(s&&/\.(?:[cm]?[jt]sx?)$/.test(c)){let b=(...E)=>{let k=ur(ny(Be(e,c)),Be(e,"palbase",...E)).split(ay).join("/");return k.startsWith(".")?k:`./${k}`},P=b(s);f=f.replace(/import\s*\{\s*pb\s*\}\s*from\s*(['"])@palbase\/web\1/g,(E,k)=>`import { pb } from ${k}${P}${k}`),f=f.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,(E,k,$,O,A,L)=>{if(n.size<=1)return`${k}${$}${O}${A}${P}${A}`;let X=$.startsWith("{")?$.slice(1,-1).split(",").map(j=>j.trim().replace(/^type\s+/,"").split(/\s+as\s+/)[0]).filter(j=>j!==""):[],I=[...a].filter(([,j])=>X.length>0&&X.every(ne=>j.has(ne))).map(([j])=>j);return I.length!==1?(p.push(`${c}: its import from ${L} is no one backend's \u2014 ${X.length===0?"it takes the module whole":`${X.join(", ")} ${I.length===0?"are not all one backend's":`are ${I.join("'s and ")}'s`}`}: import them from palbase/palbe/<machine>`),E):`${k}${$}${O}${A}${b("palbe",I[0])}${A}`})}f!==g&&l.push({path:c,before:g,after:f})}return{changes:l,moved:d,left:p}}function wd(e){let t=[];for(let n of e){let s=n.before.endsWith(`
634
+ `),a=(s?n.before.slice(0,-1):n.before).split(`
635
+ `),r=(s?n.after.slice(0,-1):n.after).split(`
636
+ `);if(a.length!==r.length||s!==n.after.endsWith(`
637
+ `))throw new Error(`${n.path}: a change moved lines`);let i=p=>!s&&p===a.length-1?["\"]:[];t.push(`diff --git a/${n.path} b/${n.path}`,`--- a/${n.path}`,`+++ b/${n.path}`);let o=a.map((p,d)=>p!==r[d]),l=0;for(;l<a.length;){if(!o[l]){l++;continue}let p=Math.max(0,l-3),d=l;for(let P=l;P<a.length;P++)if(o[P])d=P;else if(P-d>6)break;let c=Math.min(a.length,d+4),g=[],f=p;for(;f<c;){if(!o[f]){g.push(` ${a[f]}`,...i(f)),f++;continue}let P=[];for(;f<c&&o[f];)P.push(f++);for(let E of P)g.push(`-${a[E]}`,...i(E));for(let E of P)g.push(`+${r[E]}`,...i(E))}let b=c-p;t.push(`@@ -${p+1},${b} +${p+1},${b} @@`,...g),l=c}}return t.length>0?`${t.join(`
638
638
  `)}
639
- `:""}async function $p(e){let t;try{t=JSON.parse(await gs(Ne(e,"palbase","project.json"),"utf8"))}catch(l){throw l.code==="ENOENT"?new g("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(Ne(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(Ne(e,"palbase","environments",l,"palbe"))}catch{continue}for(let u of p.filter(d=>d.endsWith(".ts"))){let d=await gs(Ne(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(Ne(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 gs(Ne(e,"palbase","environments",l,"contracts",u),"utf8")));n.set(d,new Set([...n.get(d)??[],...f]))}}if(n.size===0)throw new g("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 $p(t),a=e.flags.app??(s.length===1?s[0]:void 0);if(e.flags.app!==void 0&&!s.includes(e.flags.app))throw m(`--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 m("this checkout has several web apps: name the one whose pb its modules take (--app)",`its web apps: ${s.join(", ")}`);let i=await Pp(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 m(`${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 $h={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 ${q(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 ${q(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 m("--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 m("--dev or --prod, not both");let n=Number(e.flags.limit??30);if(!Number.isInteger(n)||n<1||n>500)throw m("--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=[$h,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 ge(e.flags.events)}function ms(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(ms(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 m(`--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 m("--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 ${ms(i)}; its secret is ${n} (${s}, v${o.version}) in ${q(t)}.`));return}e.out.result(r,()=>{e.out.line(`Added ${ms(i)}.`),e.out.line(`signing secret (shown this once): ${a}`)})}},xh={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 m("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 m(`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: ${ms(r)}.`))}},Nh={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,xh,Nh];import{constants as hs,createReadStream as qh}from"node:fs";import{mkdir as Kh,open as Mh,readdir as Uh,realpath as jp,stat as Lp}from"node:fs/promises";import{dirname as Op,extname as Gh,join as xp,relative as Bh,resolve as Dp,sep as fs}from"node:path";import{Readable as Np}from"node:stream";import{pipeline as Jh}from"node:stream/promises";var qp=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 g("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 Kp(e,t,n,s){let r;try{r=await e.io.fetch(t,{...n,redirect:"manual"})}catch(a){throw new g("network_error",`${s}: ${a instanceof Error?a.message:String(a)}`)}if(!r.ok){let a=await r.json().catch(()=>({}));throw new g(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:qp,...a?{owner_id:a}:{}});return await Kp(e,o.url,{method:"PUT",body:Np.toWeb(qh(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 g("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=xp(e,...n);if(!s.startsWith(e+fs))throw new g("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:qp}),o=await Kp(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${r}`);if(await Kh(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 g("unsafe_path",`${s} would land outside ${a} (through a link)`)}let p=(await Mh(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=Np.fromWeb(o.body).on("data",f=>{u+=f.length});return await Jh(d,p),u}async function Mp(e){let t=[];for(let n of await Uh(e,{withFileTypes:!0})){let s=xp(e,n.name);n.isDirectory()?t.push(...await Mp(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 m(`${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 m("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!r&&!a)throw m("one side is bucket:path");let l=0,p=0;if(a){let u=Dp(e.io.cwd,n),d=i?await Mp(u):[u];if(!i&&(await Lp(u)).isDirectory())throw m(`${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 ${q(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 m(`${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 ${q(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 m(`${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 m("--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))}},Up=[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=($,S)=>{let C=S.price,P=S.intro?.price??void 0;if(C.currency.toUpperCase()===n)return{price:C.amount,intro:P??void 0};let U=C.overrides?.[n];return U!==void 0?{price:U,intro:P==null?void 0:Math.round(P*U/C.amount)}:(s.push(`${$} has no ${n} price: its ${Wt(C.amount,C.currency)} ${C.currency.toUpperCase()} is written as ${n}`),{price:C.amount,intro:P??void 0})},a=$=>{let S=new Set,C=[];for(let P of Object.keys($.channels.ios?.locales??{}).sort()){let U=$.channels.ios?.locales[P],X=df(P);S.has(X)||(S.add(X),C.push({description:U.description??"",displayName:U.name??"",locale:X}))}return C.sort((P,U)=>P.locale<U.locale?-1:P.locale>U.locale?1:0)},i=[],o=[],l=new Map;for(let $ of e.products){let S=$.channels.ios;if(!(!S||S.plans.length===0)){if($.type==="subscription"){let C=$.group??$.key;l.set(C,[...l.get(C)??[],...S.plans.map(P=>({offering:$,plan:P}))]);continue}for(let C of S.plans){let{price:P}=r(C.store_id,C),U={displayPrice:Wt(P,n),familyShareable:!1,internalID:Ft(`product:${C.store_id}`),localizations:a($),productID:C.store_id,referenceName:C.store_id,type:Bp[$.type]};($.type==="non_renewing"?o:i).push(U)}}}let p=[],u=0;for(let $ of[...l.keys()].sort()){let S=l.get($),C=Ft(`group:${$}`),P=Math.max(...S.map(X=>X.offering.tier??1)),U=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?gf(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:P-(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:[]}]});U.length!==0&&(U.sort((X,L)=>X.groupNumber-L.groupNumber||(X.productID<L.productID?-1:X.productID>L.productID?1:0)),p.push({id:C,localizations:[],name:$,subscriptions:U}))}let d=($,S)=>$.productID<S.productID?-1:1;i.sort(d),o.sort(d);let f=[...i,...o,...p.flatMap($=>$.subscriptions)],y=t.settings?._locale,v=typeof y=="string"&&f.some($=>Hp($).has(y))?y:mf(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 gf(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 mf(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?`[
639
+ `:""}async function vd(e){let t;try{t=JSON.parse(await Es(Be(e,"palbase","project.json"),"utf8"))}catch(l){throw l.code==="ENOENT"?new h("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 Ts(Be(e,"palbase","environments"))}catch{}let a=new Map,r=typeof t.types_environment=="string"&&s.includes(t.types_environment)?t.types_environment:void 0;for(let l of s){if(r!==void 0&&l!==r)continue;let p=[];try{p=await Ts(Be(e,"palbase","environments",l,"palbe"))}catch{continue}for(let d of p.filter(c=>c.endsWith(".ts"))){let c=await Es(Be(e,"palbase","environments",l,"palbe",d),"utf8"),g=d.slice(0,-3),f=a.get(g)??new Set;for(let b of c.matchAll(/^export (?:declare )?(?:abstract )?(?:class|interface|type|const|let|enum|function) ([A-Za-z_$][\w$]*)/gm))f.add(b[1]);a.set(g,f)}}for(let l of s){let p=[];try{p=await Ts(Be(e,"palbase","environments",l,"contracts"))}catch{continue}for(let d of p){let c=d.replace(/\.openapi\.json$/,"");if(c===d)continue;let g=dy(JSON.parse(await Es(Be(e,"palbase","environments",l,"contracts",d),"utf8")));n.set(c,new Set([...n.get(c)??[],...g]))}}if(n.size===0)throw new h("no_backend","this checkout's apps reach no backend with a contract yet",{hint:"palbase apps:sync, once the backend is deployed"});let i=$s(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"&&yd.test(l.slug)).map(l=>l.slug),modules:a}}var _d={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:a}=await vd(t),r=e.flags.app??(s.length===1?s[0]:void 0);if(e.flags.app!==void 0&&!s.includes(e.flags.app))throw m(`--app ${String(e.flags.app)} is not a web app of this checkout`,`its web apps: ${s.join(", ")||"none"}`);if(r===void 0&&s.length>1)throw m("this checkout has several web apps: name the one whose pb its modules take (--app)",`its web apps: ${s.join(", ")}`);let i=await bd(t,e.args.length>0?e.args:["."],n,r,a);e.io.stdout(wd(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 uy=/^[A-Z][A-Z0-9_]{0,127}$/,tn={dev:{type:"boolean",description:"the development set (what a backend on a developer's computer reads), not production"}};function nn(e){return e.flags.dev===!0?"development":"production"}function On(e){let t=e.args[0];if(!uy.test(t))throw m(`${JSON.stringify(t)} is not a secret's name: capital letters, digits and _, a letter first`);return t}async function mr(e,t,n){let s=[],a;do{let r=await u(e,t,"GET",`/secrets/${n}?limit=1000${a?`&cursor=${encodeURIComponent(a)}`:""}`);s.push(...r.secrets),a=r.next_cursor}while(a);return s}var my={name:"secrets:list",summary:"the environment's secrets by name, with their version \u2014 never their values",usage:"[--dev]",flags:tn,scope:"environment",async run(e){let t=await y(e),n=nn(e),s=await mr(e,t,n);e.out.result({set:n,secrets:s},()=>{s.length===0&&e.out.line(`No ${n} secrets.`);for(let a of s)e.out.line(`${a.name} v${a.version} ${a.updated_at}`)})}},hy={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:tn,scope:"environment",async run(e){let t=await y(e),n=On(e),s=nn(e),a=await $e(e,`the value of ${n}`);await w(e,t,`set the ${s} secret ${n}`);let r=await u(e,t,"PUT",`/secrets/${s}/${n}`,{value:a});e.out.result({set:s,name:n,version:r.version},()=>e.out.line(`Set ${n} (${s}, v${r.version}) in ${B(t)}.`))}},gy={name:"secrets:unset",summary:"take a secret away",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:tn,scope:"environment",async run(e){let t=await y(e),n=On(e),s=nn(e);await w(e,t,`take away the ${s} secret ${n}`),await u(e,t,"DELETE",`/secrets/${s}/${n}`),e.out.result({set:s,name:n,deleted:!0},()=>e.out.line(`Took ${n} (${s}) away from ${B(t)}.`))}},fy={name:"secrets:get",summary:"print one secret's value (an admin's)",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:tn,scope:"environment",async run(e){let t=await y(e),n=On(e),s=await u(e,t,"GET",`/secrets/${nn(e)}/${n}`);e.out.result({name:n,version:s.version,value:s.value},()=>e.out.line(s.value))}},yy={name:"secrets:versions",summary:"a secret's versions, newest first \u2014 never their values",usage:"<NAME> [--dev]",args:{min:1,max:1},flags:tn,scope:"environment",async run(e){let t=await y(e),n=On(e),s=await u(e,t,"GET",`/secrets/${nn(e)}/${n}/versions`);e.out.result(s,()=>{for(let a of s.versions)e.out.line(`v${a.version} ${a.created_at}${a.current?" (current)":""}`)})}},by={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:{...tn,version:{type:"string",value:"n",description:"the version to go back to"}},scope:"environment",async run(e){let t=await y(e),n=On(e),s=Number(e.flags.version);if(!Number.isInteger(s)||s<1)throw m("--version is a version number (secrets:versions lists them)");let a=nn(e);await w(e,t,`put the ${a} secret ${n} back to v${s}`);let r=await u(e,t,"POST",`/secrets/${a}/${n}/rollback`,{version:s});e.out.result({set:a,name:n,version:r.version},()=>e.out.line(`${n} (${a}) is v${s}'s value again, as v${r.version}.`))}},wy={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 y(e);if(e.flags.dev===!0&&e.flags.prod===!0)throw m("--dev or --prod, not both");let n=Number(e.flags.limit??30);if(!Number.isInteger(n)||n<1||n>500)throw m("--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"}:{}}),a=await u(e,t,"GET",`/secrets/audit?${s}`);e.out.result(a,()=>{a.entries.length===0?e.out.line("No read or write of a secret yet."):e.out.table(a.entries,[{header:"when",value:r=>r.at.slice(0,19).replace("T"," ")},{header:"what",value:r=>r.action},{header:"set",value:r=>r.environment},{header:"secret",value:r=>r.name?`${r.name}${r.version?` v${r.version}`:""}`:"every one"},{header:"key",value:r=>r.key_id}])})}},kd=[my,hy,fy,gy,yy,by,wy];var vy=/^[A-Z][A-Z0-9_]{0,127}$/;function xn(e,t){return`/environments/${e.env_ref}/webhooks${t?`/${encodeURIComponent(t)}`:""}`}function Sd(e){return Q(e.flags.events)}function Cs(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 _y={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 y(e),{endpoints:n}=await e.studio.get(xn(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(Cs(s))})}},ky={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 y(e),n=e.flags["secret-to"];if(n!==void 0&&!vy.test(n))throw m(`--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 m("--dev goes with --secret-to <NAME>");let s=e.flags.dev===!0?"development":"production";await w(e,t,`add the webhook endpoint ${e.args[0]}${n?`, its secret kept in ${n} (${s})`:""}`);let a=await e.studio.post(xn(t),{url:e.args[0],events:Sd(e)??[]}),{secret:r,...i}=a;if(n!==void 0){let o=await u(e,t,"PUT",`/secrets/${s}/${n}`,{value:r});e.out.result({endpoint:i,secret_to:{set:s,name:n,version:o.version}},()=>e.out.line(`Added ${Cs(i)}; its secret is ${n} (${s}, v${o.version}) in ${B(t)}.`));return}e.out.result(a,()=>{e.out.line(`Added ${Cs(i)}.`),e.out.line(`signing secret (shown this once): ${r}`)})}},Sy={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 y(e);if(e.flags.url===void 0&&e.flags.events===void 0)throw m("nothing to change: --url or --events");let{endpoints:n}=await e.studio.get(xn(t)),s=n.find(r=>r.id===e.args[0]);if(!s)throw m(`the environment has no webhook endpoint ${JSON.stringify(e.args[0])}`,"palbase webhooks:list shows them");await w(e,t,`change the webhook endpoint ${s.url}`);let a=await e.studio.request("PUT",xn(t,s.id),{url:e.flags.url??s.url,events:Sd(e)??s.events});e.out.result(a,()=>e.out.line(`Changed: ${Cs(a)}.`))}},$y={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 y(e);await w(e,t,`remove the webhook endpoint ${e.args[0]}`),await e.studio.request("DELETE",xn(t,e.args[0])),e.out.result({deleted:e.args[0]},()=>e.out.line(`Removed the webhook endpoint ${e.args[0]}.`))}},$d=[_y,ky,Sy,$y];import{constants as js,createReadStream as Py}from"node:fs";import{mkdir as Ay,open as Ry,readdir as Ty,realpath as Pd,stat as Ed}from"node:fs/promises";import{dirname as Ad,extname as Ey,join as Cd,relative as Cy,resolve as Rd,sep as Os}from"node:path";import{Readable as jd}from"node:stream";import{pipeline as jy}from"node:stream/promises";var Od=600;function Ln(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 xs(e){if(e.split("/").some(n=>n===""||n==="."||n===".."))throw new h("unsafe_path",`the object ${JSON.stringify(e)} has a name no request can carry`);return encodeURIComponent(e)}async function hr(e,t,n,s){let a=[],r;do{let i=new URLSearchParams({limit:"1000",...s?{prefix:s}:{},...r?{cursor:r}:{}}),o=await u(e,t,"GET",`/storage/buckets/${encodeURIComponent(n)}/objects?${i}`);a.push(...o.objects),r=o.next_cursor}while(r);return a}async function xd(e,t,n,s){let a;try{a=await e.io.fetch(t,{...n,redirect:"manual"})}catch(r){throw new h("network_error",`${s}: ${r instanceof Error?r.message:String(r)}`)}if(!a.ok){let r=await a.json().catch(()=>({}));throw new h(r.error??`http_${a.status}`,`${s}: ${r.error_description??r.error??`answered ${a.status}`}`)}return a}var Oy={".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 Td(e){return Oy[Ey(e).toLowerCase()]}async function gr(e,t,n,s,a,r){let i=(await Ed(n)).size,o=await u(e,t,"POST",`/storage/buckets/${encodeURIComponent(s)}/upload-urls`,{path:a,expires_in:Od,...r?{owner_id:r}:{}});return await xd(e,o.url,{method:"PUT",body:jd.toWeb(Py(n)),headers:{"content-length":String(i),...Td(n)?{"content-type":Td(n)}:{}},duplex:"half"},`${n} \u2192 ${s}:${a}`),i}function xy(e,t){let n=t.split("/");if(t===""||n.some(a=>a===""||a==="."||a===".."||a.includes("\\")||a.includes("\0")))throw new h("unsafe_path",`the object ${JSON.stringify(t)} has a name no file here can take`);let s=Cd(e,...n);if(!s.startsWith(e+Os))throw new h("unsafe_path",`${t} would land outside ${e}`);return s}async function Ly(e,t,n,s,a,r){let i=await u(e,t,"POST",`/storage/buckets/${encodeURIComponent(n)}/signed-urls`,{path:s,expires_in:Od}),o=await xd(e,i.url,{method:"GET"},`${n}:${s} \u2192 ${a}`);if(await Ay(Ad(a),{recursive:!0}),r!==void 0){let[g,f]=await Promise.all([Pd(r),Pd(Ad(a))]);if(f!==g&&!f.startsWith(g+Os))throw new h("unsafe_path",`${s} would land outside ${r} (through a link)`)}let p=(await Ry(a,js.O_WRONLY|js.O_CREAT|js.O_TRUNC|js.O_NOFOLLOW)).createWriteStream();if(!o.body)return await new Promise((g,f)=>p.on("error",f).end(g)),0;let d=0,c=jd.fromWeb(o.body).on("data",g=>{d+=g.length});return await jy(c,p),d}async function Ld(e){let t=[];for(let n of await Ty(e,{withFileTypes:!0})){let s=Cd(e,n.name);n.isDirectory()?t.push(...await Ld(s)):n.isFile()&&t.push(s)}return t}var Dy={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 y(e),n=Ln(e.args[0].includes(":")?e.args[0]:`${e.args[0]}:`);if(!n)throw m(`${e.args[0]} is not a bucket (bucket or bucket:prefix)`);let s=await hr(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 a of s)e.out.line(`${a.path} ${a.size} ${a.content_type??""}`)})}},Iy={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 y(e),[n,s]=e.args,a=Ln(n),r=Ln(s),i=e.flags.recursive===!0,o=e.flags.owner;if(a&&r)throw m("one side is a bucket and the other this computer (bucket-to-bucket: the storage API copies within a bucket)");if(!a&&!r)throw m("one side is bucket:path");let l=0,p=0;if(r){let d=Rd(e.io.cwd,n),c=i?await Ld(d):[d];if(!i&&(await Ed(d)).isDirectory())throw m(`${n} is a folder: copy it with -r`);await w(e,t,`upload ${c.length} file${c.length===1?"":"s"} to ${r.bucket}:${r.path} in ${B(t)}`);for(let g of c){let f=i?Cy(d,g).split(Os).join("/"):g.split(Os).pop(),b=i||r.path===""||r.path.endsWith("/")?`${r.path.replace(/\/?$/,r.path?"/":"")}${f}`:r.path;p+=await gr(e,t,g,r.bucket,b,o),l++,c.length>1&&e.out.note(`${l}/${c.length} ${b}`)}}else if(a){let d=Rd(e.io.cwd,s),c=i?await hr(e,t,a.bucket,a.path):[{path:a.path,size:0}];for(let g of c){let f=i?g.path.slice(a.path.length).replace(/^\/+/,""):"",b=i?xy(d,f):d;p+=await Ly(e,t,a.bucket,g.path,b,i?d:void 0),l++,c.length>1&&e.out.note(`${l}/${c.length} ${g.path}`)}}e.out.result({copied:l,bytes:p},()=>e.out.line(`Copied ${l} file${l===1?"":"s"} (${p} bytes).`))}},Ny={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 y(e),n=Ln(e.args[0]);if(!n||!n.path&&e.flags.recursive!==!0)throw m(`${e.args[0]} is not bucket:path`);let s=e.flags.recursive===!0?(await hr(e,t,n.bucket,n.path)).map(a=>a.path):[n.path];await w(e,t,`delete ${s.length} object${s.length===1?"":"s"} from ${n.bucket} in ${B(t)}`);for(let a of s)await u(e,t,"DELETE",`/storage/buckets/${encodeURIComponent(n.bucket)}/objects/${xs(a)}`);e.out.result({deleted:s.length},()=>e.out.line(`Deleted ${s.length} object${s.length===1?"":"s"}.`))}},My={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 y(e),n=Ln(e.args[0]);if(!n||!n.path)throw m(`${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 m("--expires is 1 to 604800 seconds");let a=await u(e,t,"POST",`/storage/buckets/${encodeURIComponent(n.bucket)}/signed-urls`,{path:n.path,expires_in:s});e.out.result(a,()=>e.out.line(a.url))}},Dd=[Dy,Iy,Ny,My];import{openAsBlob as eb}from"node:fs";import{readFile as tb,stat as Wd}from"node:fs/promises";import{basename as nb,resolve as Vd}from"node:path";import{createHash as qy}from"node:crypto";var Ky={major:5,minor:0},Md={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"},rn=new Map(Object.entries(Md).flatMap(([e,t])=>t.split(" ").map(n=>[n,e])).sort(([e],[t])=>e<t?-1:1)),Id={USD:"USA",EUR:"DEU"};function qd(e){let t=e.toUpperCase();if(Id[t])return Id[t];let n=Md[t]?.split(" ");return n?.length===1?n[0]:void 0}var Uy=new Set(["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","VND","VUV","XAF","XOF","XPF"]),zy=new Set(["BHD","JOD","KWD","OMR","TND"]);function Gy(e){let t=e.toUpperCase();return Uy.has(t)?0:zy.has(t)?3:2}function sn(e,t){let n=Gy(t),s=e<0?"-":"",a=String(Math.abs(Math.round(e))).padStart(n+1,"0");return n===0?`${s}${a}`:`${s}${a.slice(0,-n)}.${a.slice(-n)}`}var Kd=["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"],By={en:"en-US",fr:"fr-FR",es:"es-ES",pt:"pt-PT",zh:"zh-Hans",nb:"no",nn:"no",iw:"he",in:"id"},Jy={"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"},Fy=new Map(Kd.map(e=>[e.toLowerCase(),e])),Ls=new Map;for(let e of Kd)Ls.has(e.split("-")[0])||Ls.set(e.split("-")[0],e);for(let[e,t]of Object.entries(By))Ls.set(e,t);var Wy={"ar-SA":"ar","de-DE":"de","fr-FR":"fr","nl-NL":"nl"};function Vy(e){let t=e.trim().replaceAll("_","-").toLowerCase(),n=Fy.get(t)??Jy[t]??Ls.get(t.split("-")[0]);return n===void 0?e.trim().replaceAll("-","_"):Wy[n]??n.replaceAll("-","_")}function an(e){return qy("sha256").update(e).digest("hex").slice(0,8).toUpperCase()}var Hy=["Load Products","Purchase","Verification","App Store Sync","Subscription Status","App Transaction","Manage Subscriptions Sheet","Refund Request Sheet","Offer Code Redeem Sheet"];function Xy(){return{_askToBuyEnabled:!1,_billingGracePeriodEnabled:!1,_billingIssuesEnabled:!1,_disableDialogs:!1,_failTransactionsEnabled:!1,_renewalBillingIssuesEnabled:!1,_storeKitErrors:Hy.map(e=>({enabled:!1,name:e})),_timeRate:0}}var Nd={consumable:"Consumable",non_consumable:"NonConsumable",non_renewing:"NonRenewingSubscription",subscription:"RecurringSubscription"},Ud={free_trial:"free",pay_as_you_go:"payAsYouGo",pay_up_front:"payUpFront"};function zd(e,t){let n=rn.get(t.storefront);if(n===void 0)throw new Error(`${t.storefront} is not an App Store storefront`);let s=[],a=(k,$)=>{let O=$.price,A=$.intro?.price??void 0;if(O.currency.toUpperCase()===n)return{price:O.amount,intro:A??void 0};let L=O.overrides?.[n];return L!==void 0?{price:L,intro:A==null?void 0:Math.round(A*L/O.amount)}:(s.push(`${k} has no ${n} price: its ${sn(O.amount,O.currency)} ${O.currency.toUpperCase()} is written as ${n}`),{price:O.amount,intro:A??void 0})},r=k=>{let $=new Set,O=[];for(let A of Object.keys(k.channels.ios?.locales??{}).sort()){let L=k.channels.ios?.locales[A],X=Vy(A);$.has(X)||($.add(X),O.push({description:L.description??"",displayName:L.name??"",locale:X}))}return O.sort((A,L)=>A.locale<L.locale?-1:A.locale>L.locale?1:0)},i=[],o=[],l=new Map;for(let k of e.products){let $=k.channels.ios;if(!(!$||$.plans.length===0)){if(k.type==="subscription"){let O=k.group??k.key;l.set(O,[...l.get(O)??[],...$.plans.map(A=>({offering:k,plan:A}))]);continue}for(let O of $.plans){let{price:A}=a(O.store_id,O),L={displayPrice:sn(A,n),familyShareable:!1,internalID:an(`product:${O.store_id}`),localizations:r(k),productID:O.store_id,referenceName:O.store_id,type:Nd[k.type]};(k.type==="non_renewing"?o:i).push(L)}}}let p=[],d=0;for(let k of[...l.keys()].sort()){let $=l.get(k),O=an(`group:${k}`),A=Math.max(...$.map(X=>X.offering.tier??1)),L=$.flatMap(({offering:X,plan:I})=>{if(I.period===void 0)return s.push(`${I.store_id} is a subscription without a period: it is left out`),[];let{price:j,intro:ne}=a(I.store_id,I),T=I.intro?Yy(I.store_id,I.intro,ne,n):void 0;return I.intro&&Ud[I.intro.mode]===void 0&&s.push(`${I.store_id}: an introductory offer ${I.intro.mode} is not one Xcode has; it is left out`),d++,[{adHocOffers:[],billingPlans:[{billingPlanType:"BILLED_UPFRONT",commitmentDisplayPrice:sn(j,n),displayPrice:sn(j,n),internalID:an(`billing:${I.store_id}`),isEnabled:!0}],codeOffers:[],displayPrice:sn(j,n),familyShareable:!1,groupNumber:A-(X.tier??1)+1,internalID:an(`product:${I.store_id}`),...T?{introductoryOffer:T}:{},introductoryOffers:T?[T]:[],localizations:r(X),productID:I.store_id,recurringSubscriptionPeriod:I.period,referenceName:I.store_id,subscriptionGroupID:O,type:Nd.subscription,winbackOffers:[]}]});L.length!==0&&(L.sort((X,I)=>X.groupNumber-I.groupNumber||(X.productID<I.productID?-1:X.productID>I.productID?1:0)),p.push({id:O,localizations:[],name:k,subscriptions:L}))}let c=(k,$)=>k.productID<$.productID?-1:1;i.sort(c),o.sort(c);let g=[...i,...o,...p.flatMap(k=>k.subscriptions)],f=t.settings?._locale,b=typeof f=="string"&&g.some(k=>Gd(k).has(f))?f:Zy(g),P={...t.settings??Xy(),_locale:b,_storefront:t.storefront},E={appPolicies:{eula:"",policies:[{locale:b,policyText:"",policyURL:""}]},identifier:an(`configuration:${t.seed}`),nonRenewingSubscriptions:o,products:i,settings:P,subscriptionGroups:p,version:{...Ky}};return{text:`${fr(E,"")}
640
+ `,storefront:t.storefront,currency:n,locale:b,counts:{groups:p.length,subscriptions:d,products:i.length,non_renewing:o.length},notes:[...new Set(s)].sort()}}function Yy(e,t,n,s){let a=Ud[t.mode];if(a!==void 0)return{billingPlanType:"BILLED_UPFRONT",...a!=="free"&&n!==void 0?{displayPrice:sn(n,s)}:{},internalID:an(`intro:${e}`),numberOfPeriods:t.mode==="pay_as_you_go"?Math.max(1,t.periods??1):1,paymentMode:a,subscriptionPeriod:t.duration}}function Gd(e){return new Set((e.localizations??[]).map(t=>t.locale))}function Zy(e){let t=e.map(Gd);if(t.length===0)return"en_US";let n=[...t[0]].filter(s=>t.every(a=>a.has(s))).sort();return n.includes("en_US")?"en_US":n[0]??[...t[0]].sort()[0]??"en_US"}function Qy(e,t){let n=e.toLowerCase(),s=t.toLowerCase();return n!==s?n<s?-1:1:e<t?-1:e>t?1:0}function fr(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
- ${e.map(r=>n+Ir(r,n)).join(`,
643
+ ${e.map(a=>n+fr(a,n)).join(`,
644
644
  `)}
645
- ${t}]`;let s=Object.keys(e).filter(r=>e[r]!==void 0).sort(hf);return s.length===0?`{
645
+ ${t}]`;let s=Object.keys(e).filter(a=>e[a]!==void 0).sort(Qy);return s.length===0?`{
646
646
 
647
647
  ${t}}`:`{
648
- ${s.map(r=>`${n}${JSON.stringify(r)} : ${Ir(e[r],n)}`).join(`,
648
+ ${s.map(a=>`${n}${JSON.stringify(a)} : ${fr(e[a],n)}`).join(`,
649
649
  `)}
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 m(`${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 g("unexpected_address",`${s} is not a URL: nothing was sent`)}if(a!==r)throw new g("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 g("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 Nr(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 Pf={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,()=>Nr(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 m("--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 m("--key-file names the .p8 file","or --print-link for a link a browser opens");if(s===void 0||s.trim()==="")throw m("--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 m(`--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 g("not_a_key",`${n} is larger than a .p8 key`);i=await yf(d,"utf8")}catch(d){throw d instanceof g?d:new g("unreadable_file",`${n} could not be read: ${d instanceof Error?d.message:String(d)}`)}if(!i.includes("-----BEGIN PRIVATE KEY-----"))throw new g("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 ${q(t)}.`),Nr(e,u)})}},$f={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,()=>Nr(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 ${q(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=Ke(e.flags.limit,"limit")??20;if(n<1||n>200)throw m("--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 m("--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 m("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)})`))})}},xf={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 m("--file names the .ipa");let r=e.flags.token??e.io.env.PALBASE_UPLOAD_TOKEN;if(r===void 0||r.trim()==="")throw m("--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 g("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 g("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 Nf(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 g(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 qf={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 m(`--channel is one of ${Yp.join(", ")}`);let r=e.flags["what-to-test"];if(e.flags.locale!==void 0&&r===void 0)throw m("--locale is the --what-to-test note's");let a={channel:s},i=ge(e.flags.groups);i&&i.length>0&&(a.groups=await Nf(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}`)})}},Kf={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}`:""}`:""}])})}},Mf={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 m("--text is the note testers read");if(s.length>4e3)throw m(`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 m("a group's name is 1 to 40 characters on one line");return n}var Uf={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 m('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=ge(e.flags.emails);if(s===void 0==(e.flags.file===void 0))throw m("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 m(`${JSON.stringify(p.email)} is not an email address`);if(r.length>Lr)throw m(`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 m("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 qr(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=qr("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=qr("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 m("--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=qr("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 m("--status is planned, done, declined or failed");let s=Ke(e.flags.limit,"limit")??30;if(s<1||s>200)throw m("--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 m("--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 g("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}.`))}},xr={"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(xr).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 g("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=xr[s.trim().toLowerCase()];if(l===void 0)throw m(`--renewal ${s}: a monthly subscription renews every ${Object.keys(xr).join(", ")}`);n.subscription_renewal_rate=l}let r=e.flags.interrupt;if(r!==void 0){if(r!=="on"&&r!=="off")throw m("--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 m(`--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 m("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=[Pf,Af,$f,Rf,oy,ly,py,Ef,Cf,jf,Df,If,Lf,xf,qf,Kf,Mf,Uf,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 Mr,stat as od,writeFile as ld}from"node:fs/promises";import{basename as cy,dirname as gy,relative as my,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 m(`${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 m("the catalog is a JSON object: { base_locale, products, entitlements, credits, limits }");if("catalog"in t&&"revision"in t)throw m('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 g("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=Ke(e.flags.revision,"revision");if(n===void 0)throw m("--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 g("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 g("invalid_catalog",`the catalog breaks ${s.problems.filter(r=>!r.warning).length} rule(s)`)}};function Kr(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?Kr(e,ws.stripe,t.stripe):e.out.line(`${ws.stripe}: no account connected (palbase purchases:stripe:grant connects one)`),Kr(e,ws.apple,t.apple),Kr(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 m("--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 g("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 g("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 g("plan_failed",`the plan ${i.id} failed: a new plan picks up where it stopped`)}},Py={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 $y={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 m("--until or --for, not both");if(t!==void 0){let s=Date.parse(t);if(!Number.isFinite(s))throw m(`--until ${t}: a date or time (2026-12-31, 2026-12-31T18:00:00Z)`);if(s<=e.io.now())throw m(`--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 m(`--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 m("--reason says why it is given: the grant's record");if(r.length>500)throw m("--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 Ur(e,t){let n=(await Pe(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=xe(n,r);if(o.platform!=="ios")throw m(`${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 g("no_ios_app",`${t.project_name} has no iOS app`,{hint:"register it: palbase apps:create ios <name> --team-id <team id>"}):m("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=my(e.io.cwd,vs(e.io.cwd,t)).split(hy).join("/");if(!n.startsWith(`${B}/`)||n.split("/").includes(".."))throw m(`--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 m(`--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 m(`--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 Ur(e,t);if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(t.env_slug))throw new g("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 g("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 g("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 Mr(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(gy(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===$e,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,$=[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 ${$}, priced in ${d.currency} (storefront ${d.storefront}), catalog revision ${a.revision}.`),e.out.line(""),T.forEach((S,C)=>e.out.line(`${C+1}. ${S}`))})}},xy=/^SubscriptionKey_([A-Z0-9]{10})\.p8$/,Ny=/^AuthKey_[A-Z0-9]{10}\.p8$/,qy=8192,Ky=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 My={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 Uy(e,t){let n=e.environments.find(s=>s.ref===t.env_ref)?.ios?.bundle;if(n===void 0)throw new g("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 m("--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 m("--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(Ny.test(s))throw m(`${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=xy.exec(s)?.[1],r===void 0)throw m(`--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>qy)throw new g("not_a_key",`${n} is larger than a .p8 key`);a=await Mr(y,"utf8")}catch(y){throw y instanceof g?y:new g("unreadable_file",`${n} could not be read: ${y instanceof Error?y.message:String(y)}`)}if(!a.includes("-----BEGIN PRIVATE KEY-----"))throw new g("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 m("--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 Ur(e,t),l=Uy(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 ${q(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 ${q(t)}.`))}};async function Jy(e,t){let n=(await Pe(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===$e:(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>Ky)throw new g("not_a_certificate",`${e} is larger than Xcode's certificate`);n=await Mr(a)}catch(a){throw a instanceof g?a:new g("unreadable_file",`${e} could not be read: ${a instanceof Error?a.message:String(a)}`)}let s;try{s=new dy(n)}catch{throw new g("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 m("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(`${q(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 g("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(`${q(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 Ur(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: ${q(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}},gd=[by,wy,vy,Sy,ky,Py,$y,Ty,Ey,Cy,jy,Ly,My,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,md=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 m(`${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>md)throw new g("user_too_large",`the user ${String(r.id??"")} is larger than one call takes (${a} bytes)`);(n.length===eb||s+a>md)&&(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 m(`${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 m(`${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 m(`${d} names another hash than the files before it`);i=f.hash}}if(a.length===0)throw m(`${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 m("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 ${q(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 g("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 m(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function Pn(e,t,n,s){if(e===void 0)return;let r=Number(e);if(!Number.isInteger(r)||r<n||r>s)throw m(`--${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 m(`--${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=Pn(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.`),gb={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))})}},mb={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=Pn(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 m(`--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 m("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 m("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 m("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=Pn(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=Pn(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(" "))}])})}},Pb={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 g("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 m("--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 m(`--${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
- `)||a===""?a:`${a}
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
- `)||a===""?a:`${a}
658
- `)}},$b={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 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(Pn(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,Pb,Ab,$b,Rb,gb,mb,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 $n(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 g("no_database","the environment has no database",{hint:"palbase databases:create opens one"}):m("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 g("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 xb(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 m("--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 g("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 Pd={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 $n(e,t,e.args[0]),s=await Ye(e,t,n,Sd(e),e.flags.pooled===!0),r=e.flags["with-password"]===!0?xb(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 $n(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 g("psql_failed",i.stderr.trim()||`psql ended with ${i.code}`);return}if(!e.interactive)throw m("db:shell with no person at the terminal needs --command <sql>");return e.out.note(`${q(t)} \u203A ${n} as ${s.role}`),(await Yt(e,"psql",[s.url],s,!0)).code}},$d={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 $n(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 g("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 $n(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 g("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 m("db:copy needs --from <env> and --to <env>");if(t===n)throw m("--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 g("no_such_environment",`the project has no environment ${JSON.stringify(r?n:t)}`);let i=ue(r),o=ue(a),l=await $n(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 g("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 Nb,mkdir as qb,readFile as Kb,rename as Mb,rm as Ub,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
- `)}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.
650
+ ${t}}`}var Bd=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,sb=/^AuthKey_([A-Z0-9]{10})\.p8$/,ab=8192,Jd=["testflight"],yr=100,Fd=5,rb=5,ib=300,In={beta_review:"Beta App Review",export_compliance:"the export compliance answer",expire_build:"expiring the build"};function D(e){return e?e.slice(0,19).replace("T"," "):"\u2014"}function le(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw m(`${n} is required`);return s}function on(e,t,n,s){let a=Fe(e.cloud,t.env_ref),r;try{r=new URL(n).origin}catch{throw new h("unexpected_address",`${s} is not a URL: nothing was sent`)}if(r!==a)throw new h("unexpected_address",`${s} names ${r}, not the environment's address ${a}: nothing was sent`);return a}async function Ds(e,t,n,s){let a;try{a=await e.io.fetch(t,{...n,redirect:"manual"})}catch(l){throw new h("network_error",`${s}: ${t} could not be reached: ${l instanceof Error?l.message:String(l)}`)}let r=await a.text(),i;if(r!=="")try{i=JSON.parse(r)}catch{i=r}if(a.status>=200&&a.status<300)return i;let o=i??{};throw new x({op:s,status:a.status,code:typeof o.error=="string"?o.error:`http_${a.status}`,message:ka(i,a.status),requestId:typeof o.request_id=="string"?o.request_id:a.headers.get("x-request-id")??void 0,retryAfter:ob(a.headers.get("retry-after"),o.retry_after,e.io.now()),body:i})}function ob(e,t,n){if(e!==null&&e.trim()!==""){let s=Number(e);if(Number.isFinite(s)&&s>=0)return s;let a=Date.parse(e);if(Number.isFinite(a))return Math.max(0,Math.ceil((a-n)/1e3))}return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0}function wr(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 ${D(t.checked_at)})`:""}`),t.capabilities&&e.out.line(`reaches: ${t.capabilities.length>0?t.capabilities.join(", "):"nothing"}`),t.connected_at&&e.out.line(`connected ${D(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 lb={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 y(e),n=await u(e,t,"GET","/stores/apple");e.out.result(n,()=>wr(e,n))}},pb={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 y(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw m("--print-link or --key-file, not both");await w(e,t,"make a link that connects an Apple account");let c=await u(e,t,"POST","/stores/apple/grants",void 0,R());on(e,t,c.connect_url,"the connect link"),e.out.result({connect_url:c.connect_url,expires_at:c.expires_at},()=>{e.out.line(c.connect_url),e.out.note(`Open it in a browser and pick the .p8 file; it works once, until ${D(c.expires_at)}.`)});return}let n=e.flags["key-file"],s=e.flags["issuer-id"];if(n===void 0)throw m("--key-file names the .p8 file","or --print-link for a link a browser opens");if(s===void 0||s.trim()==="")throw m("--issuer-id is the account's issuer id (App Store Connect \u2192 Users and Access \u2192 Integrations)");let a=nb(n),r=e.flags["key-id"]??sb.exec(a)?.[1];if(r===void 0)throw m(`--key-id is needed: ${a} is not named AuthKey_<KEYID>.p8`);let i;try{let c=Vd(e.io.cwd,n);if((await Wd(c)).size>ab)throw new h("not_a_key",`${n} is larger than a .p8 key`);i=await tb(c,"utf8")}catch(c){throw c instanceof h?c:new h("unreadable_file",`${n} could not be read: ${c instanceof Error?c.message:String(c)}`)}if(!i.includes("-----BEGIN PRIVATE KEY-----"))throw new h("not_a_key",`${n} is not a .p8 key (no "BEGIN PRIVATE KEY")`);await w(e,t,`connect the Apple account of issuer ${s} with key ${r}`);let o=await u(e,t,"POST","/stores/apple/grants",void 0,R()),l=on(e,t,o.connect_url,"the connect link"),p={grant:o.grant,issuer_id:s.trim(),key_id:r,private_key:i,file_name:a},d=await Ds(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(d,()=>{e.out.line(`Connected key ${r} to ${B(t)}.`),wr(e,d)})}},db={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 y(e),n=await u(e,t,"POST","/stores/apple/check",void 0,R());e.out.result(n,()=>wr(e,n))}},cb={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 y(e);await w(e,t,"disconnect the Apple account"),await u(e,t,"DELETE","/stores/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the Apple account from ${B(t)}.`))}};function ub(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 mb={name:"stores:apps",summary:"the environment's apps linked to their store",scope:"environment",async run(e){let t=await y(e),n=await u(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"}])})}},hb={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 y(e),n=le(e,0,"the app's id"),s=e.flags.name!==void 0?{name:e.flags.name}:{};await w(e,t,`link the app ${n} to the App Store`);let a=await u(e,t,"POST",`/stores/apps/${v(n)}/link`,s,R());e.out.result(a,()=>{e.out.line(`Linked ${a.name} (${a.bundle_id}, team ${a.team_id}).`),ub(e,a)})}},gb={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 y(e),n=le(e,0,"the app's id"),s=await u(e,t,"GET",`/stores/apps/${v(n)}/feedback`);e.out.result(s,()=>{s.feedback.length===0?e.out.line("No feedback yet."):e.out.table(s.feedback,[{header:"when",value:a=>D(a.created_at)},{header:"kind",value:a=>a.kind},{header:"tester",value:a=>a.email??""},{header:"comment",value:a=>a.comment??""}])})}};function fb(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 ${D(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 ${D(t.created_at)} by ${t.created_by} (command ${t.command_id})`);for(let n of t.deliveries)e.out.line(`${n.channel}: ${Hd(n)}`)}function Hd(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 yb={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 y(e),n=fe(e.flags.limit,"limit")??20;if(n<1||n>200)throw m("--limit is a whole number from 1 to 200");let s=new URLSearchParams({limit:String(n),...e.flags.app?{app_id:e.flags.app}:{}}),a=await u(e,t,"GET",`/stores/packages?${s}`);e.out.result(a,()=>{a.packages.length===0?e.out.line("No package yet."):e.out.table(a.packages,[{header:"package",value:r=>r.id},{header:"app",value:r=>r.app_id},{header:"build",value:r=>String(r.build_number)},{header:"version",value:r=>r.version??""},{header:"status",value:r=>r.status},{header:"testflight",value:r=>r.deliveries.find(i=>i.channel==="testflight")?.status??""},{header:"made",value:r=>D(r.created_at)}]),a.next_cursor&&e.out.note(`More than ${n}: raise --limit to see older ones.`)})}},bb={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 y(e),n=await u(e,t,"GET",`/stores/packages/${v(le(e,0,"the package's id"))}`);e.out.result(n,()=>fb(e,n))}},wb={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 y(e),n=le(e,0,"the app's id"),s=e.flags["command-id"];if(s===void 0||s.trim()==="")throw m("--command-id names the request: sent again, it answers the same package");let a={command_id:s};if(e.flags.notes!==void 0&&(a.notes=e.flags.notes),e.flags["entitlements-file"]!==void 0){let o=ge(await _e(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 m("the entitlements are an object of bundle id \u2192 its entitlements object");a.entitlements=o}await w(e,t,`make a package of ${n}`);let r=await we(e,t,"POST",`/stores/apps/${v(n)}/packages`,a,R()),i=r.body;e.out.result(i,()=>{if(e.out.line(`Package ${i.id} of ${i.app_id}: build number ${i.build_number}${r.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 ${D(i.upload.expires_at)})`))})}},vb={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 y(e),n=le(e,0,"the package's id"),s=e.flags.file;if(s===void 0)throw m("--file names the .ipa");let a=e.flags.token??e.io.env.PALBASE_UPLOAD_TOKEN;if(a===void 0||a.trim()==="")throw m("--token is the package's upload token (stores:packages:create printed it)","or PALBASE_UPLOAD_TOKEN");let r=Vd(e.io.cwd,s),i;try{let d=await Wd(r);if(!d.isFile())throw new Error("not a file");i=d.size}catch(d){throw new h("unreadable_file",`${s} could not be read: ${d instanceof Error?d.message:String(d)}`)}await w(e,t,`upload ${s} (${i} bytes) as the build of package ${n}`);let o=`${Fe(e.cloud,t.env_ref)}/stores/packages/${v(n)}/artifact`,l;try{l=await eb(r)}catch(d){throw new h("unreadable_file",`${s} could not be read: ${d instanceof Error?d.message:String(d)}`)}let p=`${s} \u2192 package ${n}`;for(let d=1;;d++)try{let c=await Ds(e,o,{method:"PUT",headers:{"palbase-upload-token":a,"content-type":"application/octet-stream","content-length":String(l.size),accept:"application/json"},body:l},p);e.out.result(c,()=>{e.out.line(`Package ${c.id}: build ${c.build_number}${c.version?`, version ${c.version}`:""} arrived (${c.size??l.size} bytes, sha256 ${c.sha256??"?"}).`),e.out.line(`Send it to testers: palbase stores:deliver ${c.id}`)});return}catch(c){if(!(c instanceof x))throw c;if(c.status===409&&c.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 g=c.status===409&&c.code==="upload_in_progress"||c.status===503&&c.code==="stores_busy",f=Math.max(c.retryAfter??rb,1);if(!g||d>=Fd||f>ib)throw c;e.out.note(`${p}: ${c.message}; trying again in ${f} s (${d+1} of ${Fd})`),await e.io.sleep(f*1e3)}}};async function _b(e,t,n,s){if(s.every(i=>Bd.test(i)))return s;let a=await u(e,t,"GET",`/stores/packages/${v(n)}`),r=(await u(e,t,"GET",`/stores/apps/${v(a.app_id)}/groups`)).groups;return s.map(i=>{if(Bd.test(i))return i;let o=r.filter(p=>p.name===i),l=o.length>0?o:r.filter(p=>p.name.toLowerCase()===i.toLowerCase());if(l.length!==1)throw new h(l.length===0?"no_such_group":"ambiguous_group",`${a.app_id} has ${l.length===0?"no group":"more than one group"} named ${JSON.stringify(i)}`,{hint:`palbase stores:groups ${a.app_id} lists them`});return l[0].id})}var kb={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 y(e),n=le(e,0,"the package's id"),s=e.flags.channel??"testflight";if(!Jd.includes(s))throw m(`--channel is one of ${Jd.join(", ")}`);let a=e.flags["what-to-test"];if(e.flags.locale!==void 0&&a===void 0)throw m("--locale is the --what-to-test note's");let r={channel:s},i=Q(e.flags.groups);i&&i.length>0&&(r.groups=await _b(e,t,n,i)),a!==void 0&&(r.what_to_test={text:a,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}}),await w(e,t,`send package ${n} to ${s}${r.groups?` (${r.groups.length} group${r.groups.length===1?"":"s"})`:""}`);let o=await u(e,t,"POST",`/stores/packages/${v(n)}/deliveries`,r,R());e.out.result(o,()=>{e.out.line(`Package ${n} \u2192 ${o.channel}: ${Hd(o)}.`),o.status!=="ready"&&o.status!=="failed"&&e.out.line(`Follow it: palbase stores:deliveries ${n}`)})}},Sb={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 y(e),n=le(e,0,"the package's id"),s=await u(e,t,"GET",`/stores/packages/${v(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:a=>a.channel},{header:"status",value:a=>a.status},{header:"apple",value:a=>a.processing_state??""},{header:"groups",value:a=>String(a.groups.length)},{header:"updated",value:a=>D(a.updated_at)},{header:"error",value:a=>a.error_code?`${a.error_code}${a.error?`: ${a.error}`:""}`:""}])})}},$b={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 y(e),n=le(e,0,"the package's id"),s=e.flags.text;if(s===void 0||s.trim()==="")throw m("--text is the note testers read");if(s.length>4e3)throw m(`the note is 4000 characters at most (${s.length} were given)`);let a={text:s,...e.flags.locale!==void 0?{locale:e.flags.locale}:{}};await w(e,t,`change what testers read with package ${n}${a.locale?` (${a.locale})`:""}`);let r=await u(e,t,"PUT",`/stores/packages/${v(n)}/what-to-test`,a,R());e.out.result(r,()=>e.out.line(`Testers of package ${n} read${a.locale?` (${a.locale})`:""}: ${r.what_to_test??s}`))}};function Xd(e,t){let n=(e.args[t]??"").trim();if(n===""||n.length>40||/[\r\n]/.test(n))throw m("a group's name is 1 to 40 characters on one line");return n}var Pb={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 y(e),n=le(e,0,"the app's id"),s=await u(e,t,"GET",`/stores/apps/${v(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:a=>a.id},{header:"name",value:a=>a.name},{header:"testers",value:a=>String(a.testers)},{header:"updated",value:a=>D(a.updated_at)}])})}},Ab={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 y(e),n=le(e,0,"the app's id"),s=Xd(e,1);await w(e,t,`make the group ${JSON.stringify(s)} of ${n}`);let a=await u(e,t,"POST",`/stores/apps/${v(n)}/groups`,{name:s},R());e.out.result(a,()=>e.out.line(`Made ${a.name} (${a.id}).`))}},Rb={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 y(e),n=le(e,0,"the group's id"),s=Xd(e,1);await w(e,t,`rename the group ${n} to ${JSON.stringify(s)}`);let a=await u(e,t,"PATCH",`/stores/groups/${v(n)}`,{name:s},R());e.out.result(a,()=>e.out.line(`${a.id} is ${a.name}.`))}},Tb={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 y(e),n=le(e,0,"the group's id");await w(e,t,`delete the group ${n}`),await u(e,t,"DELETE",`/stores/groups/${v(n)}`,void 0,R()),e.out.result({id:n,deleted:!0},()=>e.out.line(`Deleted the group ${n}.`))}},Eb={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 y(e),n=le(e,0,"the group's id"),s=await u(e,t,"GET",`/stores/groups/${v(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:a=>a.email},{header:"name",value:a=>[a.first_name,a.last_name].filter(Boolean).join(" ")},{header:"side",value:a=>a.side},{header:"added",value:a=>D(a.added_at)}])})}};function Cb(e){let t=e.trim();if(t.startsWith("[")){let n=ge(t,"the testers");if(!Array.isArray(n)||!n.every(s=>typeof s=="object"&&s!==null&&typeof s.email=="string"))throw m('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,a,r]=n.split(",").map(i=>i.trim());return{email:s,...a?{first_name:a}:{},...r?{last_name:r}:{}}})}var jb={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. ${yr} 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 y(e),n=le(e,0,"the group's id"),s=Q(e.flags.emails);if(s===void 0==(e.flags.file===void 0))throw m("the testers are named by --emails or --file, one of the two");let a=s?s.map(p=>({email:p})):Cb(await _e(e,e.flags.file,"the testers"));for(let p of a)if(!/^[^@\s]+@[^@\s]+$/.test(p.email))throw m(`${JSON.stringify(p.email)} is not an email address`);if(a.length>yr)throw m(`a group holds ${yr} testers at most (${a.length} were named)`);let r=(await u(e,t,"GET",`/stores/groups/${v(n)}/testers`)).testers,i=new Set(a.map(p=>p.email.toLowerCase())),o=r.filter(p=>!i.has(p.email.toLowerCase())).map(p=>p.email);await w(e,t,`set the testers of ${n} to ${a.length}${o.length>0?`; ${o.length} leave: ${o.join(", ")}`:""}`);let l=await u(e,t,"PUT",`/stores/groups/${v(n)}/testers`,{testers:a},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`:""}.`)})}},Ob={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 y(e),n=le(e,0,"the group's id"),s=le(e,1,"the package's id");await w(e,t,`give package ${s} to the group ${n}`),await u(e,t,"POST",`/stores/groups/${v(n)}/builds`,{package_id:s},R()),e.out.result({group_id:n,package_id:s},()=>e.out.line(`Gave package ${s} to ${n}.`))}},xb={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 Dn(e){return e===""?"(not set)":e}var Lb={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 y(e),n=le(e,0,"the app's id"),s=e.flags.locale!==void 0?`?${new URLSearchParams({locale:e.flags.locale})}`:"",a=await u(e,t,"GET",`/stores/apps/${v(n)}/testflight/review-info${s}`);e.out.result(a,()=>{let r=a.locales.filter(d=>d!==a.locale);e.out.line(`Test information of ${n} in ${a.locale}${r.length>0?` (also in ${r.join(", ")})`:""}:`);let i=[`${a.contact_first_name} ${a.contact_last_name}`.trim(),a.contact_phone,a.contact_email].filter(d=>d!=="").join(", "),o=a.demo_account_required?`required: ${Dn(a.demo_account_name)}, password ${a.demo_account_password_set?"set":"not set"}`:"not required",l=[["what to test",Dn(a.description)],["feedback email",Dn(a.feedback_email)],["review contact",Dn(i)],["demo account",o],["notes",Dn(a.notes)]],p=Math.max(...l.map(([d])=>d.length));for(let[d,c]of l)e.out.line(` ${d.padEnd(p)} ${c}`);e.out.line(a.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${a.missing.join(", ")} (palbase stores:review-info:set ${n} \u2026).`)})}},Db={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 y(e),n=le(e,0,"the app's id"),s={};for(let[r,i]of Object.entries(xb))e.flags[r]!==void 0&&(s[i]=e.flags[r]);if(e.flags["demo-password-stdin"]===!0&&(s.demo_account_password=await $e(e,"the demo account's password")),Object.keys(s).length===0)throw m("nothing to write: name at least one field","palbase help stores:review-info:set");await w(e,t,`write ${Object.keys(s).join(", ")} of ${n}'s Beta App Review information`);let a=await u(e,t,"PUT",`/stores/apps/${v(n)}/testflight/review-info`,s,R());e.out.result(a,()=>{e.out.line(a.missing.length===0?"Beta App Review has what it needs.":`Beta App Review still lacks: ${a.missing.join(", ")}.`)})}};function Ib(e,t){e.out.line(`Planned ${In[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 ${D(t.expires_at)}:`),e.out.line(` palbase stores:actions:confirm ${t.id} --hash ${t.hash}`)}function vr(e,t,n,s,a={},r){return{name:e,summary:s,usage:"<package id>",args:{min:1,max:1},scope:"environment",...a,async run(i){let o=await y(i),l=le(i,0,"the package's id"),p=r?.(i);await w(i,o,`plan ${In[t]} of package ${l}`);let d=await u(i,o,"POST",`/stores/packages/${v(l)}/${n}`,p,R());i.out.result(d,()=>Ib(i,d))}}}var Nb=vr("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"),Mb=vr("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=Kt(e.flags["uses-non-exempt-encryption"],"uses-non-exempt-encryption");if(t===void 0)throw m("--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}}),qb=vr("stores:expire","expire_build","expire","plan expiring a package's build in TestFlight (testers can no longer install it); it runs when confirmed"),Kb={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 y(e),n=e.flags.status;if(n!==void 0&&!["planned","done","declined","failed"].includes(n))throw m("--status is planned, done, declined or failed");let s=fe(e.flags.limit,"limit")??30;if(s<1||s>200)throw m("--limit is a whole number from 1 to 200");let a=new URLSearchParams({limit:String(s),...n?{status:n}:{}}),r=await u(e,t,"GET",`/stores/actions?${a}`);e.out.result(r,()=>{r.actions.length===0?e.out.line(n?`No ${n} action.`:"No action yet."):e.out.table(r.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=>D(i.created_at)},{header:"what",value:i=>i.summary}]),r.next_cursor&&e.out.note(`More than ${s}: raise --limit to see older ones.`)})}},Ub={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 y(e),n=le(e,0,"the action's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw m("--hash is the plan's hash, as stores:actions or the plan printed it: a confirmation names the plan it saw");let a=await u(e,t,"GET",`/stores/actions/${v(n)}`);if(a.status!=="planned")throw new h("action_not_planned",`the action ${n} is ${a.status}: only a planned one is confirmed`);await w(e,t,`run ${In[a.kind]??a.kind} of package ${a.package_id}: ${a.summary}`,"It is done at Apple and cannot be undone");let r=await u(e,t,"POST",`/stores/actions/${v(n)}/confirm`,{hash:s},R());e.out.result(r,()=>{e.out.line(`${In[r.kind]??r.kind} of package ${r.package_id}: ${r.status}.`),r.status==="failed"&&Object.keys(r.result??{}).length>0&&e.out.line(`Apple answered: ${JSON.stringify(r.result)}`)})}},zb={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 y(e),n=le(e,0,"the action's id");await w(e,t,`decline the action ${n}`);let s=await u(e,t,"POST",`/stores/actions/${v(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined ${In[s.kind]??s.kind} of package ${s.package_id}.`))}},br={"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"},Yd=e=>Object.entries(br).find(([,t])=>t===e)?.[0]??e,Gb=100;function Bb(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=>Yd(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 Zd(e,t,n){let s=(await u(e,t,"GET","/stores/apple/sandbox-testers")).testers,a=[];for(let r of n){let i=s.find(o=>o.id===r||o.email.toLowerCase()===r.toLowerCase());if(!i)throw new h("no_such_tester",`the team has no sandbox tester ${r}`,{hint:`its testers: ${s.map(o=>o.email).join(", ")||"none \u2014 App Store Connect \u203A Users and Access \u203A Sandbox makes one"}`});a.some(o=>o.id===i.id)||a.push(i)}return a}var Jb={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 y(e),n=await u(e,t,"GET","/stores/apple/sandbox-testers");e.out.result(n,()=>Bb(e,n))}},Fb={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 y(e),n={},s=e.flags.renewal;if(s!==void 0){let l=br[s.trim().toLowerCase()];if(l===void 0)throw m(`--renewal ${s}: a monthly subscription renews every ${Object.keys(br).join(", ")}`);n.subscription_renewal_rate=l}let a=e.flags.interrupt;if(a!==void 0){if(a!=="on"&&a!=="off")throw m("--interrupt is on or off");n.interrupt_purchases=a==="on"}let r=e.flags.storefront?.trim().toUpperCase();if(r!==void 0){if(!rn.has(r))throw m(`--storefront ${e.flags.storefront}: not an App Store storefront`,"three letters, its country's ISO 3166-1 code: USA, DEU, TUR \u2026");n.territory=r}if(Object.keys(n).length===0)throw m("nothing to change: --renewal, --interrupt or --storefront");let[i]=await Zd(e,t,[e.args[0]]);await w(e,t,`change the sandbox tester ${i.email}`);let o=await u(e,t,"PATCH",`/stores/apple/sandbox-testers/${v(i.id)}`,n,R());e.out.result(o,()=>{e.out.line(`${o.email}: monthly subscriptions renew every ${Yd(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.")})}},Wb={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:Gb},scope:"environment",async run(e){let t=await y(e),n=await Zd(e,t,e.args);await w(e,t,`clear what ${n.map(s=>s.email).join(", ")} bought in the sandbox`),await u(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.")})}},Qd=[lb,pb,db,cb,Jb,Fb,Wb,mb,hb,gb,yb,bb,wb,vb,kb,Sb,$b,Pb,Ab,Rb,Tb,Eb,jb,Ob,Lb,Db,Nb,Mb,qb,Kb,Ub,zb];import{randomUUID as Yb}from"node:crypto";import{mkdir as Zb,stat as Qb,writeFile as ew}from"node:fs/promises";import{basename as Ms,dirname as tw,posix as lc,relative as nw,resolve as cc,sep as sw}from"node:path";var ec="PaywallDefinition",tc="PaywallComponent",sc={PaywallString:"text",PaywallLiteral:"string",PaywallColorValue:"color",PaywallToken:"token",PaywallPoints:"points",PaywallLength:"points",PaywallFontSize:"points",PaywallSizeKeyword:'fit|fill|"50%"',PaywallComponent:"component"},ac=e=>e.replace(/^Paywall/,"").replace(/([a-z])([A-Z])/g,"$1_$2").toLowerCase(),Is=e=>typeof e.$ref=="string"?e.$ref.split("/").pop():void 0,nc="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";function Vb(e){let t=e.replace(/^\^/,"").replace(/\$$/,"").replaceAll(`(${nc})`,"<id>").replaceAll(nc,"<id>");return t=t.replaceAll("([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})","<product>.<plan>").replaceAll("[a-z]{2,3}(-[A-Z][A-Za-z]{1,3})?","<language tag: en, pt-BR>"),t=t.replaceAll("#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})","#RRGGBB|#RRGGBBAA").replaceAll("[a-z][a-z0-9_]{0,31}","token").replaceAll(".+","\u2026"),t.startsWith("(")&&t.endsWith(")")&&!t.slice(1,-1).includes("(")&&(t=t.slice(1,-1)),t}function pn(e){let t=Is(e);return t!==void 0?sc[t]??ac(t):Array.isArray(e.enum)?e.enum.map(String).join("|"):Array.isArray(e.oneOf)?[...new Set(e.oneOf.map(pn))].join(" or "):e.type==="array"?`[${pn(e.items??{})}]`:e.type==="object"&&e.additionalProperties&&typeof e.additionalProperties=="object"?`{ key: ${pn(e.additionalProperties)} }`:e.type==="string"&&typeof e.pattern=="string"?Vb(e.pattern):String(e.type??"any")}function rc(e){let t=[];return typeof e.minimum=="number"&&e.minimum===e.maximum?t.push(`always ${e.minimum}`):(typeof e.minimum=="number"||typeof e.maximum=="number")&&t.push(`${e.minimum??"\u2026"} to ${e.maximum??"\u2026"}`),typeof e.maxLength=="number"&&t.push(`at most ${e.maxLength} characters`),typeof e.maxItems=="number"&&t.push(`${typeof e.minItems=="number"?`${e.minItems} to `:"at most "}${e.maxItems}`),typeof e.maxProperties=="number"&&t.push(`at most ${e.maxProperties}`),e.default!==void 0&&t.push(`default ${JSON.stringify(e.default)}`),t.length===0?"":` (${t.join("; ")})`}function ic(e,t){let n=typeof t.description=="string"?t.description.trim():"",s=`${e.replaceAll("_"," ")}.`;return n===""||n.toLowerCase()===s.toLowerCase()||n==="The component."?"":n}function Se(e,t,n=100){let s=[];for(let a of e.split(`
651
+ `)){let r="";for(let i of a.split(/\s+/).filter(Boolean))r!==""&&t.length+r.length+1+i.length>n?(s.push(t+r),r=i):r=r===""?i:`${r} ${i}`;s.push(t+r)}return s}function ln(e,t=new Set){let n=new Set(e.required??[]),s=[];for(let[a,r]of Object.entries(e.properties??{})){if(t.has(a))continue;let i=ic(a,r);s.push(` ${a}${n.has(a)?"*":""}: ${pn(r)}${rc(r)}`),i!==""&&s.push(...Se(i," "))}return s}function _r(e){if(Array.isArray(e))return`[${e.map(_r).join(", ")}]`;if(typeof e=="object"&&e!==null){let t=Object.entries(e);return t.length===0?"{}":`{ ${t.map(([n,s])=>`${JSON.stringify(n)}: ${_r(s)}`).join(", ")} }`}return JSON.stringify(e)}function kr(e,t="",n=110){let s=_r(e);if(typeof e!="object"||e===null||t.length+s.length<=n)return s;let a=`${t} `;return Array.isArray(e)?`[
652
+ ${e.map(r=>`${a}${kr(r,a,n)}`).join(`,
653
+ `)}
654
+ ${t}]`:`{
655
+ ${Object.entries(e).map(([r,i])=>`${a}${JSON.stringify(r)}: ${kr(i,a,n)}`).join(`,
656
+ `)}
657
+ ${t}}`}var rt=e=>["",e.toUpperCase(),"-".repeat(e.length)],Hb=[{title:"One package and the button that buys it",note:`The least a paywall is: a title, a package that says its own price, the purchase button, and the links App Review expects. Colors are the theme's tokens; "{{price}}" is filled by the app from its store.`,definition:{schema:2,name:"Simple",default_locale:"en",theme:{colors:{light:{background:"#FFFFFF",surface:"#F2F2F7",text:"#111114",muted:"#6B6B70",accent:"#5B5BD6",on_accent:"#FFFFFF"},dark:{background:"#000000",surface:"#1C1C1E",text:"#F5F5F7",muted:"#9A9AA0",accent:"#8E8EF5",on_accent:"#000000"}}},root:{type:"stack",spacing:16,align:"stretch",style:{padding:24,background:"background"},children:[{type:"text",text:"Go Pro",size:28,weight:700},{type:"spacer"},{type:"package",product:"pro",plan:"monthly",selected:!0,style:{padding:16,corners:14,background:"surface"},children:[{type:"text",text:"{{product_name}} \u2014 {{price}} / {{period}}",weight:600}]},{type:"purchase_button",style:{height:52,corners:14,background:"accent"},content:{type:"text",text:"Continue",color:"on_accent",weight:600}},{type:"stack",axis:"horizontal",spacing:24,distribution:"center",children:[{type:"button",action:{type:"restore"},visible:{platform:["ios","android"]},content:{type:"text",text:"Restore",size:13,color:"muted"}},{type:"button",action:{type:"terms"},content:{type:"text",text:"Terms",size:13,color:"muted"}},{type:"button",action:{type:"privacy"},content:{type:"text",text:"Privacy",size:13,color:"muted"}}]}]}}},{title:"Two packages, two languages, a trial said only to who can take it",note:`Texts are keys of the language table ({"t": key}); the package selected is drawn in its "states.selected" style; a text with "visible.intro_eligible" shows only while the customer can still take the selected package's trial, and one naming "{{savings}}" is not drawn for a package that saves nothing.`,definition:{schema:2,name:"Two plans",default_locale:"en",locales:{en:{title:"Read without limits",yearly:"Yearly",monthly:"Monthly",per:"{{price}} / {{period}}",save:"Save {{savings}}",cta:"Continue",cta_trial:"Try {{trial}}",then:"Then {{price}} a {{period}}. Cancel anytime."},es:{title:"Lee sin l\xEDmites",yearly:"Anual",monthly:"Mensual",per:"{{price}} / {{period}}",save:"Ahorra {{savings}}",cta:"Continuar",cta_trial:"Prueba {{trial}}",then:"Despu\xE9s, {{price}} al {{period}}. Cancela cuando quieras."}},theme:{colors:{light:{background:"#FBFAF7",surface:"#FFFFFF",text:"#111111",muted:"#77736B",accent:"#111111",on_accent:"#FBFAF7",stroke:"#1111111F"},dark:{background:"#0D0D0C",surface:"#1A1A18",text:"#F2F0EA",muted:"#8E8A82",accent:"#F2F0EA",on_accent:"#0D0D0C",stroke:"#F2F0EA24"}},typography:{title:{size:30,weight:700,text_style:"title"},row:{size:17,weight:600,text_style:"body"},caption:{size:13,weight:400,text_style:"footnote"}},radii:{card:14},spacing:{sm:8,md:16,lg:24}},root:{type:"stack",spacing:"md",align:"stretch",style:{padding:"lg",background:"background"},children:[{type:"text",text:{t:"title"},size:"title",align:"start"},{type:"spacer"},...["yearly","monthly"].map(e=>({type:"package",product:"pro",plan:e,selected:e==="yearly",style:{padding:"md",corners:"card",background:"surface",border:{width:1,color:"stroke"}},states:{selected:{border:{width:2,color:"accent"}}},children:[{type:"stack",axis:"horizontal",align:"center",children:[{type:"text",text:{t:e},size:"row",align:"start"},{type:"spacer"},{type:"stack",align:"end",children:[{type:"text",text:{t:"per"},size:"row"},{type:"text",text:{t:"save"},size:"caption",color:"muted"}]}]}]}))]},footer:{type:"stack",spacing:"sm",align:"stretch",style:{padding:"lg",background:"background"},children:[{type:"purchase_button",style:{height:52,corners:"card",background:"accent"},visible:{intro_eligible:!0},content:{type:"text",text:{t:"cta_trial"},size:"row",color:"on_accent"}},{type:"purchase_button",style:{height:52,corners:"card",background:"accent"},visible:{intro_eligible:!1},content:{type:"text",text:{t:"cta"},size:"row",color:"on_accent"}},{type:"text",text:{t:"then"},size:"caption",color:"muted"}]}}},{title:"A photograph, a sheet a button opens, and what only one platform shows",note:`An image names a project asset (palbase purchases:paywalls:assets:upload answers its asset:<id> \u2014 the id below is a placeholder); a button's action opens a sheet by its id; "visible.platform" keeps the offer code button to iOS.`,definition:{schema:2,name:"With a sheet",default_locale:"en",theme:{colors:{light:{background:"#FFFFFF",surface:"#F2F2F7",text:"#111114",muted:"#6B6B70",accent:"#FF5A36",on_accent:"#FFFFFF"},dark:{background:"#0B0B0F",surface:"#1C1C1E",text:"#F5F5F7",muted:"#9A9AA0",accent:"#FF7A5C",on_accent:"#0B0B0F"}}},header:{type:"image",src:"asset:00000000-0000-4000-8000-000000000000",alt:"A traveller at sunrise",fit:"fill",style:{height:220}},root:{type:"stack",spacing:16,align:"stretch",style:{padding:24,background:"background"},children:[{type:"text",text:"Your next trip, planned in minutes",size:26,weight:700,align:"start"},{type:"package",product:"pro",plan:"yearly",selected:!0,style:{padding:16,corners:14,background:"surface"},children:[{type:"text",text:"{{price}} a {{period}}",weight:600}]},{type:"button",action:{type:"sheet",sheet:"details"},content:{type:"text",text:"What is included?",color:"accent"}},{type:"button",action:{type:"offer_code"},visible:{platform:["ios"]},content:{type:"text",text:"Redeem a code",size:13,color:"muted"}}]},footer:{type:"stack",align:"stretch",style:{padding:24,background:"background"},children:[{type:"purchase_button",style:{height:52,corners:26,background:"accent"},content:{type:"text",text:"Continue",color:"on_accent",weight:600}}]},sheets:{details:{title:"What is included",root:{type:"stack",spacing:12,align:"stretch",style:{padding:24},children:[{type:"features",items:[{icon:"globe",text:"Offline maps for 190 countries"},{icon:"sparkles",text:"Itineraries that adapt"},{icon:"bell",text:"Price alerts"}]},{type:"button",action:{type:"close"},content:{type:"text",text:"Close",color:"accent"}}]}}}}}];function oc(e){let t=e.$defs,n=t[ec],s=t[tc].oneOf.map(l=>Is(l)),a=s.map(l=>new Set(Object.keys(t[l].properties))).reduce((l,p)=>new Set([...l].filter(d=>p.has(d)))),r=l=>String(t[l].properties.type.enum[0]),i=[];i.push(`PAYWALL DEFINITIONS \u2014 writing one by hand (schema ${n.properties.schema?.description?.match(/: (\d+)\./)?.[1]??"2"})`),i.push("",...Se(String(e.description??""),"")),i.push(...rt("The flow")),i.push(" palbase purchases:paywalls the paywalls, their draft revisions, where each is shown"," palbase purchases:paywalls:templates the module's designs to start from"," palbase purchases:paywalls:new <name> --template <key> a template to a file, selling the catalog's packages"," palbase purchases:paywalls:get <name> writes palbase/paywalls/<name>.json and prints its revision"," (edit the file: its first line names palbase/paywalls/paywall.schema.json, which an editor checks it against)"," palbase purchases:paywalls:validate --file <file> every problem \u2014 path: code \u2014 sentence; exit 1 while one is left"," palbase purchases:paywalls:preview --file <file> a PNG to look at; --all: every platform, appearance and text size"," palbase purchases:paywalls:check --file <file> what is wrong on every screen: overflow, cut text, contrast \u2026"," palbase purchases:paywalls:put <name> --file <file> --revision <n> [--publish]"," palbase purchases:paywalls:publish <name> the draft becomes what the apps draw",' palbase purchases:placements:set <placement> --paywall <name> an app asks for a placement: pb.purchases.paywall("<placement>")',""," A new paywall: start from a template (new), or write the file (an example below is one); put it with --revision 0."," A problem's path is where it is in the file: root.children[2].content.text. Fix it there, validate again."," Look before saving: preview draws the file, not the saved draft. --package, --state tab=<id>,switch=on|off,sheet=<id>"," and --locale draw the states a customer puts it in."),i.push(...rt("The definition")," (* is required; nothing the schema does not name is taken)"),i.push(...ln(n,new Set(["$schema"]))),i.push("",...Se("header and footer stay fixed; root scrolls between them. Each is one component \u2014 a stack, to hold more than one."," ")),i.push(...rt("The theme")),i.push(...Se(String(t.PaywallTheme?.description??"")," "),...ln(t.PaywallTheme)),i.push(" colors:",...Se(String(t.PaywallColors?.description??"")," "),...Se(String(t.PaywallPalette?.description??"")," ")),i.push(" a typography token:",...ln(t.PaywallTypography).map(l=>` ${l}`)),i.push(...Se("Wherever a field takes a color, points or a size, it takes a token of the theme (by its name) or the value itself."," ")),i.push(...rt("Texts and variables")),i.push(...Se(String(t.PaywallString?.description??"")," "),...Se(String(t.PaywallLocales?.description??"")," "),...Se(String(t.PaywallTexts?.description??"")," ")),i.push(...rt("Every component takes"));for(let l of[...a].filter(p=>p!=="type").sort()){let p=t[s[0]].properties[l],d=Is(p),c=ic(l,p)||(d?String(t[d]?.description??""):"");i.push(` ${l}: ${pn(p)}`),c!==""&&i.push(...Se(c," ")),d&&t[d]?.properties&&i.push(...ln(t[d]).map(g=>` ${g}`))}i.push(...rt("Components"),' ("type" names one; its own fields follow, beside what every component takes)');for(let l of s)i.push("",` ${r(l)}`),i.push(...Se(String(t[l].description??"")," ")),i.push(...ln(t[l],a).map(p=>` ${p}`));let o=new Set([ec,tc,...s,"PaywallTheme","PaywallColors","PaywallPalette","PaywallTypography","PaywallTypeScale","PaywallScale","PaywallLocales","PaywallTexts","PaywallString","PaywallLiteral",...Object.keys(sc)]);for(let l of a){let p=Is(t[s[0]].properties[l]);p&&o.add(p)}i.push(...rt("Parts")," (what the fields above take, by the name they are given there)");for(let[l,p]of Object.entries(t))o.has(l)||(i.push("",` ${ac(l)}${p.properties?"":`: ${pn(p)}${rc(p)}`}`),typeof p.description=="string"&&i.push(...Se(p.description," ")),p.properties&&i.push(...ln(p).map(d=>` ${d}`)));return i.push(...rt("Assets")),i.push(...Se(`An image, a video, a font or an HTML block is a file of the project: palbase purchases:paywalls:assets:upload <file> uploads and registers it and answers the asset:<id> a field takes (an image's or a video's "src", a typography's or a text's "font", a custom_html's "src"). palbase purchases:paywalls:assets lists them. An image or a video may name an https address instead: every app downloads it from there.`," ")),i.push(...rt("Examples")),Hb.forEach((l,p)=>{i.push("",` ${p+1}. ${l.title}`,...Se(l.note," "),""),i.push(...kr(l.definition).split(`
658
+ `).map(d=>` ${d}`))}),`${i.join(`
659
+ `)}
660
+ `}var Ns={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Palbase paywall definition",description:"A paywall: a design system (the theme's colors, type scale, radii and spacing) and a free layout of components that name it \u2014 a fixed header and footer around the part that scrolls, and sheets it opens. Every component takes a style, a condition to be shown, a style for when it is selected, and an entrance animation. Prices are never in it: an app fills `{{price}}` and the other variables from its store. Its assets (asset:<id>) are the project's, given as https addresses in the app's answer and listed for the app to download before it shows the paywall. The server refuses what it does not know and holds a definition to bounds: 128 KiB of JSON, 400 components, 16 deep, 500 characters a text, 40 languages, 8 stops a gradient, 2 seconds an animation, 10 carousel pages, 5 tabs, 5 sheets.",$ref:"#/$defs/PaywallDefinition",$defs:{PaywallAction:{additionalProperties:!1,description:"What a button does.",properties:{package:{description:"Package.",pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"},sheet:{description:"Sheet.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},type:{description:"Type.",enum:["close","restore","open_url","terms","privacy","offer_code","sheet","select_package"],type:"string"},url:{description:"Url.",maxLength:2048,pattern:"^https://.+$",type:"string"}},required:["type"],type:"object"},PaywallAnimation:{additionalProperties:!1,description:"How the component appears; a renderer skips it when the system asks to reduce motion.",properties:{delay_ms:{default:0,description:"Delay ms.",maximum:2e3,minimum:0,type:"integer"},duration_ms:{default:300,description:"Duration ms.",maximum:2e3,minimum:50,type:"integer"},easing:{default:"ease_out",description:"Easing.",enum:["ease_out","ease_in_out","spring"],type:"string"},type:{description:"Type.",enum:["fade","slide_up","slide_down","slide_leading","slide_trailing","scale"],type:"string"}},required:["type"],type:"object"},PaywallBorder:{additionalProperties:!1,description:"A border.",properties:{color:{description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},width:{description:"Width.",maximum:16,minimum:.5,type:"number"}},required:["color","width"],type:"object"},PaywallButton:{additionalProperties:!1,description:"A button that does an action: closes the paywall, restores purchases, opens an address, the terms or the privacy policy, the store's offer code sheet, one of the paywall's sheets, or selects a package.",properties:{action:{$ref:"#/$defs/PaywallAction"},animation:{$ref:"#/$defs/PaywallAnimation"},content:{$ref:"#/$defs/PaywallComponent"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["button"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["action","content","type"],type:"object"},PaywallCarousel:{additionalProperties:!1,description:"Pages the customer swipes through, the next peeking in, advancing on their own, with an indicator.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},auto_advance_ms:{description:"Auto advance ms.",maximum:3e4,minimum:1e3,type:"integer"},height:{description:"Height.",oneOf:[{$ref:"#/$defs/PaywallSizeKeyword"},{$ref:"#/$defs/PaywallLength"}]},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},indicator:{$ref:"#/$defs/PaywallIndicator"},loop:{default:!1,description:"Loop.",type:"boolean"},pages:{description:"Pages.",items:{$ref:"#/$defs/PaywallComponent"},maxItems:10,minItems:1,type:"array"},peek:{default:0,description:"Peek.",maximum:64,minimum:0,type:"integer"},spacing:{default:8,description:"Spacing.",maximum:64,minimum:0,type:"integer"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["carousel"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["pages","type"],type:"object"},PaywallColorValue:{description:"A color: a token of the theme's colors (the light or the dark value, as the app appears), or #RRGGBB / #RRGGBBAA.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},PaywallColors:{additionalProperties:!1,description:"The theme's colors by name, for each appearance; both name the same colors, at least background, surface, text, muted, accent and on_accent.",properties:{dark:{$ref:"#/$defs/PaywallPalette"},light:{$ref:"#/$defs/PaywallPalette"}},required:["dark","light"],type:"object"},PaywallComponent:{description:"A component, by its type. A newer schema adds components; an app never draws a definition with one it does not know.",oneOf:[{$ref:"#/$defs/PaywallButton"},{$ref:"#/$defs/PaywallCarousel"},{$ref:"#/$defs/PaywallCountdown"},{$ref:"#/$defs/PaywallCustomHtml"},{$ref:"#/$defs/PaywallDivider"},{$ref:"#/$defs/PaywallFeatures"},{$ref:"#/$defs/PaywallIcon"},{$ref:"#/$defs/PaywallImage"},{$ref:"#/$defs/PaywallPackage"},{$ref:"#/$defs/PaywallPurchaseButton"},{$ref:"#/$defs/PaywallSpacer"},{$ref:"#/$defs/PaywallStack"},{$ref:"#/$defs/PaywallSwitch"},{$ref:"#/$defs/PaywallTabs"},{$ref:"#/$defs/PaywallText"},{$ref:"#/$defs/PaywallTimeline"},{$ref:"#/$defs/PaywallVideo"}]},PaywallCondition:{additionalProperties:!1,description:"When the component is shown: every field named holds at once.",properties:{appearance:{description:"Appearance.",enum:["light","dark"],type:"string"},intro_eligible:{description:"Intro eligible.",type:"boolean"},locale:{description:"Locale.",items:{pattern:"^[a-z]{2,3}(-[A-Z][A-Za-z]{1,3})?$",type:"string"},maxItems:40,minItems:1,type:"array",uniqueItems:!0},platform:{description:"Platform.",items:{enum:["ios","android","web"],type:"string"},maxItems:3,minItems:1,type:"array",uniqueItems:!0},selected_package:{description:"Selected package.",items:{pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"},maxItems:6,minItems:1,type:"array"},tab:{description:"Tab.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"}},type:"object"},PaywallCorners:{additionalProperties:!1,description:"Each corner its own, in points or a token of the theme's radii.",properties:{bottom_leading:{default:0,description:"Bottom leading.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},bottom_trailing:{default:0,description:"Bottom trailing.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},top_leading:{default:0,description:"Top leading.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},top_trailing:{default:0,description:"Top trailing.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]}},type:"object"},PaywallCountdown:{additionalProperties:!1,description:"A countdown to an instant, or for a time from the moment the paywall opened: one content while it counts \u2014 its texts name {{countdown}}, {{days}}, {{hours}}, {{minutes}}, {{seconds}} \u2014, another once it ended.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},counting:{$ref:"#/$defs/PaywallComponent"},ended:{$ref:"#/$defs/PaywallComponent"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},target:{$ref:"#/$defs/PaywallTarget"},type:{description:"The component.",enum:["countdown"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["counting","target","type"],type:"object"},PaywallCustomHtml:{additionalProperties:!1,description:"A decorative HTML block from an archive the project uploaded (an animation, a Lottie or Rive scene): drawn in a sandbox of an origin of its own: it reaches neither the page around it, nor the app, nor the purchase, and loads only what came in its archive (a content security policy) \u2014 it may still navigate its own frame away and open a WebRTC connection (a content security policy does not cover WebRTC). It is told the selected package, the language and the appearance, and is hidden from screen readers. It cannot buy anything.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},height:{description:"Height.",oneOf:[{$ref:"#/$defs/PaywallSizeKeyword"},{$ref:"#/$defs/PaywallLength"}]},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},src:{description:"Src.",maxLength:2048,pattern:"^asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["custom_html"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["height","src","type"],type:"object"},PaywallDefinition:{additionalProperties:!1,description:"A paywall: a design system (the theme's colors, type scale, radii and spacing) and a free layout of components that name it \u2014 a fixed header and footer around the part that scrolls, and sheets it opens. Every component takes a style, a condition to be shown, a style for when it is selected, and an entrance animation. Prices are never in it: an app fills `{{price}}` and the other variables from its store. Its assets (asset:<id>) are the project's, given as https addresses in the app's answer and listed for the app to download before it shows the paywall. The server refuses what it does not know and holds a definition to bounds: 128 KiB of JSON, 400 components, 16 deep, 500 characters a text, 40 languages, 8 stops a gradient, 2 seconds an animation, 10 carousel pages, 5 tabs, 5 sheets.",properties:{$schema:{type:"string",description:"The JSON Schema an editor checks this file against. The CLI takes it out before the definition is sent."},default_locale:{description:"The language a text falls back to: a tag such as en or pt-BR.",pattern:"^[a-z]{2,3}(-[A-Z][A-Za-z]{1,3})?$",type:"string"},footer:{$ref:"#/$defs/PaywallComponent"},header:{$ref:"#/$defs/PaywallComponent"},locales:{$ref:"#/$defs/PaywallLocales"},name:{description:"The paywall's name, for the people who edit it (1 to 100 characters).",maxLength:100,minLength:1,type:"string"},root:{$ref:"#/$defs/PaywallComponent"},schema:{description:"The schema the definition is written in: 2. An app says the newest schema it draws (X-Palbase-Paywall-Schema) and is given the newest version written in it or an older one; an app that draws no schema this server takes is told to update (426).",maximum:2,minimum:2,type:"integer"},sheets:{$ref:"#/$defs/PaywallSheets"},theme:{$ref:"#/$defs/PaywallTheme"}},required:["schema","name","default_locale","theme","root"],type:"object"},PaywallDivider:{additionalProperties:!1,description:"A thin line across its stack.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},color:{default:"muted",description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},thickness:{default:1,description:"Thickness.",maximum:8,minimum:.5,type:"number"},type:{description:"The component.",enum:["divider"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["type"],type:"object"},PaywallEdges:{additionalProperties:!1,description:"Each edge its own, in points or a token of the theme's spacing.",properties:{bottom:{default:0,description:"Bottom.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},leading:{default:0,description:"Leading.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},top:{default:0,description:"Top.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},trailing:{default:0,description:"Trailing.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]}},type:"object"},PaywallFeature:{additionalProperties:!1,description:"A feature.",properties:{icon:{$ref:"#/$defs/PaywallIconName"},text:{$ref:"#/$defs/PaywallString"}},required:["icon","text"],type:"object"},PaywallFeatures:{additionalProperties:!1,description:"What the purchase gives, each with an icon.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},color:{default:"text",description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},icon_color:{default:"accent",description:"Icon color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},items:{description:"Items.",items:{$ref:"#/$defs/PaywallFeature"},maxItems:12,minItems:1,type:"array"},spacing:{default:12,description:"Spacing.",maximum:64,minimum:0,type:"integer"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["features"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["items","type"],type:"object"},PaywallFontSize:{description:"A size in points, scaled with the platform's body style.",maximum:120,minimum:6,type:"number"},PaywallIcon:{additionalProperties:!1,description:"An icon every renderer draws.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},color:{default:"accent",description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},name:{$ref:"#/$defs/PaywallIconName"},size:{default:24,description:"Size.",maximum:128,minimum:8,type:"integer"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["icon"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["name","type"],type:"object"},PaywallIconName:{description:"An icon every renderer draws (SF Symbols on iOS).",enum:["ad_free","arrow_right","bell","bolt","book","calendar","chart","chat","check","check_circle","clock","close","cloud","crown","download","gift","globe","heart","infinity","lock","lock_open","music","people","person","photo","play","quote","shield","sparkles","star","trophy","video"],type:"string"},PaywallImage:{additionalProperties:!1,description:"An image the app downloads before it shows the paywall: an https address, or an image the project uploaded.",properties:{alt:{$ref:"#/$defs/PaywallString"},animation:{$ref:"#/$defs/PaywallAnimation"},aspect:{description:"Aspect.",maximum:10,minimum:.1,type:"number"},decorative:{default:!1,description:"Decorative.",type:"boolean"},fit:{default:"fit",description:"Fit.",enum:["fit","fill"],type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},mask:{$ref:"#/$defs/PaywallMask"},overlay:{description:"Overlay.",oneOf:[{$ref:"#/$defs/PaywallLinear"},{$ref:"#/$defs/PaywallRadial"}]},src:{description:"Src.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["image"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["src","type"],type:"object"},PaywallImageFill:{additionalProperties:!1,description:"An image behind the component.",properties:{fit:{default:"fill",description:"Fit.",enum:["fit","fill"],type:"string"},src:{description:"Src.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},type:{description:"Type.",enum:["image"],type:"string"}},required:["src","type"],type:"object"},PaywallIndicator:{additionalProperties:!1,description:"A carousel's page indicator.",properties:{active_color:{default:"accent",description:"Active color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},color:{default:"muted",description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},size:{default:8,description:"Size.",maximum:16,minimum:2,type:"integer"},spacing:{default:6,description:"Spacing.",maximum:32,minimum:0,type:"integer"},style:{default:"dots",description:"Its style.",enum:["dots","bars","none"],type:"string"}},type:"object"},PaywallLength:{description:"A length in points.",maximum:4e3,minimum:0,type:"number"},PaywallLinear:{additionalProperties:!1,description:"A linear gradient: its angle (0 up, clockwise, 180 down) and 2 to 8 stops.",properties:{angle:{default:180,description:"Angle.",maximum:360,minimum:0,type:"number"},stops:{description:"Stops.",items:{$ref:"#/$defs/PaywallStop"},maxItems:8,minItems:2,type:"array"},type:{description:"Type.",enum:["linear"],type:"string"}},required:["stops","type"],type:"object"},PaywallLiteral:{description:"The text itself, in every language.",maxLength:500,type:"string"},PaywallLocales:{additionalProperties:{$ref:"#/$defs/PaywallTexts"},description:"The texts in each language (at most 40), by language tag: a component's `{\"t\": key}` is the app's language's, or the default locale's when it lacks the key.",type:"object"},PaywallMask:{additionalProperties:!1,description:"The image's shape: a rectangle with corners, a circle, or a rectangle whose bottom edge curves in (concave) or out (convex).",properties:{depth:{default:16,description:"Depth.",maximum:64,minimum:0,type:"number"},radius:{default:0,description:"Radius.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},shape:{description:"Shape.",enum:["rect","circle","concave","convex"],type:"string"}},required:["shape"],type:"object"},PaywallPackage:{additionalProperties:!1,description:"A package on sale, laid out as the project likes: tapping it selects it; its content's styles for when it is selected apply while it is.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},children:{description:"The components.",items:{$ref:"#/$defs/PaywallComponent"},maxItems:50,type:"array"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},plan:{description:"Plan.",pattern:"^[a-z][a-z0-9_]{0,39}$",type:"string"},product:{description:"Product.",pattern:"^[a-z][a-z0-9_]{0,39}$",type:"string"},selected:{default:!1,description:"Selected when the paywall opens (at most one).",type:"boolean"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["package"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["plan","product","type"],type:"object"},PaywallPalette:{additionalProperties:{pattern:"^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$",type:"string"},description:"Colors by token (lowercase letters, digits and _, at most 32), each #RRGGBB or #RRGGBBAA.",maxProperties:40,type:"object"},PaywallPoints:{description:"Points.",maximum:200,minimum:0,type:"number"},PaywallPurchaseButton:{additionalProperties:!1,description:"The button that buys the selected package \u2014 or its own package \u2014: the store's sheet (StoreKit) or checkout (Stripe) opens.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},content:{$ref:"#/$defs/PaywallComponent"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},package:{description:"Package.",pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["purchase_button"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["content","type"],type:"object"},PaywallRadial:{additionalProperties:!1,description:"A radial gradient: its center and radius as shares of the component, and 2 to 8 stops.",properties:{center_x:{default:.5,description:"Center x.",maximum:1,minimum:0,type:"number"},center_y:{default:.5,description:"Center y.",maximum:1,minimum:0,type:"number"},radius:{default:.5,description:"Radius.",maximum:2,minimum:.01,type:"number"},stops:{description:"Stops.",items:{$ref:"#/$defs/PaywallStop"},maxItems:8,minItems:2,type:"array"},type:{description:"Type.",enum:["radial"],type:"string"}},required:["stops","type"],type:"object"},PaywallScale:{additionalProperties:{maximum:200,minimum:0,type:"number"},description:"Points by token (lowercase letters, digits and _, at most 32).",maxProperties:16,type:"object"},PaywallShadow:{additionalProperties:!1,description:"A shadow.",properties:{color:{description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},radius:{default:0,description:"Radius.",maximum:64,minimum:0,type:"number"},x:{default:0,description:"X.",maximum:64,minimum:-64,type:"number"},y:{default:0,description:"Y.",maximum:64,minimum:-64,type:"number"}},required:["color"],type:"object"},PaywallSheet:{additionalProperties:!1,description:"A second page a button opens over the paywall.",properties:{root:{$ref:"#/$defs/PaywallComponent"},title:{$ref:"#/$defs/PaywallString"}},required:["root"],type:"object"},PaywallSheets:{additionalProperties:{$ref:"#/$defs/PaywallSheet"},description:"The sheets by id (lowercase letters, digits, _ and -, starting with a letter).",maxProperties:5,type:"object"},PaywallSizeKeyword:{description:`fit: the content's size; fill: all the parent gives; a share of the parent ("50%").`,pattern:"^(fit|fill|([1-9][0-9]?|100)%)$",type:"string"},PaywallSpacer:{additionalProperties:!1,description:"Space: a fixed size, or all that is left along its stack when absent.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},size:{description:"Size.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["spacer"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["type"],type:"object"},PaywallSpan:{additionalProperties:!1,description:"A part of a text: bold, italic, underlined, struck through, colored, a link.",properties:{bold:{default:!1,description:"Bold.",type:"boolean"},color:{description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},italic:{default:!1,description:"Italic.",type:"boolean"},link:{description:"Link.",maxLength:2048,pattern:"^(terms|privacy|https://.+)$",type:"string"},strikethrough:{default:!1,description:"Strikethrough.",type:"boolean"},text:{$ref:"#/$defs/PaywallString"},underline:{default:!1,description:"Underline.",type:"boolean"}},required:["text"],type:"object"},PaywallStack:{additionalProperties:!1,description:"Components laid out one after another \u2014 down, across \u2014 or over each other (z). The root, the header and the footer are each one; the root scrolls between the fixed header and footer.",properties:{align:{default:"center",description:"Align.",enum:["start","center","end","stretch"],type:"string"},animation:{$ref:"#/$defs/PaywallAnimation"},axis:{default:"vertical",description:"Axis.",enum:["vertical","horizontal","z"],type:"string"},children:{description:"The components.",items:{$ref:"#/$defs/PaywallComponent"},maxItems:50,type:"array"},distribution:{default:"start",description:"Distribution.",enum:["start","center","end","space_between","space_around","space_evenly"],type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},scroll:{default:!1,description:"Scroll.",type:"boolean"},spacing:{default:0,description:"Spacing.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"}]},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["stack"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["type"],type:"object"},PaywallStates:{additionalProperties:!1,description:"The component's style while what it is in is selected: the package it is in (or is), the tab whose label it is.",properties:{selected:{$ref:"#/$defs/PaywallStyle"}},type:"object"},PaywallStop:{additionalProperties:!1,description:"A gradient's color at a point along it (0 to 1).",properties:{at:{description:"At.",maximum:1,minimum:0,type:"number"},color:{description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"}},required:["at","color"],type:"object"},PaywallString:{description:'A text: the string itself, or `{"t": key}` of the language table. Either may hold variables the app fills from its store \u2014 `{{price}}`, `{{price_per_month}}`, `{{period}}`, `{{trial}}`, `{{savings}}`, `{{product_name}}` \u2014 of the package an item is, or of the selected package anywhere else. A text that names a variable its package has no value for (no trial, no savings) is not drawn.',oneOf:[{$ref:"#/$defs/PaywallLiteral"},{$ref:"#/$defs/PaywallTextKey"}]},PaywallStyle:{additionalProperties:!1,description:"What every component takes: padding and margin by edge, a size, a fill, a border, corners each their own, a shadow, an opacity. Points and colors name the theme's tokens or are given as they are.",properties:{background:{description:"Background.",oneOf:[{$ref:"#/$defs/PaywallColorValue"},{$ref:"#/$defs/PaywallLinear"},{$ref:"#/$defs/PaywallRadial"},{$ref:"#/$defs/PaywallImageFill"},{$ref:"#/$defs/PaywallVideoFill"}]},border:{$ref:"#/$defs/PaywallBorder"},corners:{description:"Corners.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"},{$ref:"#/$defs/PaywallCorners"}]},height:{description:"Height.",oneOf:[{$ref:"#/$defs/PaywallSizeKeyword"},{$ref:"#/$defs/PaywallLength"}]},margin:{description:"Margin.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"},{$ref:"#/$defs/PaywallEdges"}]},max_width:{description:"Max width.",maximum:4e3,minimum:1,type:"integer"},opacity:{default:1,description:"Opacity.",maximum:1,minimum:0,type:"number"},padding:{description:"Padding.",oneOf:[{$ref:"#/$defs/PaywallPoints"},{$ref:"#/$defs/PaywallToken"},{$ref:"#/$defs/PaywallEdges"}]},shadow:{$ref:"#/$defs/PaywallShadow"},width:{description:"Width.",oneOf:[{$ref:"#/$defs/PaywallSizeKeyword"},{$ref:"#/$defs/PaywallLength"}]}},type:"object"},PaywallSwitch:{additionalProperties:!1,description:"A switch between two contents \u2014 a yearly and a monthly offer \u2014, each selecting its own package when it is shown.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},default:{default:!1,description:"Default.",type:"boolean"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},label:{$ref:"#/$defs/PaywallString"},off:{$ref:"#/$defs/PaywallComponent"},off_package:{description:"Off package.",pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"},on:{$ref:"#/$defs/PaywallComponent"},on_package:{description:"On package.",pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},tint:{default:"accent",description:"Tint.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},type:{description:"The component.",enum:["switch"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["off","on","type"],type:"object"},PaywallTab:{additionalProperties:!1,description:"A tab: its label, its content, and the package it selects when it is shown.",properties:{content:{$ref:"#/$defs/PaywallComponent"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},label:{$ref:"#/$defs/PaywallComponent"},package:{description:"Package.",pattern:"^([a-z][a-z0-9_]{0,39})\\.([a-z][a-z0-9_]{0,39})$",type:"string"}},required:["content","id","label"],type:"object"},PaywallTabs:{additionalProperties:!1,description:"Tabs, each its own content and default package; the labels, in a bar, are components whose style when selected is the shown tab's.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},bar:{$ref:"#/$defs/PaywallStyle"},default:{description:"Default.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},tabs:{description:"Tabs.",items:{$ref:"#/$defs/PaywallTab"},maxItems:5,minItems:2,type:"array"},type:{description:"The component.",enum:["tabs"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["tabs","type"],type:"object"},PaywallTarget:{additionalProperties:!1,description:"What a countdown counts to: an instant (at), or a time from the moment the paywall opened (after_open_seconds) \u2014 one of them.",properties:{after_open_seconds:{description:"After open seconds.",maximum:604800,minimum:1,type:"integer"},at:{description:"At.",format:"date-time",type:"string"}},type:"object"},PaywallText:{additionalProperties:!1,description:"A text: one string, or spans \u2014 bold, italic, colored parts and links \u2014, in a type style of the theme or its own.",properties:{align:{default:"center",description:"Align.",enum:["start","center","end"],type:"string"},animation:{$ref:"#/$defs/PaywallAnimation"},color:{default:"text",description:"Color.",oneOf:[{$ref:"#/$defs/PaywallColorValue"},{$ref:"#/$defs/PaywallLinear"},{$ref:"#/$defs/PaywallRadial"}]},font:{description:"Font.",pattern:"^(system|rounded|serif|mono|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},letter_spacing:{description:"Letter spacing.",maximum:20,minimum:-5,type:"number"},line_height:{description:"Line height.",maximum:3,minimum:.5,type:"number"},max_lines:{description:"Max lines.",maximum:20,minimum:1,type:"integer"},size:{description:"Size.",oneOf:[{$ref:"#/$defs/PaywallFontSize"},{$ref:"#/$defs/PaywallToken"}]},spans:{description:"Spans.",items:{$ref:"#/$defs/PaywallSpan"},maxItems:20,minItems:1,type:"array"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},text:{$ref:"#/$defs/PaywallString"},type:{description:"The component.",enum:["text"],type:"string"},uppercase:{default:!1,description:"Uppercase.",type:"boolean"},visible:{$ref:"#/$defs/PaywallCondition"},weight:{description:"Weight.",enum:[100,200,300,400,500,600,700,800,900],type:"integer"}},required:["type"],type:"object"},PaywallTextKey:{additionalProperties:!1,description:"A text of the language table.",properties:{t:{description:"The text's key.",type:"string"}},required:["t"],type:"object"},PaywallTexts:{additionalProperties:{maxLength:500,type:"string"},description:"One language's texts, by key (lowercase letters, digits, `_` `.` `-`, at most 64): each at most 500 characters, with variables.",type:"object"},PaywallTheme:{additionalProperties:!1,description:"The paywall's design system: colors for light and dark, a type scale, radii and spacing; the components name them.",properties:{colors:{$ref:"#/$defs/PaywallColors"},radii:{$ref:"#/$defs/PaywallScale"},spacing:{$ref:"#/$defs/PaywallScale"},typography:{$ref:"#/$defs/PaywallTypeScale"}},required:["colors"],type:"object"},PaywallTimeline:{additionalProperties:!1,description:"Steps in time \u2014 a trial's today, its reminder, its first charge \u2014, each with an icon, joined by a line.",properties:{animation:{$ref:"#/$defs/PaywallAnimation"},color:{default:"text",description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},icon_color:{default:"on_accent",description:"Icon color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},items:{description:"Items.",items:{$ref:"#/$defs/PaywallTimelineItem"},maxItems:8,minItems:2,type:"array"},line_color:{default:"accent",description:"Line color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},line_width:{default:2,description:"Line width.",maximum:8,minimum:1,type:"integer"},spacing:{default:16,description:"Spacing.",maximum:64,minimum:0,type:"integer"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["timeline"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["items","type"],type:"object"},PaywallTimelineItem:{additionalProperties:!1,description:"A step of a timeline.",properties:{color:{description:"Color.",pattern:"^(#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})|[a-z][a-z0-9_]{0,31})$",type:"string"},icon:{$ref:"#/$defs/PaywallIconName"},text:{$ref:"#/$defs/PaywallString"},title:{$ref:"#/$defs/PaywallString"}},required:["icon","title"],type:"object"},PaywallToken:{description:"A token of the theme's scale.",pattern:"^[a-z][a-z0-9_]{0,31}$",type:"string"},PaywallTypeScale:{additionalProperties:{$ref:"#/$defs/PaywallTypography"},description:"Type styles by token (lowercase letters, digits and _, at most 32).",maxProperties:16,type:"object"},PaywallTypography:{additionalProperties:!1,description:"A type style of the theme: what a text whose size names it is drawn in. It scales with its text style (Dynamic Type on iOS, the root font size on the web).",properties:{font:{default:"system",description:"Font.",pattern:"^(system|rounded|serif|mono|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},letter_spacing:{default:0,description:"Letter spacing.",maximum:20,minimum:-5,type:"number"},line_height:{description:"Line height.",maximum:3,minimum:.5,type:"number"},size:{description:"Size.",maximum:120,minimum:6,type:"number"},text_style:{default:"body",description:"Text style.",enum:["large_title","title","title2","title3","headline","body","callout","subheadline","footnote","caption"],type:"string"},weight:{default:400,description:"Weight.",enum:[100,200,300,400,500,600,700,800,900],type:"integer"}},required:["size"],type:"object"},PaywallVideo:{additionalProperties:!1,description:"A video the app downloads before it shows the paywall; muted and looping unless it says otherwise.",properties:{alt:{$ref:"#/$defs/PaywallString"},animation:{$ref:"#/$defs/PaywallAnimation"},aspect:{description:"Aspect.",maximum:10,minimum:.1,type:"number"},autoplay:{default:!0,description:"Autoplay.",type:"boolean"},controls:{default:!1,description:"Controls.",type:"boolean"},fit:{default:"fill",description:"Fit.",enum:["fit","fill"],type:"string"},id:{description:"Names the component, unique in the paywall: an app's code can draw its own part in its place.",pattern:"^[a-z][a-z0-9_-]{0,39}$",type:"string"},loop:{default:!0,description:"Loop.",type:"boolean"},mute:{default:!0,description:"Mute.",type:"boolean"},poster:{description:"Poster.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},src:{description:"Src.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},states:{$ref:"#/$defs/PaywallStates"},style:{$ref:"#/$defs/PaywallStyle"},type:{description:"The component.",enum:["video"],type:"string"},visible:{$ref:"#/$defs/PaywallCondition"}},required:["src","type"],type:"object"},PaywallVideoFill:{additionalProperties:!1,description:"A muted video behind the component.",properties:{loop:{default:!0,description:"Loop.",type:"boolean"},poster:{description:"Poster.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},src:{description:"Src.",maxLength:2048,pattern:"^(https://.+|asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$",type:"string"},type:{description:"Type.",enum:["video"],type:"string"}},required:["src","type"],type:"object"}}};var me=`${U}/paywalls`,dn=`${me}/paywall.schema.json`,vt="paywall-assets",$r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Us=e=>`"${e}"`;function cn(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw m(`${n} is required`);return s}async function it(e,t){return(await u(e,t,"GET","/purchases/paywalls")).paywalls}function zs(e,t){if($r.test(t))return e.find(a=>a.id===t.toLowerCase());let n=e.filter(a=>a.name===t),s=n.length>0?n:e.filter(a=>a.name.toLocaleLowerCase()===t.toLocaleLowerCase());if(s.length>1)throw new h("ambiguous_paywall",`${s.length} paywalls are named ${JSON.stringify(t)}`,{hint:`name one by its id: ${s.map(a=>a.id).join(", ")}`});return s[0]}async function jt(e,t,n){let s=await it(e,t),a=zs(s,n);if(!a)throw new h("paywall_not_found",`no paywall ${JSON.stringify(n)}`,{hint:s.length>0?`the project's: ${s.map(r=>r.name).join(", ")}`:"the project has none yet: palbase purchases:paywalls:put <name> --file <file> --revision 0 makes one"});return a}function Nn(e){let t=e.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60);return t===""?"paywall":t}function Je(e,t,n,s){let a=nw(e.io.cwd,cc(e.io.cwd,n)).split(sw).join("/");if(!a.startsWith(`${U}/`)||a.split("/").includes(".."))throw m(`--${t} ${n}: the file goes under ${U}/, as the CLI writes nothing else in the checkout`,`--${t} ${me}/<name>${s}`);if(!a.endsWith(s))throw m(`--${t} ${n}: the file's name ends in ${s}`);return a}async function qs(e,t,n){let s=await ye(e.io.cwd,t);await Zb(tw(s),{recursive:!0}),await ye(e.io.cwd,t),await ew(s,n)}var uc=`${JSON.stringify(Ns,null,2)}
661
+ `;async function Ks(e,t,n){await qs(e,dn,uc);let s=lc.relative(lc.dirname(t),dn);await qs(e,t,`${JSON.stringify({$schema:s.startsWith(".")?s:`./${s}`,...n},null,2)}
662
+ `)}async function Gs(e,t){let n=ge(await _e(e,t,"the paywall"),"the paywall");if(typeof n!="object"||n===null||Array.isArray(n))throw m("a paywall is a JSON object: { schema, name, default_locale, theme, root, \u2026 }");if("draft"in n&&"draft_revision"in n)throw m('the file is a whole paywall (its draft, its revision, its problems): give its "draft" alone, as purchases:paywalls:get --out writes it');if("definition"in n&&!("root"in n))throw m('the file is a definition inside something else: give the "definition" alone, as purchases:paywalls:get --out writes it');let{$schema:s,...a}=n;return a}function Bs(e,t){for(let n of t)e.out.note(` ${n.path}: ${n.code} \u2014 ${n.message}${n.warning?" (warning)":""}`)}var mc=e=>e.filter(t=>!t.warning).length;function hc(e){if(!(!(e instanceof x)||e.code!=="invalid_paywall"))return(e.body??{}).problems??[]}function gc(e,t,n){let s=hc(t);throw s!==void 0?(s.length>0&&(e.out.note(`The paywall breaks ${mc(s)} rule(s):`),Bs(e,s)),new h("invalid_paywall",`${n}: ${t.message}`,{hint:"fix each problem in the file (palbase purchases:paywalls:validate --file <file> checks it without saving)"})):t}function fc(e,t,n){return new h("paywall_changed",`the draft of ${e} is no longer at revision ${t}: someone saved a newer one since it was read \u2014 nothing was ${n}`,{hint:`read it again (palbase purchases:paywalls:get ${JSON.stringify(e)}), make the edit on it, and save it with that revision`})}async function aw(e,t){let n=new Map,s=(o,l)=>{if(o===void 0)return;let p=n.get(o)??[];p.includes(l)||p.push(l),n.set(o,p)},{placements:a}=await u(e,t,"GET","/purchases/placements");for(let o of a)s(o.paywall_id,o.key);let r=a.filter(o=>o.rule_count>0);return(await Promise.all(r.map(o=>u(e,t,"GET",`/purchases/placements/${v(o.key)}/rules`)))).forEach((o,l)=>{for(let p of o.rules)s(p.target.paywall_id,`${r[l].key} (rule)`)}),n}var rw={name:"purchases:paywalls",summary:"the environment's paywalls: each one's draft revision, the version published, and the placements that show it",details:["A paywall is a JSON definition: purchases:paywalls:get writes one to a file, purchases:paywalls:put saves the edit as its draft, purchases:paywalls:publish makes the draft what the apps draw.","An app sees a paywall once it is published and a placement shows it.","How a definition is written \u2014 every component and field, and three to start from: palbase purchases:paywalls:guide"],scope:"environment",async run(e){let t=await y(e),[n,s]=await Promise.all([it(e,t),aw(e,t)]),a=n.map(r=>({...r,placements:s.get(r.id)??[]}));e.out.result({paywalls:a},()=>{if(a.length===0){e.out.line("No paywall yet."),e.out.line(`Write a definition and save it: palbase purchases:paywalls:put <name> --file ${me}/<name>.json --revision 0`);return}e.out.table(a,[{header:"name",value:r=>r.name},{header:"id",value:r=>r.id},{header:"draft",value:r=>`revision ${r.draft_revision}`},{header:"published",value:r=>r.published_version===0?"never":`version ${r.published_version}, ${D(r.published_at)}`},{header:"shown at",value:r=>r.placements.length===0?"\u2014":r.placements.join(", ")}])})}},iw={name:"purchases:paywalls:get",summary:"write a paywall's draft \u2014 or a version published \u2014 to a file to edit, with the revision it is at",usage:`<name|id> [--out <${me}/name.json>] [--version <n>]`,details:[`The file is the definition alone, as purchases:paywalls:put takes it, under ${me}/. Its first line names its JSON Schema, written beside it (${dn}), so an editor checks the file as it is edited.`,"The revision printed is the one an edit is saved on: purchases:paywalls:put --revision."],args:{min:1,max:1},flags:{out:{type:"string",value:"file",description:`where under ${U}/ (default: ${me}/<name>.json)`},version:{type:"string",value:"n",description:"a published version instead of the draft"}},scope:"environment",async run(e){let t=await y(e),n=fe(e.flags.version,"version");if(n===0)throw m("--version is 1 or more: the versions published");let s=await jt(e,t,cn(e,0,"the paywall's name or id")),a=e.flags.out!==void 0?Je(e,"out",e.flags.out,".json"):`${me}/${Nn(s.name)}${n===void 0?"":`.v${n}`}.json`;if(n!==void 0){let i;try{i=await u(e,t,"GET",`/purchases/paywalls/${v(s.id)}/versions/${n}`)}catch(o){throw o instanceof x&&o.status===404?new h("version_not_found",`${s.name} has no version ${n}${s.published_version===0?": it was never published":`: its newest is ${s.published_version} (the newest 50 are kept)`}`):o}if(!i.definition)throw new h("unexpected_answer",`the module answered version ${n} of ${s.name} without its definition`);await Ks(e,a,i.definition),e.out.result({file:a,paywall_id:s.id,name:s.name,version:i.version,published_at:i.published_at,draft_revision:s.draft_revision},()=>{e.out.line(`Wrote version ${i.version} of ${s.name} (published ${D(i.published_at)}) to ${a}.`),e.out.line(`Saved as the draft, it goes on the draft's revision: palbase purchases:paywalls:put ${JSON.stringify(s.name)} --file ${a} --revision ${s.draft_revision}`)});return}let r=await u(e,t,"GET",`/purchases/paywalls/${v(s.id)}`);await Ks(e,a,r.draft),e.out.result({file:a,paywall_id:r.id,name:r.name,revision:r.draft_revision,published_version:r.published_version,problems:r.problems},()=>{e.out.line(`Wrote draft revision ${r.draft_revision} of ${r.name} to ${a}${r.published_version===0?" (never published)":` (version ${r.published_version} is published)`}.`),r.problems.length>0&&(e.out.note("The module says of the draft:"),Bs(e,r.problems)),e.out.line(`Save an edit of it: palbase purchases:paywalls:put ${JSON.stringify(r.name)} --file ${a} --revision ${r.draft_revision}`)})}},ow={name:"purchases:paywalls:validate",summary:"check a paywall definition as publishing it would, without saving it: every problem, by its path",usage:"--file <paywall.json>",details:["Every rule of the schema is checked, and every package against the environment's catalog as it is. A warning (what App Review expects of a paywall, say) does not stop a save or a publish.","It exits 1 while a problem that is not a warning is left; --json prints { valid, problems } either way."],flags:{file:{type:"string",value:"paywall.json",description:"the definition ('-' or none: stdin)"}},scope:"environment",async run(e){let t=await y(e),n=await Gs(e,e.flags.file),s;try{s={valid:!0,problems:(await u(e,t,"POST","/purchases/paywalls/validate",n,R())).problems??[]}}catch(a){let r=hc(a);if(r===void 0)throw a;s={valid:!1,problems:r}}return e.out.result(s,()=>{s.problems.length>0&&Bs(e,s.problems);let a=s.problems.filter(r=>r.warning).length;e.out.line(s.valid?`The paywall can be published${a>0?`, with ${a} warning(s)`:""}.`:`The paywall breaks ${mc(s.problems)} rule(s): it cannot be saved as it is.`)}),s.valid?void 0:1}};async function yc(e,t,n,s){try{let a=await we(e,t,"POST",`/purchases/paywalls/${v(n.id)}/publish`,void 0,{"if-match":Us(s),...R()});return{version:a.body,fresh:a.status===201}}catch(a){if(a instanceof x&&a.status===412)throw fc(n.name,s,"published");gc(e,a,`${n.name} was not published`)}}function bc(e,t,n){e.out.line(n.fresh?`Published version ${n.version.version} of ${t}: the apps asking for a placement that shows it get it at once.`:`The draft of ${t} is what version ${n.version.version} already is: nothing new was published.`)}var lw={name:"purchases:paywalls:put",summary:"save a definition as a paywall's draft, on the revision it was edited from \u2014 or make the paywall, at revision 0; --publish publishes it too",usage:"<name|id> --file <paywall.json> --revision <n> [--publish]",details:["The module checks it first: a definition 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 draft moved past the revision they edited \u2014 never overwritten.","What the apps draw changes only when the draft is published (--publish, or purchases:paywalls:publish). A new paywall takes the name its definition gives.","How a definition is written: palbase purchases:paywalls:guide"],args:{min:1,max:1},flags:{file:{type:"string",value:"paywall.json",description:"the definition, as purchases:paywalls:get wrote it ('-' or none: stdin)"},revision:{type:"string",value:"n",description:"the draft revision it was edited from, as purchases:paywalls:get printed it (0 makes a new paywall)"},publish:{type:"boolean",description:"publish the saved draft as the next version"}},scope:"environment",async run(e){let t=await y(e),n=cn(e,0,"the paywall's name or id"),s=fe(e.flags.revision,"revision");if(s===void 0)throw m("--revision is the draft revision the paywall was edited from (0 for a new one): of two edits at once, the second is told, not saved over the first");let a=await Gs(e,e.flags.file),r=zs(await it(e,t),n),i=e.flags.publish===!0?" and publish it":"",o;try{if(r){if(s===0)throw new h("paywall_exists",`${r.name} is there already, at draft revision ${r.draft_revision}: an edit of it is saved on the revision it was read at`,{hint:`palbase purchases:paywalls:get ${JSON.stringify(r.name)} writes it and prints the revision`});await w(e,t,`save the draft of ${r.name} over revision ${s}${i}`),o=await u(e,t,"PUT",`/purchases/paywalls/${v(r.id)}/draft`,{definition:a},{"if-match":Us(s),...R()})}else{if($r.test(n))throw new h("paywall_not_found",`no paywall ${n}`);if(s!==0)throw new h("paywall_not_found",`no paywall ${JSON.stringify(n)} to save revision ${s} over`,{hint:"--revision 0 makes it"});if(typeof a.name!="string"||a.name.toLocaleLowerCase()!==n.toLocaleLowerCase())throw m(`the file names the paywall ${JSON.stringify(a.name)}, not ${JSON.stringify(n)}: a new paywall takes the name its definition gives`,`set "name" to ${JSON.stringify(n)} in the file, or: palbase purchases:paywalls:put ${JSON.stringify(a.name??"")} \u2026`);await w(e,t,`make the paywall ${a.name}${i}`),o=await u(e,t,"POST","/purchases/paywalls",{definition:a},R())}}catch(p){if(p instanceof x&&p.status===412)throw fc(r?.name??n,s,"saved");if(p instanceof x&&p.code==="too_many_paywalls")throw new h("too_many_paywalls","the project has 100 paywalls, the most it holds: nothing was made",{hint:"delete one no placement shows: palbase purchases:paywalls:delete <name>"});gc(e,p,"the paywall was not saved")}let l=e.flags.publish===!0?await yc(e,t,o,o.draft_revision):void 0;e.out.result({...o,...l?{published:l.version,published_new:l.fresh}:{}},()=>{e.out.line(`${r?"Saved":"Made"} ${o.name}: its draft is at revision ${o.draft_revision}.`),o.problems.length>0&&(e.out.note("The module says of the draft:"),Bs(e,o.problems)),l?bc(e,o.name,l):e.out.line(`The apps draw it once it is published: palbase purchases:paywalls:publish ${JSON.stringify(o.name)}`)})}},pw={name:"purchases:paywalls:publish",summary:"publish a paywall's draft as its next version: what the apps asking for a placement that shows it draw from then on",usage:"<name|id> [--revision <n>]",details:["It is checked as the apps will get it: every package must be sold by the catalog, every asset still what was registered.","--revision names the draft revision that was looked at: a draft saved since is not published in its place. Without it, the draft as it is now is published.","A draft the same as the newest version publishes nothing new."],args:{min:1,max:1},flags:{revision:{type:"string",value:"n",description:"the draft revision looked at (default: the draft as it is now)"}},scope:"environment",async run(e){let t=await y(e),n=await jt(e,t,cn(e,0,"the paywall's name or id")),s=fe(e.flags.revision,"revision")??n.draft_revision;await w(e,t,`publish draft revision ${s} of ${n.name}`);let a=await yc(e,t,n,s);e.out.result({...a.version,published_new:a.fresh},()=>bc(e,n.name,a))}},dw={name:"purchases:paywalls:delete",summary:"delete a paywall and its versions; one a placement or a running experiment shows is not deleted",usage:"<name|id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await y(e),n=await jt(e,t,cn(e,0,"the paywall's name or id"));await w(e,t,`delete the paywall ${n.name}: its draft and every version of it`);try{await u(e,t,"DELETE",`/purchases/paywalls/${v(n.id)}`,void 0,R())}catch(s){throw s instanceof x&&s.code==="paywall_in_use"?new h("paywall_in_use",`${n.name} is shown \u2014 a placement shows it, as its default or by a rule, or a running experiment does \u2014 so it was not deleted`,{hint:"palbase purchases:paywalls says where; give those placements another paywall, or stop the experiment, first"}):s}e.out.result({deleted:n.id,name:n.name},()=>e.out.line(`Deleted ${n.name}.`))}},cw={name:"purchases:paywalls:schema",summary:"the JSON Schema of a paywall definition: every component, its fields and their bounds, for an editor or an agent to check a file against",usage:`[--out <${dn}>]`,details:["It is the module's contract, as the CLI was built with it: a file that passes may still be refused for what only the module knows (the catalog's packages, the project's assets) \u2014 purchases:paywalls:validate asks it.",'purchases:paywalls:get writes it too, and names it on the first line of each file ("$schema").'],flags:{out:{type:"string",value:"file",description:`where under ${U}/ (default: ${dn}); --json prints it instead`}},scope:"none",async run(e){if(e.global.json){e.out.result(Ns,()=>{});return}let t=e.flags.out!==void 0?Je(e,"out",e.flags.out,".json"):dn;await qs(e,t,uc),e.out.line(`Wrote the paywall definition's JSON Schema to ${t}.`)}},uw={name:"purchases:paywalls:guide",summary:"how a paywall definition is written by hand \u2014 for an agent or a person: the flow, every component and field in the contract's words, three definitions to start from",usage:`[--out <${me}/GUIDE.txt>]`,details:["It is made from the definition's JSON Schema the CLI carries (purchases:paywalls:schema), so it says what this version of the CLI checks."],flags:{out:{type:"string",value:"file",description:`write it to a file under ${U}/ instead of printing it`}},scope:"none",async run(e){let t=oc(Ns),n=e.flags.out;if(n===void 0){e.out.result({guide:t},()=>e.io.stdout(t));return}let s=Je(e,"out",n,".txt");await qs(e,s,t),e.out.result({file:s},()=>e.out.line(`Wrote the guide to ${s}.`))}},pc=["image","video","font","html"];function Sr(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(e<10*1024?1:0)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}async function un(e,t){return(await u(e,t,"GET","/purchases/paywalls/assets")).assets??[]}var dc={unsupported_asset:"it is no file a paywall takes: an image (PNG, JPEG or WebP, never SVG), a video (MP4 or MOV), a font (TTF, OTF or WOFF2), or an HTML block as a zip",asset_too_large:"it is too large for a paywall: an image is at most 2 MiB, a video 50 MiB, a font or an HTML block's zip 5 MiB",asset_kind_mismatch:"its bytes are not the kind --kind names",invalid_archive:"the zip could not be opened: an HTML block is a zip with index.html at its root",archive_too_many_files:"the HTML block holds too many files: at most 200",archive_too_large:"the HTML block is too large opened: at most 20 MiB of files",unsafe_archive_path:"the HTML block has a file whose path leaves the zip: every file stays under its root",archive_link:"the HTML block holds a link: a zip of plain files is taken",archive_no_index:"the HTML block has no index.html at its root",asset_unreadable:"the purchases module could not read it at its public address; trying again in a moment is safe",too_many_assets:"the project has 500 assets, the most it holds: delete one no paywall names first (palbase purchases:paywalls:assets:delete <id>)"};async function Pr(e,t){let n;try{n=await u(e,t,"GET",`/storage/buckets/${vt}`)}catch(s){if(!(s instanceof x)||s.status!==404)throw s}if(!n?.public){if(n)throw new h("asset_bucket_private",`the project's ${vt} bucket is private: an app downloads a paywall's assets at their public address, and the CLI does not open a bucket the project closed`,{hint:`if everything in it may be read by anyone: palbase storage:buckets:set ${vt} --public`});await u(e,t,"PUT",`/storage/buckets/${vt}`,{public:!0,file_size_limit:null,allowed_mime_types:[],variants:[]}),e.out.note(`Made the public bucket ${vt}: paywall assets are kept in the project's own storage.`)}}function mw(e){let t=Ms(e).normalize("NFKD").replace(/[^\w.-]+/g,"-").replace(/^-+|-+$/g,"").slice(-80).replace(/^\.+$/,"")||"file";return`paywalls/${Yb()}/${t}`}async function Ar(e,t,n,s){let a=mw(n);await gr(e,t,n,vt,a);try{return await u(e,t,"POST","/purchases/paywalls/assets",{path:a,...s?{kind:s}:{}},R())}catch(r){throw await u(e,t,"DELETE",`/storage/buckets/${vt}/objects/${xs(a)}`).catch(()=>{}),r instanceof x&&r.code!==void 0&&r.code in dc?new h(r.code,`${Ms(n)} was not registered: ${dc[r.code]}`):r}}var hw={name:"purchases:paywalls:assets",summary:"the project's paywall assets: the images, videos, fonts and HTML blocks a definition names asset:<id>",scope:"environment",async run(e){let t=await y(e),n=await un(e,t);e.out.result({assets:n},()=>{if(n.length===0){e.out.line("No paywall asset yet."),e.out.line("Upload one: palbase purchases:paywalls:assets:upload <file>");return}e.out.table(n,[{header:"ref",value:s=>s.ref},{header:"kind",value:s=>s.kind},{header:"size",value:s=>Sr(s.bytes)},{header:"file",value:s=>s.path.split("/").pop()??s.path},{header:"registered",value:s=>D(s.created_at)}])})}},gw={name:"purchases:paywalls:assets:upload",summary:"upload a file to the project's storage and register it for paywalls: it answers the asset:<id> a definition names",usage:"<file> [--kind image|video|font|html]",details:[`The file goes to the project's public bucket ${vt} (made when there is none; a private one is left as it is, and refuses). The module then reads it there, says what it is by its bytes, and keeps their SHA-256: an app draws what it downloads only when it is that.`,"An image is PNG, JPEG or WebP, at most 2 MiB; a video MP4 or MOV, 50 MiB; a font TTF, OTF or WOFF2, 5 MiB; an HTML block a zip with index.html at its root, 5 MiB."],args:{min:1,max:1},flags:{kind:{type:"string",value:"kind",description:"what the file is meant to be: one whose bytes are another kind is refused"}},scope:"environment",async run(e){let t=await y(e),n=cc(e.io.cwd,cn(e,0,"the file")),s=e.flags.kind;if(s!==void 0&&!pc.includes(s))throw m(`--kind is one of ${pc.join(", ")}`);let a;try{let i=await Qb(n);if(!i.isFile())throw new Error("it is not a file");a=i.size}catch(i){throw new h("unreadable_file",`${e.args[0]} could not be read: ${i instanceof Error?i.message:String(i)}`)}if(a===0)throw new h("empty_file",`${e.args[0]} is empty`);await w(e,t,`upload ${Ms(n)} (${Sr(a)}) as a paywall asset`),await Pr(e,t);let r=await Ar(e,t,n,s);e.out.result(r,()=>{e.out.line(`Registered ${Ms(n)} as ${r.ref} (${r.kind}, ${Sr(r.bytes)}).`),e.out.line(`A definition names it "${r.ref}" where it takes ${r.kind==="html"?"an HTML block":r.kind==="image"?"an image":`a ${r.kind}`}.`)})}},fw={name:"purchases:paywalls:assets:delete",summary:"forget a paywall asset no paywall names, and remove its file from the project's storage",usage:"<asset:id|id>",args:{min:1,max:1},scope:"environment",async run(e){let t=await y(e),n=cn(e,0,"the asset's id").replace(/^asset:/,"").toLowerCase();if(!$r.test(n))throw m(`${e.args[0]} is not an asset: asset:<id>, as purchases:paywalls:assets lists them`);let s=(await un(e,t)).find(r=>r.id===n);if(!s)throw new h("asset_not_found",`the project has no asset ${n}`,{hint:"palbase purchases:paywalls:assets lists them"});await w(e,t,`delete the paywall asset ${s.ref} (${s.path.split("/").pop()})`);try{await u(e,t,"DELETE",`/purchases/paywalls/assets/${v(n)}`,void 0,R())}catch(r){throw r instanceof x&&r.code==="asset_in_use"?new h("asset_in_use",`${s.ref} is named by a paywall \u2014 in its draft, or in a version kept \u2014 so it was not deleted`,{hint:"take it out of the paywall (its draft, then publish) first"}):r}let a=!0;await u(e,t,"DELETE",`/storage/buckets/${v(s.bucket)}/objects/${xs(s.path)}`).catch(()=>{a=!1}),e.out.result({deleted:n,object_removed:a},()=>{e.out.line(`Deleted ${s.ref}.`),a||e.out.warn(`its file stays in the project's storage: palbase storage:rm ${s.bucket}:${s.path}`)})}},wc=[rw,iw,ow,lw,pw,dw,cw,uw,hw,gw,fw];import{createHash as xw}from"node:crypto";import{mkdir as Lw,writeFile as Dw}from"node:fs/promises";import{dirname as Iw}from"node:path";import{spawn as yw}from"node:child_process";import{accessSync as bw,constants as ww,mkdtempSync as vw,readFileSync as _w,rmSync as kw}from"node:fs";import{tmpdir as Sw}from"node:os";import{delimiter as $w,join as Ot}from"node:path";import{pathToFileURL as Pw}from"node:url";function vc(e,t=process.platform){let n=i=>{try{return bw(i,ww.X_OK),!0}catch{return!1}},s=e.PALBASE_BROWSER?.trim();if(s)return n(s)?{path:s}:{named:s};let a=[];if(t==="darwin")for(let i of["/Applications",Ot(e.HOME??"","Applications")])a.push(`${i}/Google Chrome.app/Contents/MacOS/Google Chrome`,`${i}/Chromium.app/Contents/MacOS/Chromium`,`${i}/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`,`${i}/Brave Browser.app/Contents/MacOS/Brave Browser`);else if(t==="win32")for(let i of[e.PROGRAMFILES,e["PROGRAMFILES(X86)"],e.LOCALAPPDATA])i&&a.push(Ot(i,"Google","Chrome","Application","chrome.exe"),Ot(i,"Microsoft","Edge","Application","msedge.exe"),Ot(i,"Chromium","Application","chrome.exe"));else for(let i of(e.PATH??"").split($w).filter(Boolean))for(let o of["google-chrome","google-chrome-stable","chromium","chromium-browser","microsoft-edge","microsoft-edge-stable","brave-browser"])a.push(Ot(i,o));let r=a.find(n);return r?{path:r}:{}}function _c(e){return(e.PALBASE_BROWSER_ARGS??"").split(/\s+/).filter(Boolean)}var Mn=class extends Error{noSandbox;constructor(t){let n=t.split(`
663
+ `).map(r=>r.replace(/^\[[^\]]*\]\s*/,"").trim()).filter(Boolean),s=n.some(r=>r.includes("No usable sandbox")),a=s?"it has no sandbox to run in here":[...n].reverse().find(r=>/FATAL|ERROR|error/.test(r))??n.at(-1);super(`the browser closed as it started${a?`: ${a.slice(0,200)}`:""}`),this.noSandbox=s}},Js=e=>new Promise(t=>setTimeout(t,e));async function kc(e,t,n,s={}){let a=s.timeoutMs??3e4,r=vw(Ot(Sw(),"palbase-preview-")),i=yw(e,["--headless=new","--disable-gpu","--hide-scrollbars","--no-first-run","--no-default-browser-check","--disable-extensions","--disable-background-networking","--disable-sync","--mute-audio","--autoplay-policy=no-user-gesture-required",`--user-data-dir=${r}`,"--remote-debugging-port=0",...s.args??[],"about:blank"],{stdio:["ignore","ignore","pipe"]}),o="";i.stderr?.on("data",d=>{o=(o+d.toString("utf8")).slice(-8192)});let l=!1;i.once("exit",()=>{l=!0});let p=new Promise((d,c)=>i.once("error",g=>c(new Error(`the browser did not start: ${g.message}`))));try{return await Promise.race([p,(async()=>{let d;for(let b=Date.now()+2e4;d===void 0&&Date.now()<b&&!l;await Js(100))try{d=Number(_w(Ot(r,"DevToolsActivePort"),"utf8").split(`
664
+ `)[0])}catch{}if(d===void 0||!Number.isInteger(d))throw l?new Mn(o):new Error("the browser did not open its DevTools port in 20 s");let c;for(let b=Date.now()+1e4;c===void 0;await Js(100))try{c=await(await fetch(`http://127.0.0.1:${d}/json/list`)).json()}catch(P){if(Date.now()>b||l)throw new Error(`the browser's DevTools did not answer: ${P instanceof Error?P.message:String(P)}`)}let g=c.find(b=>b.type==="page");if(!g)throw new Error("the browser opened no page");let f=await Aw(g.webSocketDebuggerUrl);try{await f.send("Page.enable");let b=[];for(let P of n){await f.send("Emulation.setDeviceMetricsOverride",{width:P.width,height:P.height,deviceScaleFactor:P.scale,mobile:!1}),await f.send("Page.navigate",{url:`${Pw(t).href}?${P.query}`});let E=!1;for(let I=Date.now()+a;!E&&Date.now()<I;)await Js(100),E=(await f.send("Runtime.evaluate",{expression:"document.readyState === 'complete' && document.body?.dataset.ready === '1'",returnByValue:!0})).result?.value===!0;if(!E)throw new Error(`the page did not finish drawing in ${Math.round(a/1e3)} s`);let k=await f.send("Runtime.evaluate",{expression:"JSON.stringify({ error: document.body.dataset.error, selected: document.body.dataset.selected, findings: document.body.dataset.findings })",returnByValue:!0}),{error:$,selected:O,findings:A}=JSON.parse(String(k.result.value)),L=P.height;if(P.full&&!$){let I=await f.send("Runtime.evaluate",{expression:"window.PALBASE_FULL()",awaitPromise:!0,returnByValue:!0}),j=Number(I.result?.value);Number.isFinite(j)&&j>L&&(L=Math.min(j,16e3),await f.send("Emulation.setDeviceMetricsOverride",{width:P.width,height:L,deviceScaleFactor:P.scale,mobile:!1}),await f.send("Runtime.evaluate",{expression:"new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(1))))",awaitPromise:!0}))}let X=P.measureOnly?void 0:await f.send("Page.captureScreenshot",{format:"png",captureBeyondViewport:!1});b.push({png:X?Buffer.from(String(X.data),"base64"):new Uint8Array,height:L,...$?{error:$}:{},...O?{selected:O}:{},...A?{findings:JSON.parse(A)}:{}})}return b}finally{f.close()}})()])}finally{i.kill();for(let d=0;d<20&&!l;d++)await Js(50);l||i.kill("SIGKILL");try{kw(r,{recursive:!0,force:!0,maxRetries:10,retryDelay:100})}catch{}}}async function Aw(e){let t=new WebSocket(e);await new Promise((a,r)=>{t.addEventListener("open",()=>a(),{once:!0}),t.addEventListener("error",()=>r(new Error("the browser refused the DevTools connection")),{once:!0})});let n=0,s=new Map;return t.addEventListener("message",a=>{let r=JSON.parse(String(a.data));if(r.id===void 0)return;let i=s.get(r.id);s.delete(r.id),r.error?i?.reject(new Error(r.error.message)):i?.resolve(r.result??{})}),t.addEventListener("close",()=>{for(let a of s.values())a.reject(new Error("the browser closed the DevTools connection"));s.clear()}),{send(a,r={}){let i=++n;return new Promise((o,l)=>{s.set(i,{resolve:o,reject:l}),t.send(JSON.stringify({id:i,method:a,params:r}))})},close:()=>t.close()}}function Sc(){return Promise.resolve('"use strict";(()=>{var We=o=>`pw-font-${o}`;function Se(o){return(typeof o=="string"?/^asset:([0-9a-f-]{36})$/.exec(o):null)?.[1]}async function pt(o){let t=new DataView(o.buffer,o.byteOffset,o.byteLength),n=-1;for(let y=o.length-22;y>=Math.max(0,o.length-22-65535);y--)if(t.getUint32(y,!0)===101010256){n=y;break}if(n<0)throw new Error("not a zip");let i=t.getUint16(n+10,!0),d=t.getUint32(n+16,!0),p=new Map,h=0;for(let y=0;y<i;y++){if(t.getUint32(d,!0)!==33639248)throw new Error("a broken zip");let m=t.getUint16(d+10,!0),k=t.getUint32(d+20,!0),v=t.getUint32(d+24,!0),x=t.getUint16(d+28,!0),L=t.getUint16(d+30,!0),A=t.getUint16(d+32,!0),C=t.getUint32(d+42,!0),M=new TextDecoder().decode(o.subarray(d+46,d+46+x));if(d+=46+x+L+A,M.endsWith("/"))continue;if(M.startsWith("/")||M.split("/").includes(".."))throw new Error("a path leaving the zip");if(h+=v,p.size>=200||h>20971520)throw new Error("a zip too large");let j=C+30+t.getUint16(C+26,!0)+t.getUint16(C+28,!0),J=o.subarray(j,j+k);if(m===0)p.set(M,J);else if(m===8)p.set(M,await Dt(J));else throw new Error("a zip entry compressed another way")}return p}async function Dt(o){let t=new Response(new Uint8Array(o)).body.pipeThrough(new DecompressionStream("deflate-raw"));return new Uint8Array(await new Response(t).arrayBuffer())}var Bt={html:"text/html",css:"text/css",js:"text/javascript",mjs:"text/javascript",json:"application/json",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",webp:"image/webp",gif:"image/gif",svg:"image/svg+xml",mp4:"video/mp4",webm:"video/webm",woff2:"font/woff2",woff:"font/woff",ttf:"font/ttf",otf:"font/otf"};function dt(o,t){let n=Bt[o.split(".").pop()?.toLowerCase()??""]??"application/octet-stream",i="";for(let d=0;d<t.length;d+=32768)i+=String.fromCharCode(...t.subarray(d,d+32768));return`data:${n};base64,${btoa(i)}`}var Jt="default-src \'none\'; img-src data:; media-src data:; font-src data:; style-src \'unsafe-inline\' data:; script-src \'unsafe-inline\' data:";function ut(o){let t=o.get("index.html");if(!t)return;let n=new TextDecoder().decode(t),i=k=>new TextDecoder().decode(o.get(k)),d=k=>{let v=k.replace(/^\\.\\//,"").split(/[?#]/)[0];return o.has(v)?v:void 0},p=k=>k.replace(/url\\(\\s*([\'"]?)([^\'")]+)\\1\\s*\\)/g,(v,x,L)=>{let A=d(L);return A?`url("${dt(A,o.get(A))}")`:v}),h=globalThis.DOMParser;if(!h)return;let y=new h().parseFromString(n,"text/html");for(let k of[...y.querySelectorAll(\'link[rel~="stylesheet"][href]\')]){let v=d(k.getAttribute("href")),x=y.createElement("style");x.textContent=v?p(i(v)):"",k.replaceWith(x)}for(let k of[...y.querySelectorAll("style")])k.textContent=p(k.textContent??"");for(let k of[...y.querySelectorAll("[src], [href], [poster]")])for(let v of["src","href","poster"]){let x=k.getAttribute(v);if(x===null)continue;let L=d(x);L?k.setAttribute(v,dt(L,o.get(L))):/^(https?:)?\\/\\//i.test(x)&&k.removeAttribute(v)}for(let k of[...y.querySelectorAll("[style]")])k.setAttribute("style",p(k.getAttribute("style")??""));let m=y.createElement("meta");return m.setAttribute("http-equiv","Content-Security-Policy"),m.setAttribute("content",Jt),y.head.prepend(m),`<!doctype html>${y.documentElement.outerHTML}`}var Vt={ios:44,android:44,web:24},ft=.12;function ee(o){let t=/^rgba?\\(([\\d.]+),\\s*([\\d.]+),\\s*([\\d.]+)(?:,\\s*([\\d.]+))?\\)$/.exec(o.trim());return t?[Number(t[1]),Number(t[2]),Number(t[3]),t[4]===void 0?1:Number(t[4])]:void 0}var Me;function Wt(o){if(o.trim()==="")return;let t=ee(o);return t||(Me??=document.body.appendChild(document.createElement("i")),Me.style.color="",Me.style.color=o.trim(),Me.style.color===""?void 0:ee(getComputedStyle(Me).color))}var Ke=(o,t)=>{let n=o[3]+t[3]*(1-o[3]);if(n===0)return[0,0,0,0];let i=d=>(o[d]*o[3]+t[d]*t[3]*(1-o[3]))/n;return[i(0),i(1),i(2),n]};function gt([o,t,n]){let i=d=>{let p=d/255;return p<=.04045?p/12.92:((p+.055)/1.055)**2.4};return .2126*i(o)+.7152*i(t)+.0722*i(n)}function ht(o,t){let[n,i]=[gt(o),gt(t)].sort((d,p)=>p-d);return(n+.05)/(i+.05)}var mt=([o,t,n])=>`#${[o,t,n].map(i=>Math.round(i).toString(16).padStart(2,"0")).join("").toUpperCase()}`,Ue=o=>`${(Math.floor(o*10)/10).toFixed(1)}:1`,G=o=>Math.round(o);function He(o){return o.parentElement??o.getRootNode().host??null}function yt(o){let t=[],n=1;for(let d=o;d;d=He(d)){let p=getComputedStyle(d);if(p.backgroundImage!=="none")return;let h=ee(p.backgroundColor);if(h&&h[3]>0&&t.push(h),t.length===0&&(n*=Number(p.opacity)),h&&h[3]>=1)break}let i=[255,255,255,1];for(let d of t.reverse())i=Ke(d,i);return{color:i,opacity:n}}function Ut(o){for(let t=o;t;t=He(t)){let n=getComputedStyle(t);if(n.backgroundImage!=="none")return t;let i=ee(n.backgroundColor);if(i&&i[3]>=1)return}}function bt(o){let t=yt(o),n=ee(getComputedStyle(o).color);if(!t||!n)return;let i=n[3];for(let d=o;d;d=He(d)){let p=getComputedStyle(d);i*=Number(p.opacity);let h=ee(p.backgroundColor);if(h&&h[3]>0)break}return{text:Ke([n[0],n[1],n[2],i],t.color),bg:t.color}}function ce(o){let t=o.getBoundingClientRect();if(t.width<1||t.height<1)return!1;for(let n=o;n;n=He(n)){let i=getComputedStyle(n);if(i.visibility==="hidden"||i.display==="none"||Number(i.opacity)===0)return!1}return!0}var Ce=o=>o.closest("[data-pw-path]")?.dataset.pwPath??"the page";function kt(o){let t=(o.innerText??o.textContent??"").replace(/\\s+/g," ").trim();return t.length>40?`${t.slice(0,39)}\\u2026`:t}var ue=o=>{let t=kt(o),n=o.dataset?.pwType??o.getAttribute("role")??o.closest("[data-pw-path]")?.dataset.pwType??o.tagName.toLowerCase();return`${Ce(o)}${t?` "${t}"`:` (${n}${o.getAttribute("aria-label")?` "${o.getAttribute("aria-label")}"`:""})`}`},wt="button, [role=radio], [role=tab], [role=switch], a[href]";function qt(o,t){for(let n=o.parentElement;n&&t.contains(n);n=n.parentElement){if(n.classList.contains("pw2-body")||n.classList.contains("pw2"))return;let i=getComputedStyle(n);if(i.overflowX!=="visible"||i.overflowY!=="visible")return n}}function qe(o,t){for(let n=o.parentElement;n&&t.contains(n);n=n.parentElement){if(n.classList.contains("pw-track")||n.classList.contains("pw-carousel"))return!0;let i=getComputedStyle(n).overflowX;if((i==="auto"||i==="scroll")&&!n.classList.contains("pw2-body"))return!0}return!1}function vt(o,t){let n=[],i=(u,g,E)=>n.push({code:u,where:typeof g=="string"?g:ue(g),message:E}),d=o.querySelector(".pw2"),p=o.querySelector(".pw2-body"),h=o.querySelector(".pw2-foot"),y=d.getBoundingClientRect(),m=h&&ce(h)?h.getBoundingClientRect():void 0,k=()=>{let u=p.getBoundingClientRect(),g=d.getBoundingClientRect(),E=h&&ce(h)?h.getBoundingClientRect():void 0;return{top:Math.max(u.top,g.top),bottom:Math.min(u.bottom,g.bottom,E&&E.height>0?E.top:1/0)}},v=k(),x=[...o.querySelectorAll("[data-pw-path]")].filter(ce),L=u=>u.dataset.pwType??"",A=u=>p.contains(u),C=(u,g)=>{let E=u.getBoundingClientRect(),R=Math.max(E.top,g.top),w=Math.min(E.bottom,g.bottom);for(let _=u.parentElement;_&&o.contains(_)&&!_.classList.contains("pw2-body")&&!_.classList.contains("pw2");_=_.parentElement){let z=getComputedStyle(_);if(z.overflowX==="visible"&&z.overflowY==="visible")continue;let F=_.getBoundingClientRect();Math.min(E.right,F.right)-Math.max(E.left,F.left)<=0&&(w=R),R=Math.max(R,F.top),w=Math.min(w,F.bottom)}return{top:R,bottom:w,r:E,area:g}},M=u=>C(u,A(u)?v:y),j=Math.max(d.scrollWidth-d.clientWidth,p.scrollWidth-p.clientWidth);j>1&&i("overflow_x","the page",`the paywall is ${G(j)}px wider than the screen: it moves sideways`);let J=new Set;for(let u of x){if(qe(u,o)||[...J].some(R=>R.contains(u)))continue;let g=u.getBoundingClientRect(),E=Math.max(y.left-g.left,g.right-y.right);E>1&&(J.add(u),i("overflow_x",u,`it reaches ${G(E)}px past the screen\'s ${g.right>y.right?"right":"left"} edge`))}let Q=x.filter(u=>L(u)==="text");for(let u of Q){if(u.classList.contains("pw-clamp")&&u.scrollHeight>u.clientHeight+1){i("text_cut",u,"its text is cut off: it needs more lines than its max_lines gives at this size");continue}if(u.scrollWidth>u.clientWidth+1){i("text_cut",u,`a word is ${G(u.scrollWidth-u.clientWidth)}px wider than its place and is cut`);continue}let g=qe(u,o)?void 0:qt(u,o);if(g){let w=u.getBoundingClientRect(),_=g.getBoundingClientRect(),z=Math.max(_.top-w.top,w.bottom-_.bottom,_.left-w.left,w.right-_.right);if(z>1){i("text_cut",u,`${G(z)}px of its text is hidden by ${Ce(g)}, which clips what overflows it`);continue}}let E=document.createTreeWalker(u,NodeFilter.SHOW_TEXT),R;for(let w=E.nextNode();w&&!R;w=E.nextNode())for(let _ of(w.textContent??"").matchAll(/\\S+/g)){let z=document.createRange();if(z.setStart(w,_.index),z.setEnd(w,_.index+_[0].length),new Set([...z.getClientRects()].filter(D=>D.width>0).map(D=>Math.round(D.top))).size>1){R=_[0];break}}R&&i("word_broken",u,`"${R}" is broken across lines: a word, a price above all, is drawn whole`)}let ne=x.filter(u=>L(u)==="purchase_button"&&!o.querySelector(".pw-layer")?.contains(u));if(ne.length===0)i("purchase_hidden","the paywall","nothing on it buys: it has no purchase button where it is drawn");else if(!ne.some(u=>{let g=M(u);return g.r.top>=g.area.top-1&&g.r.bottom<=g.area.bottom+1})){let u=M(ne[0]),g=u.r.top>=u.area.bottom?`${G(u.r.top-u.area.bottom)}px below what shows`:`${G(u.r.height-Math.max(0,u.bottom-u.top))}px of it hidden`;i("purchase_hidden",ne[0],`the purchase button is not on the screen as the paywall opens (${g}): a customer scrolls to find it \\u2014 keep it in the fixed footer`)}if(m&&m.height>0){let u=[p,d].filter(E=>/^(auto|scroll)$/.test(getComputedStyle(E).overflowY)&&E.scrollHeight>E.clientHeight+1),g=u.map(E=>E.scrollTop);for(let E of u)E.scrollTop=E.scrollHeight;try{let E=k(),R=new Set;for(let w of x.filter(_=>A(_)&&_.matches(wt))){if(qe(w,o)||[...R].some(z=>z.contains(w)))continue;let _=C(w,{top:-1/0,bottom:1/0});if(_.bottom>_.top&&_.bottom>E.bottom+1){R.add(w);let z=Math.min(_.bottom-E.bottom,_.bottom-_.top);i("covered_by_footer",w,`${G(z)}px of its ${G(_.r.height)}px ${z>=_.bottom-_.top-1?"(all of it) ":""}stay under the fixed footer with the paywall scrolled to its end: nothing brings it into view \\u2014 end the body with room as tall as the footer (padding or a spacer)`)}}}finally{u.forEach((E,R)=>{E.scrollTop=g[R]})}}let xe=new Set(["stack","tabs","switch","carousel","countdown"]),Ee=[...o.querySelectorAll(".pw-switchrow, .pw-tablist, .pw-dots")].filter(ce);Ee.push(...x.filter(u=>{let g=L(u);if(g==="spacer")return!1;if(!xe.has(g))return!0;let E=u.parentElement;if(E&&(E.classList.contains("pw2-head")||E.classList.contains("pw2-body")||E.classList.contains("pw2-foot")))return!1;let R=getComputedStyle(u),w=ee(R.backgroundColor);return R.backgroundImage!=="none"||w!==void 0&&w[3]>0||u.style.getPropertyValue("--pw-bw")!==""}));let he=Ee.filter(u=>{let g=M(u);return g.bottom>g.top}),Ae=he.map(M).map(u=>[u.top,u.bottom]).sort((u,g)=>u[0]-g[0]),q={size:0,from:y.top,to:y.top},X=y.top;for(let[u,g]of Ae)u-X>q.size&&(q={size:u-X,from:X,to:u}),X=Math.max(X,g);if(y.bottom-X>q.size&&(q={size:y.bottom-X,from:X,to:y.bottom}),q.size>t.height*ft){let u=he.filter(R=>Math.abs(M(R).bottom-q.from)<1).pop(),g=he.find(R=>Math.abs(M(R).top-q.to)<1),E=u&&g?`between ${ue(u)} and ${ue(g)}`:u?`below ${ue(u)}`:g?`above ${ue(g)}`:"on the screen";i("large_gap",u?Ce(u):g?Ce(g):"the paywall",`a ${G(q.size)}px empty band (${Math.round(q.size/t.height*100)}% of the screen) ${E}: no single empty space is over ${Math.round(ft*100)}%`)}let me=new Set,oe=document.createTreeWalker(d,NodeFilter.SHOW_TEXT);for(let u=oe.nextNode();u;u=oe.nextNode())(u.textContent??"").trim()!==""&&u.parentElement&&me.add(u.parentElement);let B=new Map;for(let u of me){if(!ce(u)||u.closest(".pw-grad")||u.closest("button:disabled, [aria-disabled=true]"))continue;let g=bt(u);if(!g){let F=Ut(u);F&&B.set(F,[...B.get(F)??[],kt(u)]);continue}let E=getComputedStyle(u),R=parseFloat(E.fontSize),w=R>=24||R>=18.66&&Number(E.fontWeight)>=700,_=w?3:4.5,z=ht(g.text,g.bg);z<_&&i("contrast",u,`its text is ${Ue(z)} against what is behind it (${mt(g.text)} on ${mt(g.bg)}): ${w?"large ":""}text needs ${Ue(_)}`)}for(let[u,g]of B){let E=g.slice(0,3).map(w=>`"${w}"`).join(", "),R=u.dataset?.pwPath!==void 0||u.closest("[data-pw-path]")?ue(u).replace(/ ".*"$/,""):"the page";n.push({code:"unmeasured_contrast",where:R,message:`not measured: ${g.length} text(s) over its picture or gradient \\u2014 ${E}${g.length>3?" \\u2026":""} \\u2014 whose colors the check does not read; look at them (purchases:paywalls:preview)`,note:!0})}let be=u=>{let g=getComputedStyle(u),E=ee(g.backgroundColor);return g.backgroundImage!=="none"||E!==void 0&&E[3]>0||(u.style?.getPropertyValue("--pw-bw")??"")!==""||parseFloat(g.borderTopWidth)>0},se=u=>{for(let g=u,E=0;g&&E<4;g=g.firstElementChild,E++)if(be(g))return g;return u},we=new Map;for(let u of[...o.querySelectorAll("[role=radio], [role=tab]")].filter(ce)){let g=u.parentElement;we.set(g,[...we.get(g)??[],u])}for(let[,u]of we){let g=u.find(z=>z.getAttribute("aria-checked")==="true"||z.getAttribute("aria-selected")==="true");if(!g||u.length<2)continue;let E=z=>{let F=se(z),D=yt(F),Z=getComputedStyle(F),ie=Wt(Z.getPropertyValue("--pw-bc"))??ee(Z.borderTopColor),pe=parseFloat(Z.getPropertyValue("--pw-bw"))||parseFloat(Z.borderTopWidth)||0,Le=[...z.querySelectorAll("*")].find(Ie=>[...Ie.childNodes].some(_e=>_e.nodeType===3&&(_e.textContent??"").trim()!==""));return{bg:D?.color,border:pe>0&&ie&&D?Ke(ie,D.color):D?.color,text:Le?bt(Le)?.text:void 0}},R=E(g),w=1/0,_;for(let z of u.filter(F=>F!==g)){let F=E(z),D=[[R.bg,F.bg],[R.border,F.border],[R.text,F.text]].filter(([ie,pe])=>ie&&pe);if(D.length===0)continue;let Z=Math.max(...D.map(([ie,pe])=>ht(ie,pe)));Z<w&&(w=Z,_=z)}if(_&&w<3){let z=g.getAttribute("role")==="tab"?"tab":"package";i("selection_contrast",g,`the selected ${z} differs from ${ue(_)} by ${Ue(w)} at most \\u2014 in its fill, its border and its text: what shows it is selected needs 3:1`)}}let re=Vt[t.platform];for(let u of[...o.querySelectorAll(wt)].filter(ce)){if(u.closest(".pw-text")||u.closest(".pw-dots")||u.getAttribute("role")==="switch"||u.disabled)continue;let g=u.getBoundingClientRect();(g.width<re||g.height<re)&&i("small_target",u,`it is ${G(g.width)}\\xD7${G(g.height)}: ${t.platform==="web"?"a pointer needs":"a finger needs"} ${re}\\xD7${re} \\u2014 give it padding or a height`)}for(let u of x.filter(g=>L(g)==="image"||L(g)==="video")){let g=u.querySelector("img, video");if(!g||(g instanceof HTMLImageElement?g.complete&&g.naturalWidth===0:g.readyState<2)){let R=t.nodeAt(u.dataset.pwPath??"")?.src;i("image_missing",u.dataset.pwPath??"an image",`the ${L(u)} did not load${typeof R=="string"?` (${R})`:""}: it is drawn as an empty frame`)}}for(let u of x.filter(g=>L(g)==="package")){let g=[...u.querySelectorAll("[data-pw-type=text]")].filter(ce).map(R=>(R.textContent??"").replace(/\\s+/g," ").trim()).filter(R=>R!==""),E=(R,w)=>{let _=F=>` ${F.replace(/[^\\p{L}\\p{N}]+/gu," ").trim()} `,z=_(R);return z.trim().length>=3&&_(w).includes(z)};e:for(let R=0;R<g.length;R++)for(let w=0;w<g.length;w++)if(!(R===w||g[R].length>g[w].length)&&E(g[R],g[w])){i("repeated_text",u,`"${g[R]}" is said twice in one package: in "${g[R]}" and in "${g[w]}"`);break e}}return n}var de=(o,t,n)=>`M${o-n} ${t}a${n} ${n} 0 1 0 ${2*n} 0a${n} ${n} 0 1 0 ${-2*n} 0`,ze={ad_free:["M3 7h18v10H3z","M4 4l16 16"],bell:["M6 16v-5a6 6 0 0 1 12 0v5l2 2H4z","M10 21a2 2 0 0 0 4 0"],bolt:["M13 2L4 14h7l-1 8 9-12h-7z"],book:["M5 4h11a3 3 0 0 1 3 3v13H8a3 3 0 0 1-3-3z","M5 17a3 3 0 0 1 3-3h11"],chart:["M3 20h18","M6 20v-8","M11 20V5","M16 20v-6","M21 20V9"],chat:["M4 5h16v11H9l-5 4z"],check:["M5 12l5 5 9-10"],check_circle:[de(12,12,9),"M8 12l3 3 5-6"],clock:[de(12,12,9),"M12 7v5l3 2"],cloud:["M7 18a4 4 0 0 1-.5-7.97A6 6 0 0 1 18 9a4.5 4.5 0 0 1-.5 9z"],crown:["M3 8l4 4 5-7 5 7 4-4-2 11H5z"],download:["M12 4v11","M7 10l5 5 5-5","M4 20h16"],gift:["M3 9h18v4H3z","M5 13v8h14v-8","M12 9v12","M12 9C10 5 6 5 6 7.5S12 9 12 9zm0 0c2-4 6-4 6-1.5S12 9 12 9z"],globe:[de(12,12,9),"M3 12h18","M12 3a14 14 0 0 1 0 18a14 14 0 0 1 0-18z"],heart:["M12 20s-7-4.5-9-9a4.5 4.5 0 0 1 9-3 4.5 4.5 0 0 1 9 3c-2 4.5-9 9-9 9z"],infinity:["M7 9a3 3 0 1 0 0 6c3 0 7-6 10-6a3 3 0 1 1 0 6c-3 0-7-6-10-6z"],lock_open:["M5 11h14v10H5z","M8 11V7a4 4 0 0 1 7.5-2"],music:["M9 18V5l11-2v13",de(6,18,3),de(17,16,3)],people:[de(9,8,3.5),"M2 20a7 7 0 0 1 14 0","M16 4.5a3.5 3.5 0 0 1 0 7","M18 14a6 6 0 0 1 4 6"],person:[de(12,8,4),"M4 21a8 8 0 0 1 16 0"],photo:["M3 5h18v14H3z",de(8.5,9.5,1.5),"M3 17l5-5 4 4 3-3 6 6"],shield:["M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"],sparkles:["M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z","M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"],star:["M12 3l2.8 5.7 6.2.9-4.5 4.4 1 6.2L12 17.3l-5.5 2.9 1-6.2L3 9.6l6.2-.9z"],video:["M3 6h12v12H3z","M15 10l6-3v10l-6-3z"],arrow_right:["M4 12h15","M13 6l6 6-6 6"],calendar:["M4 6h16v15H4z","M4 10h16","M8 3v5","M16 3v5"],close:["M6 6l12 12","M18 6L6 18"],lock:["M5 11h14v10H5z","M8 11V7a4 4 0 0 1 8 0v4"],play:["M7 4l13 8-13 8z"],quote:["M5 17c3 0 4-2 4-5V7H4v5h3","M15 17c3 0 4-2 4-5V7h-5v5h3"],trophy:["M7 4h10v5a5 5 0 0 1-10 0z","M7 6H4v2a3 3 0 0 0 3 3","M17 6h3v2a3 3 0 0 1-3 3","M12 14v4","M8 21h8","M9 18h6v3H9z"]};var xt="http://www.w3.org/2000/svg";function Oe(o,t,n){let i=o.createElementNS(xt,"svg");i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("width",String(n)),i.setAttribute("height",String(n)),i.setAttribute("fill","none"),i.setAttribute("stroke","currentColor"),i.setAttribute("stroke-width","2"),i.setAttribute("stroke-linecap","round"),i.setAttribute("stroke-linejoin","round"),i.setAttribute("aria-hidden","true"),i.setAttribute("focusable","false");for(let d of t){let p=o.createElementNS(xt,"path");p.setAttribute("d",d),i.append(p)}return i}var b=(o,t=!1)=>({kind:"value",value:"plain",def:o,required:t}),U=o=>({kind:"value",value:"color",def:o}),Et=o=>({kind:"value",value:"spacing",def:o}),Kt=o=>({kind:"value",value:"radius",def:o}),ge=(o=!1)=>({kind:"text",required:o}),Xe=(o,t=!1)=>({kind:"list",item:o,required:t}),Re=(o,t=!1)=>({kind:"object",item:o,required:t}),St=(o=!1)=>({kind:"component",required:o}),Ge={kind:"components"},Mt={stack:{axis:b("vertical"),spacing:Et(0),align:b("center"),distribution:b("start"),scroll:b(!1),children:Ge},text:{text:ge(),spans:Xe("span"),font:b(),size:b(),weight:b(),line_height:b(),letter_spacing:b(),color:{kind:"value",value:"textColor",def:"text"},align:b("center"),max_lines:b(),uppercase:b(!1)},image:{src:b(void 0,!0),fit:b("fit"),aspect:b(),mask:Re("mask"),overlay:{kind:"value",value:"textColor"},alt:ge(),decorative:b(!1)},icon:{name:b(void 0,!0),color:U("accent"),size:b(24)},video:{src:b(void 0,!0),poster:b(),autoplay:b(!0),loop:b(!0),mute:b(!0),controls:b(!1),fit:b("fill"),aspect:b(),alt:ge()},button:{action:Re("action",!0),content:St(!0)},purchase_button:{content:St(!0),package:b()},package:{product:b(void 0,!0),plan:b(void 0,!0),selected:b(!1),children:Ge},tabs:{},switch:{},carousel:{pages:Ge,peek:b(0),spacing:b(8),loop:b(!1),auto_advance_ms:b(),indicator:Re("indicator"),height:b("")},countdown:{},timeline:{items:Xe("timeline_item",!0),line_color:U("accent"),line_width:b(2),icon_color:U("on_accent"),color:U("text"),spacing:b(16)},features:{items:Xe("feature",!0),icon_color:U("accent"),color:U("text"),spacing:b(12)},spacer:{size:Et()},divider:{color:U("muted"),thickness:b(1)},custom_html:{src:b(void 0,!0),height:b("",!0)}},ye={style:{padding:{kind:"edges"},margin:{kind:"edges"},width:b(""),height:b(""),max_width:b(),background:{kind:"fill"},border:Re("border"),corners:{kind:"corners"},shadow:Re("shadow"),opacity:b(1)},border:{width:b(),color:U("")},shadow:{color:U(""),radius:b(0),x:b(0),y:b(0)},linear:{type:b(""),angle:b(180),stops:b()},radial:{type:b(""),center_x:b(.5),center_y:b(.5),radius:b(.5),stops:b()},image_fill:{type:b(""),src:b(),fit:b("fill")},video_fill:{type:b(""),src:b(),poster:b(),loop:b(!0)},animation:{type:b(""),duration_ms:b(300),delay_ms:b(0),easing:b("ease_out")},action:{type:b(""),url:b(),sheet:b(),package:b()},mask:{shape:b(""),radius:Kt(0),depth:b(16)},span:{text:ge(!0),bold:b(!1),italic:b(!1),underline:b(!1),strikethrough:b(!1),color:U(""),link:b()},timeline_item:{icon:b(void 0,!0),title:ge(!0),text:ge(),color:U("")},feature:{icon:b(void 0,!0),text:ge(!0)},indicator:{style:b("dots"),color:U("muted"),active_color:U("accent"),size:b(8),spacing:b(6)},typography:{font:b("system"),size:b(),weight:b(400),line_height:b(),letter_spacing:b(0),text_style:b("body")}},Xt={linear:"linear",radial:"radial",image:"image_fill",video:"video_fill"},Gt=["top","bottom","leading","trailing"],Yt=["top_leading","top_trailing","bottom_leading","bottom_trailing"],xn=Object.keys(Mt),Qt=/\\{\\{\\s*([^{}]*?)\\s*\\}\\}/g,ke=(o,t)=>typeof o=="object"&&o!==null&&Object.hasOwn(o,t),P=o=>typeof o=="object"&&o!==null&&!Array.isArray(o)?o:{},te=o=>Array.isArray(o)?o:[],fe=(o,t)=>o??t;function Zt(o){let t=Math.floor(o/86400),n=Math.floor(o%86400/3600),i=Math.floor(o%3600/60),d=o%60,p=y=>String(y).padStart(2,"0");return{countdown:t>0?`${t}d ${p(n)}:${p(i)}:${p(d)}`:n>0?`${n}:${p(i)}:${p(d)}`:`${p(i)}:${p(d)}`,days:String(t),hours:p(n),minutes:p(i),seconds:p(d)}}function Ne(o,t){return new Qe(o,t).resolve()}var Qe=class{d;opts;lang;texts={};palette;typography={};radii;spacing;packages;selected="";tab="";paths=new WeakMap;tabsShown={};switchesOn={};countdowns={};constructor(t,n){this.d=t,this.opts=n,this.packages=n.packages;let i=P(this.d.locales),d=String(this.d.default_locale??""),p=n.locale;ke(i,p)||(p=p.split("-")[0]??"",ke(i,p)||(p=d)),this.lang=p;for(let y of[d,p])for(let[m,k]of Object.entries(P(i[y])))typeof k=="string"&&(this.texts[m]=k);let h=P(this.d.theme);this.palette=P(P(h.colors)[n.appearance]);for(let[y,m]of Object.entries(P(h.typography))){let k={};for(let[v,x]of Object.entries(ye.typography)){let L=fe(P(m)[v],x.def);L!=null&&(k[v]=L)}this.typography[y]=k}this.radii=P(h.radii),this.spacing=P(h.spacing)}resolve(){let[t,n]=this.initialState();this.tab=t,this.selected=n;let i={selected:n,theme:{colors:this.palette,typography:this.typography,radii:this.radii,spacing:this.spacing}};for(let p of["header","root","footer"]){let h=this.d[p];if(typeof h=="object"&&h!==null){let y=this.component(P(h),p,"",!1);y&&(i[p]=y)}}let d={};if(typeof this.d.sheets=="object"&&this.d.sheets!==null){let p={};for(let[h,y]of Object.entries(P(this.d.sheets))){let m=this.component(P(P(y).root),`sheets.${h}.root`,"",!1);m&&(p[h]=m);let k=this.text(P(y).title,this.packages[this.selected]);k!==null&&(d[h]=k)}i.sheets=p}return{tree:i,pathOf:p=>this.paths.get(p),tabs:this.tabsShown,switches:this.switchesOn,sheetTitles:d,countdowns:this.countdowns,language:this.lang}}switchOn(t,n){return this.opts.switches&&Object.hasOwn(this.opts.switches,n)?this.opts.switches[n]:this.opts.switch?this.opts.switch==="on":t.default===!0}tabShown(t,n){let d=te(t.tabs).map(P).map(h=>String(h.id)),p=this.opts.tabs?.[n];return p!==void 0&&d.includes(p)?p:this.opts.tab?this.opts.tab:typeof t.default=="string"&&t.default!==""?t.default:d[0]??""}initialState(){let t="",n="",i="",d="",p=(y,m)=>{if(Array.isArray(y)){y.forEach((v,x)=>p(v,`${m}[${x}]`));return}if(typeof y!="object"||y===null)return;let k=y;switch(k.type){case"package":{let v=`${k.product}.${k.plan}`;i===""&&(i=v),k.selected===!0&&n===""&&(n=v);break}case"switch":{let v=this.switchOn(k,m),x=v?k.on_package:k.off_package;typeof x=="string"&&d===""&&(d=x),p(v?k.on:k.off,`${m}.${v?"on":"off"}`);return}case"tabs":{let v=this.tabShown(k,m);t===""&&(t=v),te(k.tabs).forEach((x,L)=>{let A=P(x);A.id===v&&(typeof A.package=="string"&&d===""&&(d=A.package),p(A.content,`${m}.tabs[${L}].content`))});return}}for(let v of Object.keys(k).sort())v!=="sheets"&&p(k[v],m===""?v:`${m}.${v}`)};for(let y of["header","root","footer"])p(this.d[y],y);let h=this.opts.selected||d||n||i;return[t,h]}holds(t,n){let i=n||this.selected;for(let[d,p]of Object.entries(t))switch(d){case"selected_package":if(!te(p).includes(this.selected))return!1;break;case"intro_eligible":if((this.opts.eligible??[]).includes(i)!==p)return!1;break;case"platform":if(!te(p).includes(this.opts.platform))return!1;break;case"locale":if(!te(p).includes(this.opts.locale)&&!te(p).includes(this.lang))return!1;break;case"appearance":if(p!==this.opts.appearance)return!1;break;case"tab":if(p!==this.tab)return!1;break}return!0}component(t,n,i,d){if(typeof t.visible=="object"&&t.visible!==null&&!this.holds(P(t.visible),i))return null;let p=String(t.type),h=i,y=d;p==="package"&&(h=`${t.product}.${t.plan}`,y=h===this.selected);let m={type:p};this.paths.set(m,n),ke(t,"id")&&(m.id=t.id);let k={...P(t.style)};y&&Object.assign(k,P(P(t.states).selected)),Object.keys(k).length>0&&(m.style=this.style(k)),typeof t.animation=="object"&&t.animation!==null&&(m.animation=this.defaults(P(t.animation),ye.animation));let v=this.packages[h||this.selected];switch(p){case"tabs":return this.tabs(t,m,n,h);case"switch":return this.switchOf(t,m,n,h,y);case"countdown":return this.countdown(t,m,n,h,y);case"purchase_button":{let x=typeof t.package=="string"?t.package:this.selected;m.package=x;let L=this.component(P(t.content),`${n}.content`,x,y);return L?(m.content=L,m):null}case"package":m.product=t.product,m.plan=t.plan,m.selected=y}for(let[x,L]of Object.entries(Mt[p]??{})){if(p==="package"&&(x==="product"||x==="plan"||x==="selected"))continue;let A=t[x],C=ke(t,x);switch(L.kind){case"component":{if(!C)continue;let M=this.component(P(A),`${n}.${x}`,h,y);if(!M&&L.required)return null;M&&(m[x]=M);break}case"components":{let M=[];te(A).forEach((j,J)=>{let Q=this.component(P(j),`${n}.${x}[${J}]`,h,y);Q&&M.push(Q)}),m[x]=M;break}case"text":{let M=this.text(A,v);if(M!==null)m[x]=M;else if(L.required||p==="text"&&x==="text"&&C)return null;break}case"list":{let M=[];for(let j of te(A)){let J=this.item(L.item,P(j),v);J&&M.push(J)}if(p==="text"&&M.length===0&&C)return null;C&&(m[x]=M);break}case"object":{C?m[x]=this.object(P(A),L.item,v):x==="indicator"&&(m[x]=this.object({},L.item,v));break}case"value":{let M=this.value(fe(A,L.def),L);M!=null&&M!==""&&(m[x]=M);break}}}return p==="text"&&this.typeStyle(m),m}typeStyle(t){if(typeof t.size!="string"){t.size!==void 0&&t.size!==null&&(t.text_style="body");return}let n=this.typography[t.size];for(let i of["font","weight","line_height","letter_spacing","text_style"])!ke(t,i)&&n?.[i]!==void 0&&n?.[i]!==null&&(t[i]=n[i]);t.size=n?.size??null}tabs(t,n,i,d){let p=this.tabShown(t,i);this.tabsShown[i]=p;let h=[];return te(t.tabs).forEach((y,m)=>{let k=P(y),v=k.id===p,x=this.component(P(k.label),`${i}.tabs[${m}].label`,d,v);if(h.push({id:k.id,selected:v,label:x}),v){let L=this.component(P(k.content),`${i}.tabs[${m}].content`,d,!1);L&&(n.content=L)}}),n.tabs=h,typeof t.bar=="object"&&t.bar!==null&&(n.bar=this.style(P(t.bar))),n}switchOf(t,n,i,d,p){let h=this.switchOn(t,i);this.switchesOn[i]=h,n.on=h;let y=this.text(t.label,this.packages[d||this.selected]);y!==null&&(n.label=y),n.tint=this.color(String(fe(t.tint,"accent")));let m=h?"on":"off",k=this.component(P(t[m]),`${i}.${m}`,d,p);return k&&(n.content=k),n}countdown(t,n,i,d,p){let h=P(t.target),y=this.opts.now??Date.now(),m;if(typeof h.after_open_seconds=="number"?m=Math.trunc(h.after_open_seconds)-(this.opts.openedSecondsAgo??0):m=Math.trunc((Date.parse(String(h.at))-y)/1e3),m>0){n.state="counting",n.remaining_seconds=m,this.countdowns[i]=m;let k=this.packages,v={};for(let[L,A]of Object.entries(k))v[L]={...A,...Zt(m)};this.packages=v;let x=this.component(P(t.counting),`${i}.counting`,d,p);return x&&(n.content=x),this.packages=k,n}if(n.state="ended",n.remaining_seconds=0,typeof t.ended=="object"&&t.ended!==null){let k=this.component(P(t.ended),`${i}.ended`,d,p);k&&(n.content=k)}return n}item(t,n,i){let d={};for(let[p,h]of Object.entries(ye[t]??{})){let y=n[p];if(h.kind==="text"){let m=this.text(y,i);if(m!==null)d[p]=m;else if(h.required)return null}else{let m=h.def,k=this.value(fe(y,m),h);k!=null&&(ke(n,p)||m!=null&&m!=="")&&(d[p]=k)}}return d}object(t,n,i){return n==="style"?this.style(t):this.item(n,t,i)??{}}defaults(t,n){let i={};for(let[d,p]of Object.entries(n)){let h=fe(t[d],p.def);h!=null&&h!==""&&(i[d]=h)}return i}style(t){let n={};for(let[i,d]of Object.entries(t)){let p=ye.style[i];if(!p){n[i]=d;continue}switch(p.kind){case"edges":n[i]=this.sides(d,Gt,this.spacing);break;case"corners":n[i]=this.sides(d,Yt,this.radii);break;case"fill":n[i]=this.fill(d);break;case"object":{let h=P(d),y={};for(let[m,k]of Object.entries(ye[p.item]??{})){let v=fe(h[m],k.def);v!=null&&v!==""&&(y[m]=this.value(v,k))}n[i]=y;break}default:n[i]=d}}return n}sides(t,n,i){let d={};for(let p of n){let h=typeof t=="object"&&t!==null?fe(P(t)[p],0):t;d[p]=Ye(h,i)}return d}fill(t){if(typeof t=="string")return{type:"color",color:this.color(t)};let n=P(t),i=this.defaults(n,ye[Xt[String(n.type)]??""]??{});return Array.isArray(n.stops)&&(i.stops=n.stops.map(d=>({color:this.color(String(P(d).color)),at:P(d).at}))),i}value(t,n){if(n.kind!=="value")return t;switch(n.value){case"color":return typeof t=="string"&&t!==""?this.color(t):t;case"textColor":return typeof t=="string"?this.color(t):t==null?t:this.fill(t);case"spacing":return Ye(t,this.spacing);case"radius":return Ye(t,this.radii);default:return t}}color(t){let n=Object.hasOwn(this.palette,t)?this.palette[t]:void 0;return typeof n=="string"?n:t}text(t,n){let i;if(typeof t=="string")i=t;else if(typeof t=="object"&&t!==null&&!Array.isArray(t)){let p=t.t;i=typeof p=="string"&&Object.hasOwn(this.texts,p)?this.texts[p]:""}else return null;let d=!1;return i=i.replace(Qt,(p,h)=>{let y=n&&Object.hasOwn(n,h)?n[h]:void 0;return typeof y!="string"||y===""?(d=!0,""):y}),d||i.trim()===""?null:i}};function Ye(o,t){return typeof o=="string"?Object.hasOwn(t,o)?t[o]:null:o}var en={packages:"Choose a plan",close:"Close",restore:"Restore purchases",page:(o,t)=>`${o} of ${t}`},ve={system:\'-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif\',rounded:\'ui-rounded, "SF Pro Rounded", Nunito, "Varela Round", system-ui, -apple-system, sans-serif\',serif:\'ui-serif, "New York", "Iowan Old Style", Georgia, "Times New Roman", serif\',mono:\'ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace\'},tn=1.35,Rt=20,nn=new Set(["button","purchase_button","package","text"]),on=new Set(["ar","he","fa","ur","ps","yi","dv","ug","ku","sd"]),At={ease_out:"cubic-bezier(0.16, 1, 0.3, 1)",ease_in_out:"cubic-bezier(0.65, 0, 0.35, 1)",spring:"cubic-bezier(0.34, 1.56, 0.64, 1)"},sn={start:"flex-start",center:"center",end:"flex-end",stretch:"stretch"},rn={start:"flex-start",center:"center",end:"flex-end",space_between:"space-between",space_around:"space-around",space_evenly:"space-evenly"},an={stack:["children"],package:["children"],button:["content"],purchase_button:["content"],tabs:["tabs","content"],switch:["content"],carousel:["pages"],countdown:["content"]},ln={package:["selected"],purchase_button:["package"],switch:["on"],countdown:["state","remaining_seconds"]},cn=`\n:host{display:block;height:100%}\n.pw2{position:relative;box-sizing:border-box;display:flex;flex-direction:column;height:100%;overflow:hidden;font-family:${ve.system};font-size:1rem;line-height:1.3;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;text-rendering:optimizeLegibility}\n.pw2 *,.pw2 *::before,.pw2 *::after{box-sizing:border-box}\n.pw2-head,.pw2-foot{flex:none;display:flex;flex-direction:column;position:relative;z-index:1}\n.pw2-head[hidden],.pw2-foot[hidden]{display:none}\n.pw2-foot{max-height:30%;overflow-y:auto;scrollbar-width:none}\n.pw2-foot .pw-text{font-size:min(var(--pw-fs,1rem),var(--pw-fs-max,21.6px))}\n.pw2.pw2-large{overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}\n.pw2.pw2-large>.pw2-body{flex:1 0 auto;overflow:visible;min-height:auto}\n.pw2.pw2-large>.pw2-foot{position:sticky;bottom:0}\n.pw2{padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}\n.pw2-head,.pw2-body.pw2-top{padding-top:env(safe-area-inset-top,0px)}\n.pw2-foot,.pw2-body.pw2-bottom{padding-bottom:env(safe-area-inset-bottom,0px)}\n.pw2-body{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column;overscroll-behavior:contain;scrollbar-width:none}\n.pw2-body::-webkit-scrollbar{display:none}\n.pw2-body>.pw-c{flex:1 0 auto}\n.pw-c{position:relative;min-width:0}\n.pw-c[style*="--pw-bw"]{isolation:isolate}\n.pw-c[style*="--pw-bw"]::after{content:"";position:absolute;inset:0;border:var(--pw-bw) solid var(--pw-bc);border-radius:inherit;pointer-events:none;z-index:2}\n.pw-stack{display:flex;flex-direction:column}\n.pw-stack.h{flex-direction:row}\n.pw-stack.z{display:grid}\n.pw-stack.z>.pw-c{grid-area:1/1}\n.pw-stack.scroll{overflow-y:auto;scrollbar-width:none}\n.pw-stack.h.scroll{overflow-x:auto;overflow-y:hidden}\n.pw-stack.scroll>.pw-c{flex-shrink:0}\n.pw-text{margin:0;overflow-wrap:break-word;white-space:pre-line}\n/* A text beside others wraps between its words, never inside one: a price stays whole, what is beside it wraps. */\n.pw-stack.h>.pw-text{min-width:min-content}\n.pw-text a{color:inherit}\n.pw-clamp{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden}\n.pw-grad{-webkit-background-clip:text;background-clip:text;color:transparent!important;-webkit-text-fill-color:transparent}\n.pw-icon{display:inline-flex;flex:none}\n.pw-icon svg{display:block}\n.pw-img{overflow:hidden;flex:none}\n.pw-img img,.pw-img video{display:block;width:100%;height:100%}\n.pw-img.natural img{height:auto}\n.pw-overlay{position:absolute;inset:0;pointer-events:none}\n.pw-bgvideo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-1;pointer-events:none}\nbutton.pw-c{font:inherit;color:inherit;background:none;border:0;margin:0;padding:0;cursor:pointer;text-align:inherit;display:flex;flex-direction:column;align-items:stretch;justify-content:center;-webkit-tap-highlight-color:transparent;transition:opacity .15s ease,transform .15s ease}\nbutton.pw-c:active{transform:scale(.98)}\nbutton.pw-c:disabled{cursor:default;opacity:.5}\n.pw-pkg{cursor:pointer;display:flex;flex-direction:column;transition:background-color .2s ease,border-color .2s ease,box-shadow .2s ease}\n.pw-pkg[aria-disabled=true]{cursor:default;opacity:.55}\n:focus-visible{outline:2px solid currentColor;outline-offset:2px}\n.pw-features{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}\n.pw-feature{display:flex;align-items:center;gap:12px;text-align:start}\n.pw-timeline{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}\n.pw-step{display:grid;grid-template-columns:auto 1fr;column-gap:14px;position:relative;text-align:start}\n.pw-dot{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;position:relative;z-index:1}\n.pw-line{position:absolute;left:calc(16px - var(--pw-line-w)/2);top:32px;width:var(--pw-line-w);bottom:calc(-1 * var(--pw-gap))}\n.pw-step:last-child .pw-line{display:none}\n.pw-step-title{font-weight:600}\n.pw-step-text{opacity:.72;font-size:.9375rem;margin-top:2px}\n.pw-tabs{display:flex;flex-direction:column;gap:12px}\n.pw-tablist{display:flex}\n.pw-tab{flex:1 1 0;min-width:0;font:inherit;color:inherit;background:none;border:0;margin:0;padding:0;cursor:pointer;display:flex}\n.pw-tab>.pw-c{flex:1 1 auto}\n.pw-switch{display:flex;flex-direction:column;gap:12px}\n.pw-switchcontent{display:flex;flex-direction:column}\n.pw-switchrow{display:flex;align-items:center;justify-content:space-between;gap:12px;text-align:start}\n.pw-toggle{flex:none;width:51px;height:31px;border-radius:999px;border:0;padding:2px;cursor:pointer;background:var(--pw-off);transition:background-color .2s ease}\n.pw-toggle[aria-checked=true]{background:var(--pw-tint)}\n.pw-toggle i{display:block;width:27px;height:27px;border-radius:50%;background:#fff;box-shadow:0 2px 4px #0003;transition:transform .2s ease}\n.pw-toggle[aria-checked=true] i{transform:translateX(20px)}\n.pw-carousel{display:flex;flex-direction:column;gap:12px;min-width:0}\n.pw-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;scroll-behavior:smooth;padding-inline:var(--pw-peek);scroll-padding-inline:var(--pw-peek);gap:var(--pw-gap)}\n.pw-track::-webkit-scrollbar{display:none}\n.pw-track>.pw-c{flex:none;width:100%;scroll-snap-align:center}\n.pw-dots{display:flex;justify-content:center;gap:var(--pw-dot-gap)}\n.pw-dots button{border:0;padding:0;cursor:pointer;width:var(--pw-dot);height:var(--pw-dot);border-radius:999px;background:var(--pw-dot-c);transition:width .25s ease,background-color .25s ease}\n.pw-dots.bars button{width:calc(var(--pw-dot) * 3)}\n.pw-dots button[aria-current=true]{background:var(--pw-dot-on)}\n.pw-dots.dots button[aria-current=true]{width:calc(var(--pw-dot) * 2.5)}\n.pw-html{display:block;width:100%;border:0;pointer-events:none;background:transparent}\n.pw-spacer{flex:1 1 0}\n.pw-stack:not(.h):not(.z)>.greedy-h,.pw-pkg>.greedy-h,button.pw-c>.greedy-h{align-self:stretch}\n.pw-stack.h>.greedy-h{flex:1 1 0}\n.pw-stack.z>.greedy-h{justify-self:stretch}\n.pw-stack:not(.h):not(.z)>.greedy-v,.pw-pkg>.greedy-v{flex:1 1 auto}\n.pw-stack.h>.greedy-v,.pw-stack.z>.greedy-v{align-self:stretch}\n.pw-layer{position:absolute;inset:0;z-index:5;display:flex;flex-direction:column;justify-content:flex-end;background:#0007}\n.pw-sheet{max-height:92%;overflow-y:auto;border-radius:16px 16px 0 0;display:flex;flex-direction:column;outline:none;padding-bottom:env(safe-area-inset-bottom,0px)}\n.pw-sheet>.pw-c{flex:1 0 auto}\n.pw-grab{width:36px;height:5px;border-radius:3px;background:currentColor;opacity:.25;margin:8px auto 0;flex:none}\n[data-pw-highlight]{outline:2px solid #2E7CF6!important;outline-offset:-1px}\n[data-pw-hover]:not([data-pw-highlight]){outline:1px dashed #2E7CF6aa!important;outline-offset:-1px}\n.pw-editing button,.pw-editing .pw-pkg,.pw-editing a{cursor:default}\n@media (prefers-reduced-motion:reduce){.pw2 *{transition:none!important;scroll-behavior:auto!important}}\n`,Fe=o=>{if(typeof o!="string"||o.length>2048)return!1;try{let t=new URL(o);return t.protocol==="https:"&&t.hostname!==""&&t.username===""&&t.password===""}catch{return!1}},dn=o=>`${String(o.product)}.${String(o.plan)}`,Ze=o=>{let[t,n]=o.split(".");return{product:t??"",plan:n??""}},T=o=>`${Number(o)||0}px`,Lt=o=>`${o/16}rem`,pn=0;function _t(o,t,n){let i=o.ownerDocument,d=i.defaultView,p=`pw${++pn}`,h=i.createElement("div");h.setAttribute("data-palbase-paywall",""),h.style.display="block",h.style.height="100%";let y=h.attachShadow({mode:"open"});un(i,y,cn);let m=i.createElement("div");m.className="pw2";let k=i.createElement("div");k.className="pw2-head";let v=i.createElement("div");v.className="pw2-body";let x=i.createElement("div");x.className="pw2-foot";let L=i.createElement("div");m.append(k,v,x),y.append(m),o.replaceChildren(h);let A={...t},C={openedAt:Date.now(),...t.state},M,j=new Map,J=new Map,Q=!1,ne=null,xe,Ee=!1,he=i.defaultView?.ResizeObserver,Ae=typeof he=="function"?new he(()=>{m.classList.toggle("pw2-large",we()>=Rt),re()}):null;Ae?.observe(m);let q=()=>re();i.fonts?.addEventListener?.("loadingdone",q);let X=null,me=d?.matchMedia?.("(prefers-reduced-motion: reduce)"),oe=()=>A.reduceMotion??me?.matches??!1,B=()=>!!n.editor,be={...en,...n.labels};function se(){let e=C.now??Date.now();M=Ne(A.definition,{locale:A.locale,appearance:A.appearance,platform:A.platform??"web",packages:A.packages,eligible:A.eligible??[],...C.selected?{selected:C.selected}:{},tabs:C.tabs??{},switches:C.switches??{},openedSecondsAgo:Math.max(0,Math.floor((e-(C.openedAt??e))/1e3)),now:e});let l=M.tree.theme.colors;m.style.background=l.background??"",m.style.color=l.text??"",m.classList.toggle("pw-editing",B()),m.classList.toggle("pw2-large",we()>=Rt),m.lang=M.language,m.dir=on.has(M.language.split("-")[0]??"")?"rtl":"ltr",J=new Map,u(k,M.tree.header,"vertical"),u(v,M.tree.root,"vertical"),u(x,M.tree.footer,"vertical"),k.hidden=!M.tree.header,x.hidden=!M.tree.footer,v.classList.toggle("pw2-top",!M.tree.header),v.classList.toggle("pw2-bottom",!M.tree.footer),Ft();for(let[r,s]of j)J.has(r)||s.cleanup.forEach(a=>a());j=J,ne&&at(ne);for(let r of j.values())r.type==="custom_html"&&it(r.el);re(),clearTimeout(xe),Ee||=h.isConnected,C.now===void 0&&Object.keys(M.countdowns).length>0&&(xe=setTimeout(()=>{Ee&&!h.isConnected||se()},1e3-Date.now()%1e3+5))}function we(){let e=i.documentElement,l=e&&d?parseFloat(d.getComputedStyle(e).fontSize):NaN;return Number.isFinite(l)?l:16}function re(){let e=[];for(let r of j.values()){if(r.type!=="text")continue;let s=r.el,a=s.dataset.pwFit??s.style.fontSize;if(!a||(s.style.fontSize=a,delete s.dataset.pwFit,!/^\\S+$/.test(s.textContent??"")))continue;let c=s.parentElement,f=c&&d?d.getComputedStyle(c):null,S=c&&f?c.clientWidth-parseFloat(f.paddingLeft||"0")-parseFloat(f.paddingRight||"0"):1/0,$=Math.min(s.clientWidth,S);e.push({e:s,base:a,k:$<=0||s.scrollWidth<=$+.5?1:Math.max(.6,$/s.scrollWidth)})}let l=r=>r.closest(".pw-stack.h");for(let r of e){let s=Math.min(r.k,...e.filter(a=>a!==r&&a.base===r.base&&l(a.e)&&l(a.e)===l(r.e)).map(a=>a.k));s>=1||(r.e.dataset.pwFit=r.base,r.e.style.fontSize=`calc(${r.base} * ${s.toFixed(3)})`)}}function u(e,l,r){let s=l?g(l,r,{inControl:!1,inSheet:!1,inFooter:e===x}):null;ae(e,s?[s]:[])}function g(e,l,r){let s=M.pathOf(e)??"",a=E(e),c=j.get(s),f;if(c&&c.type===e.type&&c.key===a&&c.axis===l&&!J.has(s))f=c;else{let $=_(e,s,l,r);if(!$)return null;f={el:$.el,type:e.type,key:a,styleKey:"",axis:l,slots:$.slots,cleanup:$.cleanup??[]},f.el.classList.add("pw-c"),f.el.dataset.pwPath=s,f.el.dataset.pwType=e.type,e.id&&(f.el.dataset.pwId=e.id),c&&c.cleanup.forEach(O=>O()),Ct(f.el,e)}let S=`${JSON.stringify(e.style??null)}|${l}`;return f.styleKey!==S&&(et(f.el,e,l),f.styleKey=S),J.set(s,f),$t(f,e),Pt(f,e,r),f.el}function E(e){let l=new Set(["style",...an[e.type]??[],...ln[e.type]??[]]),r={};for(let[s,a]of Object.entries(e))l.has(s)||(r[s]=a);return e.type==="tabs"&&(r.tabs=e.tabs.map(s=>s.id)),e.type==="carousel"&&(r.pages=e.pages.length),e.type==="stack"&&(r.greedy=e.children.some(s=>s.type==="spacer"&&typeof s.size!="number")),(e.type==="image"||e.type==="video"||e.type==="custom_html")&&(r.asset=R(e)),JSON.stringify(r)}function R(e){let l=Se(e.src);return l?(A.assets?.[l]?.url??A.assets?.[l]?.html?.length??"none").toString():""}function w(e,l){let r=i.createElement(e);return l&&(r.className=l),r}function _(e,l,r,s){let a=e;switch(e.type){case"stack":{let c=w("div",`pw-stack${a.axis==="horizontal"?" h":a.axis==="z"?" z":""}${a.scroll?" scroll":""}`);return a.axis!=="z"&&a.children.some(f=>f.type==="spacer"&&typeof f.size!="number")&&c.classList.add(a.axis==="horizontal"?"greedy-h":"greedy-v"),c.style.gap=T(a.spacing),a.axis==="z"?c.style.placeItems=a.align==="stretch"?"stretch":String(a.align??"center"):(c.style.alignItems=sn[String(a.align)]??"center",c.style.justifyContent=rn[String(a.distribution)]??"flex-start"),{el:c,slots:{children:c}}}case"text":return{el:z(a,s),slots:{}};case"icon":{let c=ze[String(a.name)];if(!c)return null;let f=w("span","pw-icon");return f.style.color=String(a.color??""),f.append(Oe(i,c,Number(a.size)||24)),f.setAttribute("aria-hidden","true"),{el:f,slots:{}}}case"image":return Z(a);case"video":return ie(a);case"features":{let c=w("ul","pw-features");c.style.gap=T(a.spacing);for(let f of a.items??[]){let S=w("li","pw-feature"),$=ze[f.icon];if($){let I=Oe(i,$,22);I.style.color=String(a.icon_color),I.style.flex="none",S.append(I)}let O=w("span");O.textContent=f.text,O.style.color=String(a.color),S.append(O),c.append(S)}return{el:c,slots:{}}}case"timeline":{let c=w("ol","pw-timeline");c.style.gap=T(a.spacing),c.style.setProperty("--pw-gap",T(a.spacing)),c.style.setProperty("--pw-line-w",T(a.line_width));for(let f of a.items??[]){let S=w("li","pw-step"),$=w("span","pw-dot");$.style.background=f.color??String(a.line_color),$.style.color=String(a.icon_color);let O=ze[f.icon];O&&$.append(Oe(i,O,18)),$.setAttribute("aria-hidden","true");let I=w("span","pw-line");I.style.background=String(a.line_color),I.setAttribute("aria-hidden","true");let K=w("span");K.style.color=String(a.color);let le=w("span","pw-step-title");if(le.textContent=f.title,K.append(le),f.text){let N=w("span","pw-step-text");N.style.display="block",N.textContent=f.text,K.append(N)}S.append($,K,I),c.append(S)}return{el:c,slots:{}}}case"spacer":{let c=w("div","pw-spacer");return c.setAttribute("aria-hidden","true"),typeof a.size=="number"&&(c.style.flex="none",r==="horizontal"?c.style.width=T(a.size):c.style.height=T(a.size)),{el:c,slots:{}}}case"divider":{let c=w("div","pw-divider");return c.setAttribute("role","separator"),c.style.background=String(a.color),c.style.alignSelf="stretch",c.style.flex="none",r==="horizontal"?(c.setAttribute("aria-orientation","vertical"),c.style.width=T(a.thickness)):c.style.height=T(a.thickness),{el:c,slots:{}}}case"button":return pe(a,s);case"purchase_button":{let c=w("button","pw-buy");return c.type="button",c.addEventListener("click",()=>{st(c,()=>Nt(c.dataset.package??""))}),{el:c,slots:{content:c}}}case"package":{let c=w("div","pw-pkg");return c.setAttribute("role","radio"),c.addEventListener("click",()=>Ve(c)),c.addEventListener("keydown",f=>Ht(f,c)),{el:c,slots:{children:c}}}case"tabs":return Le(a,l);case"switch":return Ie(a,l);case"carousel":return _e(a);case"countdown":{let c=w("div","pw-countdown");return c.setAttribute("role","timer"),c.setAttribute("aria-live","off"),{el:c,slots:{content:c}}}case"custom_html":{let c=A.assets?.[Se(a.src)??""];if(!c?.html)return null;let f=w("iframe","pw-html");return f.setAttribute("sandbox","allow-scripts"),f.setAttribute("aria-hidden","true"),f.setAttribute("title",""),f.tabIndex=-1,f.srcdoc=c.html,typeof a.height=="number"?f.style.height=T(a.height):typeof a.height=="string"&&a.height.endsWith("%")?f.style.height=a.height:a.height==="fill"&&(f.style.flex="1 1 0"),f.addEventListener("load",()=>it(f)),{el:f,slots:{}}}default:return null}}function z(e,l){let r=String(e.text_style??"body"),s=l.inControl?"span":r==="large_title"?"h1":r==="title"||r==="title2"?"h2":r==="title3"?"h3":"p",a=w(s,"pw-text");l.inControl&&(a.style.display="block"),a.style.fontFamily=F(e.font);let c=typeof e.size=="number"?e.size:16;if(l.inFooter?(a.style.setProperty("--pw-fs",Lt(c)),a.style.setProperty("--pw-fs-max",`${Number((c*tn).toFixed(2))}px`)):a.style.fontSize=Lt(c),typeof e.weight=="number"&&(a.style.fontWeight=String(e.weight)),typeof e.line_height=="number"&&(a.style.lineHeight=String(e.line_height)),typeof e.letter_spacing=="number"&&e.letter_spacing!==0&&(a.style.letterSpacing=T(e.letter_spacing)),a.style.textAlign=String(e.align??"center"),e.uppercase===!0&&(a.style.textTransform="uppercase"),typeof e.max_lines=="number"&&(a.classList.add("pw-clamp"),a.style.setProperty("-webkit-line-clamp",String(e.max_lines))),typeof e.color=="string"?a.style.color=e.color:e.color&&typeof e.color=="object"&&(a.classList.add("pw-grad"),a.style.backgroundImage=Be(e.color)??""),Array.isArray(e.spans))for(let f of e.spans){let S=f.link==="terms"?A.links?.terms:f.link==="privacy"?A.links?.privacy:f.link,$=Fe(S)&&!l.inControl,O=w($?"a":"span");O.textContent=String(f.text),f.bold&&(O.style.fontWeight="700"),f.italic&&(O.style.fontStyle="italic");let I=[f.underline?"underline":"",f.strikethrough?"line-through":""].filter(Boolean).join(" ");I&&(O.style.textDecoration=I),typeof f.color=="string"&&f.color!==""&&(O.style.color=f.color),$&&Fe(S)&&(O.href=S,O.target="_blank",O.rel="noopener noreferrer",O.addEventListener("click",K=>{B()||!n.onLink||(K.preventDefault(),n.onLink(S))})),a.append(O)}else a.textContent=String(e.text??"");return a}function F(e){if(typeof e!="string")return ve.system;let l=Se(e);if(l){let r=A.assets?.[l]?.family;return r?`"${r}", ${ve.system}`:ve.system}return ve[e]??ve.system}function D(e){let l=Se(e);return l?A.assets?.[l]?.url||void 0:Fe(e)?e:void 0}function Z(e){let l=w("div","pw-img");typeof e.aspect=="number"&&(l.style.aspectRatio=String(e.aspect));let r=D(e.src),s=e.mask;if(s?.shape==="circle")l.style.borderRadius="50%";else if(s?.shape==="rect"&&s.radius)l.style.borderRadius=T(s.radius);else if(s?.shape==="convex")l.style.setProperty("border-end-start-radius",`50% ${T(s.depth)}`),l.style.setProperty("border-end-end-radius",`50% ${T(s.depth)}`);else if(s?.shape==="concave"){let a=`radial-gradient(ellipse 50% ${T(s.depth)} at 50% 100%, transparent 98%, #000 100%)`;l.style.setProperty("-webkit-mask-image",a),l.style.setProperty("mask-image",a)}if(r){let a=w("img");a.src=r,a.decoding="async",a.style.objectFit=e.fit==="fill"?"cover":"contain",e.decorative===!0?(a.alt="",l.setAttribute("aria-hidden","true")):a.alt=String(e.alt??""),a.addEventListener("error",()=>a.style.visibility="hidden"),l.append(a)}if(e.overlay&&typeof e.overlay=="object"){let a=w("div","pw-overlay");a.style.backgroundImage=Be(e.overlay)??"",l.append(a)}return{el:l,slots:{}}}function ie(e){let l=w("div","pw-img");typeof e.aspect=="number"&&(l.style.aspectRatio=String(e.aspect));let r=D(e.src);if(r){let s=w("video");s.src=r;let a=D(e.poster);a&&(s.poster=a),s.muted=e.mute!==!1,s.loop=e.loop!==!1,s.playsInline=!0,s.controls=e.controls===!0,s.preload="auto",s.style.objectFit=e.fit==="fit"?"contain":"cover",s.autoplay=e.autoplay!==!1&&!oe()&&!B(),typeof e.alt=="string"?s.setAttribute("aria-label",e.alt):s.setAttribute("aria-hidden","true"),l.append(s)}return{el:l,slots:{}}}function pe(e,l){let r=e.action??{},s,a=r.type==="restore"||r.type==="offer_code";switch(r.type){case"close":l.inSheet?s=()=>Te(null):n.onClose&&(s=()=>n.onClose());break;case"restore":n.onRestore&&(s=()=>n.onRestore());break;case"offer_code":n.onOfferCode&&(A.platform??"web")!=="web"&&(s=()=>n.onOfferCode());break;case"open_url":case"terms":case"privacy":{let f=r.type==="open_url"?r.url:r.type==="terms"?A.links?.terms:A.links?.privacy;Fe(f)&&(s=()=>n.onLink?n.onLink(f):void d?.open(f,"_blank","noopener,noreferrer"));break}case"sheet":r.sheet&&(s=()=>Te(r.sheet));break;case"select_package":r.package&&(s=()=>Je(r.package));break}if(!s&&!B())return null;let c=w("button");return c.type="button",r.type==="close"&&c.setAttribute("aria-label",be.close),c.addEventListener("click",()=>{!s||B()||(a?st(c,s):s())}),{el:c,slots:{content:c}}}function Le(e,l){let r=w("div","pw-tabs"),s=w("div","pw-tablist");s.setAttribute("role","tablist");let a=w("div","pw-tabpanel");a.setAttribute("role","tabpanel"),a.id=`${p}-${l}-panel`;let c={content:a},f=e.tabs.map(S=>S.id);return f.forEach((S,$)=>{let O=w("button","pw-tab");O.type="button",O.setAttribute("role","tab"),O.id=`${p}-${l}-tab-${$}`,O.setAttribute("aria-controls",a.id),O.dataset.tab=S,O.addEventListener("click",()=>nt(l,S)),O.addEventListener("keydown",I=>{let K=I.key==="ArrowRight"?$+1:I.key==="ArrowLeft"?$-1:I.key==="Home"?0:I.key==="End"?f.length-1:-2;if(K===-2)return;I.preventDefault();let le=f[(K+f.length)%f.length];nt(l,le),s.querySelector(`[data-tab="${le}"]`)?.focus()}),s.append(O),c[`label:${S}`]=O}),r.append(s,a),c.bar=s,{el:r,slots:c}}function Ie(e,l){let r=w("div","pw-switch"),s=w("div","pw-switchrow"),a=w("span","pw-switchlabel");a.id=`${p}-${l}-label`;let c=w("button","pw-toggle");c.type="button",c.setAttribute("role","switch"),c.setAttribute("aria-labelledby",a.id),c.append(w("i")),c.addEventListener("click",()=>Ot(l,c.getAttribute("aria-checked")!=="true")),s.append(a,c);let f=w("div","pw-switchcontent");return r.append(s,f),{el:r,slots:{content:f,label:a,toggle:c}}}function _e(e){let l=w("div","pw-carousel");l.setAttribute("role","region"),l.setAttribute("aria-roledescription","carousel");let r=w("div","pw-track");r.tabIndex=0,r.style.setProperty("--pw-peek",T(e.peek)),r.style.setProperty("--pw-gap",T(e.spacing)),typeof e.height=="number"&&(r.style.minHeight=T(e.height));let s=e.indicator??{},a=w("div",`pw-dots ${s.style??"dots"}`);a.style.setProperty("--pw-dot",T(s.size??8)),a.style.setProperty("--pw-dot-gap",T(s.spacing??6)),a.style.setProperty("--pw-dot-c",s.color??"#8888"),a.style.setProperty("--pw-dot-on",s.active_color??"currentColor"),s.style==="none"&&(a.hidden=!0),l.append(r,a);let c=()=>[...r.children].filter(N=>N instanceof(d?.HTMLElement??HTMLElement)),f=()=>{let N=c(),W=r.scrollLeft+r.clientWidth/2,V=0;return N.forEach((Y,It)=>{Math.abs(Y.offsetLeft+Y.offsetWidth/2-W)<Math.abs((N[V]?.offsetLeft??0)+(N[V]?.offsetWidth??0)/2-W)&&(V=It)}),V},S=N=>{let W=c();if(W.length===0)return;let V=e.loop?(N+W.length)%W.length:Math.max(0,Math.min(W.length-1,N)),Y=W[V];r.scrollTo?.({left:Y.offsetLeft-(r.clientWidth-Y.offsetWidth)/2,behavior:oe()?"auto":"smooth"}),$(V)},$=N=>{[...a.children].forEach((W,V)=>W.setAttribute("aria-current",String(V===N)))};r.addEventListener("scroll",()=>$(f()),{passive:!0}),r.addEventListener("keydown",N=>{N.key!=="ArrowRight"&&N.key!=="ArrowLeft"||(N.preventDefault(),S(f()+(N.key==="ArrowRight"?1:-1)))});let O=!1,I=()=>O=!0,K=()=>O=!1;l.addEventListener("pointerenter",I),l.addEventListener("pointerleave",K),l.addEventListener("focusin",I),l.addEventListener("focusout",K);let le=[];if(typeof e.auto_advance_ms=="number"){let N=setInterval(()=>{if(O||oe()||B())return;let W=c(),V=f();!e.loop&&V>=W.length-1||S(V+1)},e.auto_advance_ms);le.push(()=>clearInterval(N))}return l.pwDots=N=>{a.children.length!==N&&(a.replaceChildren(...Array.from({length:N},(W,V)=>{let Y=w("button");return Y.type="button",Y.setAttribute("aria-label",be.page(V+1,N)),Y.addEventListener("click",()=>S(V)),Y})),$(f()))},{el:l,slots:{pages:r},cleanup:le}}function $t(e,l){let r=l;switch(l.type){case"package":{let s=dn(r),c=!!A.packages[s]?.price;e.el.dataset.package=s,e.el.setAttribute("aria-checked",String(r.selected===!0)),e.el.setAttribute("aria-disabled",String(!c));break}case"purchase_button":{let s=String(r.package);e.el.dataset.package=s,e.el.disabled=Q||!A.packages[s]?.price;break}case"switch":{let s=e.slots.toggle;s.setAttribute("aria-checked",String(r.on===!0)),s.style.setProperty("--pw-tint",String(r.tint??"")),s.style.setProperty("--pw-off",`color-mix(in srgb, ${M.tree.theme.colors.muted??"#888888"} 45%, transparent)`),e.slots.label.textContent=String(r.label??""),e.slots.label.hidden=r.label===void 0;break}case"tabs":{for(let s of r.tabs){let a=e.slots[`label:${s.id}`];a.setAttribute("aria-selected",String(s.selected)),a.tabIndex=s.selected?0:-1,s.selected&&e.slots.content.setAttribute("aria-labelledby",a.id)}r.bar&&et(e.slots.bar,{type:"stack",style:r.bar},"vertical");break}}}function Pt(e,l,r){let s=l,a={...r,inControl:r.inControl||l.type==="button"||l.type==="purchase_button"||l.type==="package"};switch(l.type){case"stack":case"package":{let c=l.type==="stack"?String(s.axis??"vertical"):"vertical",f=s.children.map($=>[$,g($,c,a)]).filter($=>$[1]!==null),S=f.map($=>$[1]);ae(e.slots.children,S),Tt(c,f),l.type==="stack"&&$e(e.el,S);break}case"button":case"purchase_button":{let c=s.content?g(s.content,"vertical",a):null;ae(e.slots.content,c?[c]:[]);break}case"countdown":case"switch":{let c=s.content?g(s.content,"vertical",a):null;ae(e.slots.content,c?[c]:[]),$e(e.slots.content,c?[c]:[]);break}case"tabs":{for(let f of s.tabs){let S=f.label?g(f.label,"vertical",{...a,inControl:!0}):null;ae(e.slots[`label:${f.id}`],S?[S]:[])}let c=s.content?g(s.content,"vertical",a):null;ae(e.slots.content,c?[c]:[]),$e(e.slots.content,c?[c]:[]);break}case"carousel":{let c=s.pages.map(f=>g(f,"page",a)).filter(Boolean);c.forEach((f,S)=>{f.setAttribute("role","group"),f.setAttribute("aria-roledescription","slide"),f.setAttribute("aria-label",be.page(S+1,c.length))}),ae(e.slots.pages,c),$e(e.slots.pages,c),e.el.pwDots?.(c.length);break}}}function Tt(e,l){let r=e==="horizontal"?"width":e==="vertical"?"height":null;if(!r)return;let s=f=>f?r==="width"?f.leading+f.trailing:f.top+f.bottom:0,a=l.filter(([f])=>f.style?.[r]==="fill"),c=Math.max(0,...a.map(([f])=>s(f.style?.padding)+s(f.style?.margin)));for(let[f,S]of a)S.style.flexBasis=T(c-s(f.style?.margin))}function $e(e,l){let r=l.filter(a=>a.dataset.pwType==="package");if(r.length===0){e.getAttribute("role")==="radiogroup"&&e.removeAttribute("role");return}e.setAttribute("role","radiogroup"),e.setAttribute("aria-label",be.packages);let s=r.find(a=>a.getAttribute("aria-checked")==="true")??r[0];for(let a of r)a.tabIndex=a===s?0:-1}function ae(e,l){let r=()=>[...e.children].filter(a=>a.dataset?.pwPath!==void 0),s=r();for(let a of s)l.includes(a)||a.remove();s=r(),l.forEach((a,c)=>{s[c]!==a&&(e.insertBefore(a,s[c]??null),s=r())})}let De=new WeakMap;function H(e,l,r){e.style.setProperty(l,r),De.get(e).push(l)}function et(e,l,r){for(let f of De.get(e)??[])e.style.removeProperty(f);De.set(e,[]);for(let f of[...e.children])f.classList.contains("pw-bgvideo")&&f.remove();let s=l.style;if(l.type==="image"&&e.classList.toggle("natural",typeof l.aspect!="number"&&(s?.height===void 0||s.height==="fit")),!s)return;let a=(f,S)=>{S&&(H(e,`${f}-block-start`,T(S.top)),H(e,`${f}-block-end`,T(S.bottom)),H(e,`${f}-inline-start`,T(S.leading)),H(e,`${f}-inline-end`,T(S.trailing)))};a("padding",s.padding),a("margin",s.margin),tt(e,"width",s.width,r),typeof s.height=="number"&&nn.has(l.type)?(H(e,"min-height",T(s.height)),r==="vertical"&&H(e,"flex","none")):tt(e,"height",s.height,r),typeof s.max_width=="number"&&H(e,"max-width",T(s.max_width));let c=s.background;if(c){if(c.type==="color")H(e,"background-color",c.color);else if(c.type==="linear"||c.type==="radial")H(e,"background-image",Be(c)??"");else if(c.type==="image"){let f=D(c.src);f&&(H(e,"background-image",`url("${f.replace(/"/g,"%22")}")`),H(e,"background-size",c.fit==="fit"?"contain":"cover"),H(e,"background-position","center"),H(e,"background-repeat","no-repeat"))}else if(c.type==="video"){let f=D(c.src);if(f){let S=w("video","pw-bgvideo");S.src=f;let $=D(c.poster);$&&(S.poster=$),S.muted=!0,S.loop=c.loop!==!1,S.playsInline=!0,S.autoplay=!oe()&&!B(),S.setAttribute("aria-hidden","true"),H(e,"isolation","isolate"),e.prepend(S)}}}if(s.border&&l.type==="custom_html"?H(e,"border",`${T(s.border.width)} solid ${s.border.color??"transparent"}`):s.border&&(H(e,"--pw-bw",T(s.border.width)),H(e,"--pw-bc",s.border.color??"transparent")),s.corners){let f=s.corners;H(e,"border-start-start-radius",T(f.top_leading)),H(e,"border-start-end-radius",T(f.top_trailing)),H(e,"border-end-start-radius",T(f.bottom_leading)),H(e,"border-end-end-radius",T(f.bottom_trailing)),(l.type==="image"||l.type==="video"||c?.type==="image"||c?.type==="video")&&H(e,"overflow","hidden")}s.shadow&&H(e,"box-shadow",`${T(s.shadow.x)} ${T(s.shadow.y)} ${T(s.shadow.radius)} ${s.shadow.color}`),typeof s.opacity=="number"&&s.opacity!==1&&H(e,"opacity",String(s.opacity))}function tt(e,l,r,s){if(r==null||r==="")return;if(s==="page"){l==="height"&&H(e,"height",r==="fill"?"100%":typeof r=="number"?T(r):r==="fit"?"fit-content":String(r));return}let a=s==="horizontal"?"width":s==="vertical"?"height":null;r==="fill"?l===a?(H(e,"flex","1 1 0"),H(e,`min-${l}`,"0")):s==="z"?H(e,l,"100%"):(H(e,"align-self","stretch"),H(e,l,"auto")):r==="fit"?(l===a&&H(e,"flex","0 0 auto"),H(e,l,"fit-content")):(H(e,l,typeof r=="number"?T(r):String(r)),l===a&&H(e,"flex","none"))}function Be(e){let l=r=>r.map(s=>`${s.color} ${(s.at*100).toFixed(2)}%`).join(", ");if(e.type==="linear")return`linear-gradient(${e.angle}deg, ${l(e.stops)})`;if(e.type==="radial"){let r=`${(e.radius*100).toFixed(2)}%`;return`radial-gradient(ellipse ${r} ${r} at ${(e.center_x*100).toFixed(2)}% ${(e.center_y*100).toFixed(2)}%, ${l(e.stops)})`}}function Ct(e,l){let r=l.animation;if(!r||A.animate===!1||oe()||typeof e.animate!="function")return;let s=24,c={fade:{opacity:0},slide_up:{opacity:0,transform:`translateY(${s}px)`},slide_down:{opacity:0,transform:`translateY(-${s}px)`},slide_leading:{opacity:0,transform:`translateX(-${s}px)`},slide_trailing:{opacity:0,transform:`translateX(${s}px)`},scale:{opacity:0,transform:"scale(0.92)"}}[r.type];c&&e.animate([c,{opacity:1,transform:"none"}],{duration:r.duration_ms,delay:r.delay_ms,easing:At[r.easing]??At.ease_out,fill:"backwards"})}function Pe(){n.onState?.({...C}),se()}function Je(e){C.selected===e&&M.tree.selected===e||(C.selected=e,Pe(),n.onSelect?.(Ze(e)))}function Ve(e){B()||e.getAttribute("aria-disabled")==="true"||Je(e.dataset.package??"")}function Ht(e,l){if(e.key===" "||e.key==="Enter"){e.preventDefault(),Ve(l);return}let r=e.key==="ArrowDown"||e.key==="ArrowRight"?1:e.key==="ArrowUp"||e.key==="ArrowLeft"?-1:0;if(r===0)return;e.preventDefault();let s=l.closest("[role=radiogroup]"),c=(s?[...s.children].filter($=>$.dataset.pwType==="package"):[l]).filter($=>$.getAttribute("aria-disabled")!=="true"),f=c.indexOf(l),S=c[(f+r+c.length)%c.length];S&&(Ve(S),(j.get(S.dataset.pwPath??"")?.el??S).focus())}function nt(e,l){if(B())return;C.tabs={...C.tabs,[e]:l};let r=zt(e,l);r&&(C.selected=r),Pe()}function zt(e,l){return ot(e)?.tabs?.find(s=>s.id===l)?.package}function Ot(e,l){if(B())return;C.switches={...C.switches,[e]:l};let r=ot(e),s=l?r?.on_package:r?.off_package;s&&(C.selected=s),Pe()}function ot(e){let l=A.definition;for(let r of e.match(/[^.[\\]]+/g)??[])l=l?.[/^\\d+$/.test(r)?Number(r):r];return l}async function st(e,l){if(!(B()||Q)){Q=!0,e.disabled=!0,e.setAttribute("aria-busy","true");try{await l()}finally{Q=!1,e.removeAttribute("aria-busy"),e.disabled=!1;for(let r of j.values())r.type==="purchase_button"&&(r.el.disabled=!A.packages[r.el.dataset.package??""]?.price)}}}async function Nt(e){A.packages[e]?.price&&await n.onPurchase(Ze(e))}function Te(e){B()&&e!==null&&C.sheet===e||(e&&!C.sheet&&(X=y.activeElement??null),C.sheet=e,Pe(),e||X?.focus())}function Ft(){let e=C.sheet,l=e?M.tree.sheets?.[e]:void 0;if(!l){L.remove();return}L.className="pw-layer";let r=L.firstElementChild;if(!r){r=w("div","pw-sheet"),r.setAttribute("role","dialog"),r.setAttribute("aria-modal","true"),r.tabIndex=-1;let a=w("div","pw-grab");a.setAttribute("aria-hidden","true"),r.append(a),L.append(r),L.addEventListener("click",c=>{c.target===L&&!B()&&Te(null)}),r.addEventListener("keydown",c=>jt(c,r))}r.style.background=M.tree.theme.colors.background??"",r.setAttribute("aria-label",M.sheetTitles[e]??"");let s=g(l,"vertical",{inControl:!1,inSheet:!0});ae(r,s?[s]:[]),L.isConnected||(m.append(L),queueMicrotask(()=>(rt(r)[0]??r).focus()))}function rt(e){return[...e.querySelectorAll(\'button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])\')].filter(l=>!l.hidden)}function jt(e,l){if(e.key==="Escape"){e.preventDefault(),Te(null);return}if(e.key!=="Tab")return;let r=rt(l);if(r.length===0){e.preventDefault();return}let s=r[0],a=r[r.length-1],c=y.activeElement;e.shiftKey&&c===s?(e.preventDefault(),a.focus()):!e.shiftKey&&c===a&&(e.preventDefault(),s.focus())}function it(e){try{e.contentWindow?.postMessage({type:"palbase.paywall",package:M.tree.selected,locale:M.language,appearance:A.appearance},"*")}catch{}}function at(e){for(let l of y.querySelectorAll("[data-pw-highlight]"))l.removeAttribute("data-pw-highlight");ne=e,e&&j.get(e)?.el.setAttribute("data-pw-highlight","")}let lt=e=>e.composedPath().find(l=>l instanceof(d?.HTMLElement??HTMLElement)&&l.dataset.pwPath!==void 0);m.addEventListener("click",e=>{if(!n.editor)return;let l=lt(e);l&&(e.preventDefault(),e.stopPropagation(),n.editor.onPick(l.dataset.pwPath))},!0),m.addEventListener("pointerover",e=>{if(n.editor){for(let l of y.querySelectorAll("[data-pw-hover]"))l.removeAttribute("data-pw-hover");lt(e)?.setAttribute("data-pw-hover","")}}),m.addEventListener("pointerleave",()=>{for(let e of y.querySelectorAll("[data-pw-hover]"))e.removeAttribute("data-pw-hover")});let ct=()=>se();return me?.addEventListener?.("change",ct),se(),{get selected(){return Ze(M.tree.selected)},get state(){return{...C}},select(e){Je(e)},update(e){A={...A,...e},e.state&&Object.assign(C,e.state),se()},highlight(e){at(e)},replay(){for(let e of j.values())e.cleanup.forEach(l=>l());j=new Map;for(let e of[k,v,x])e.replaceChildren();se()},destroy(){clearTimeout(xe),Ae?.disconnect(),i.fonts?.removeEventListener?.("loadingdone",q),me?.removeEventListener?.("change",ct);for(let e of j.values())e.cleanup.forEach(l=>l());h.remove()}}}function un(o,t,n){let i=o.defaultView;try{if(i&&"adoptedStyleSheets"in t&&typeof i.CSSStyleSheet=="function"){let p=new i.CSSStyleSheet;p.replaceSync(n),t.adoptedStyleSheets=[p];return}}catch{}let d=o.createElement("style");d.textContent=n,t.append(d)}function fn(o,t){let n=o;for(let i of t.split(".")){let d=/^([^[\\]]+)((?:\\[\\d+\\])*)$/.exec(i);if(!d||typeof n!="object"||n===null)return;n=n[d[1]];for(let p of d[2].matchAll(/\\[(\\d+)\\]/g))n=Array.isArray(n)?n[Number(p[1])]:void 0}return typeof n=="object"&&n!==null?n:void 0}var gn=o=>Uint8Array.from(atob(o),t=>t.charCodeAt(0));async function hn(o){let t={};for(let n of o.assets){let i=gn(n.data);if((n.kind==="image"||n.kind==="video")&&(t[n.id]={kind:n.kind,url:URL.createObjectURL(new Blob([i],{type:n.type}))}),n.kind==="font"){let d=new FontFace(We(n.id),i,{display:"block"});await d.load(),document.fonts.add(d),t[n.id]={kind:"font",url:"",family:We(n.id)}}if(n.kind==="html"){let d=ut(await pt(i));d!==void 0&&(t[n.id]={kind:"html",url:"",html:d})}}return t}function mn(o,t){let n=o.catalog[t.platform]??{packages:{},eligible:[]},i=Ne(o.definition,{locale:o.locale,appearance:t.appearance,platform:t.platform,packages:n.packages,eligible:n.eligible}),d=o.state;return{...d.selected?{selected:d.selected}:{},...d.tab?{tabs:Object.fromEntries(Object.keys(i.tabs).map(p=>[p,d.tab]))}:{},...d.switch?{switches:Object.fromEntries(Object.keys(i.switches).map(p=>[p,d.switch==="on"]))}:{},...d.sheet?{sheet:d.sheet}:{}}}async function bn(o){let t=[...o.querySelectorAll("img, video")];await Promise.race([Promise.all(t.map(n=>n instanceof HTMLImageElement?n.complete?Promise.resolve():n.decode().catch(()=>{}):n.readyState>=2?Promise.resolve():new Promise(i=>{n.addEventListener("loadeddata",()=>i(),{once:!0}),n.addEventListener("error",()=>i(),{once:!0})}))),new Promise(n=>setTimeout(n,5e3))])}var je=()=>new Promise(o=>requestAnimationFrame(()=>o(void 0)));async function wn(){let o=window.PALBASE_PREVIEW,t=o.shots[Number(new URLSearchParams(location.search).get("shot")??"0")]??o.shots[0];document.documentElement.style.fontSize=`${t.fontSize}px`,document.documentElement.style.colorScheme=t.appearance;let n=document.getElementById("screen");Object.assign(n.style,{width:`${t.width}px`,height:`${t.height}px`,paddingTop:`${t.top}px`,paddingBottom:`${t.bottom}px`});let i=document.getElementById("paywall"),d=o.catalog[t.platform]??{packages:{},eligible:[]},p=_t(i,{definition:o.definition,locale:o.locale,appearance:t.appearance,platform:t.platform,packages:d.packages,eligible:d.eligible,links:o.links,assets:await hn(o),state:mn(o,t),animate:!1,reduceMotion:!0},{onPurchase:()=>{},onRestore:()=>{},onClose:()=>{},onLink:()=>{},onOfferCode:()=>{}}),h=i.querySelector("[data-palbase-paywall]")?.shadowRoot;if(!h)throw new Error("the renderer drew nothing");let y=getComputedStyle(h.querySelector(".pw2")).backgroundColor;n.style.background=y==="rgba(0, 0, 0, 0)"||y==="transparent"?t.appearance==="dark"?"#000":"#fff":y,await document.fonts.ready,await bn(h),await je(),await je(),document.body.dataset.selected=p.selected?`${p.selected.product}.${p.selected.plan}`:"",o.check&&(document.body.dataset.findings=JSON.stringify(vt(h,{platform:t.platform,width:t.width,height:t.height,nodeAt:m=>fn(o.definition,m)}))),window.PALBASE_FULL=async()=>{let m=Math.max(...[".pw2-body",".pw2"].map(v=>h.querySelector(v)).map(v=>v.scrollHeight-v.clientHeight)),k=Math.max(t.height,Math.ceil(n.offsetHeight+m));return n.style.height=`${k}px`,await je(),await je(),k}}wn().then(()=>{document.body.dataset.ready="1"},o=>{document.body.dataset.error=o instanceof Error?o.message:String(o),document.body.dataset.ready="1"});})();\n')}function Rr(e){let t=/^P(\d+)([DWMY])$/.exec(e??"");if(!t)return null;let n=Number(t[1]);if(!Number.isSafeInteger(n)||n<1)return null;switch(t[2]){case"D":return{count:n,unit:"day",months:n*12/365};case"W":return{count:n,unit:"week",months:n*12/52};case"M":return{count:n,unit:"month",months:n};default:return{count:n,unit:"year",months:n*12}}}function $c(e){try{return new Intl.NumberFormat("en",{style:"currency",currency:e}).resolvedOptions().maximumFractionDigits??2}catch{return 2}}function Pc(e,t,n){let s=e/10**$c(t);try{return new Intl.NumberFormat(n,{style:"currency",currency:t}).format(s)}catch{return`${s.toFixed($c(t))} ${t}`}}function Rw(e,t){try{let n=new Intl.NumberFormat(t,{style:"unit",unit:e.unit,unitDisplay:"long"});return e.count>1?n.format(e.count):n.formatToParts(1).filter(a=>a.type==="unit").map(a=>a.value).join("").trim()||n.format(1)}catch{return e.count>1?`${e.count} ${e.unit}s`:e.unit}}var Tw={en:e=>`${e} free`,tr:e=>`${e} \xFCcretsiz`,de:e=>`${e} kostenlos`,es:e=>`${e} gratis`,it:e=>`${e} gratis`,nl:e=>`${e} gratis`,pt:e=>`${e} gr\xE1tis`,sv:e=>`${e} gratis`,da:e=>`${e} gratis`,nb:e=>`${e} gratis`};function Ew(e,t){let n;try{n=new Intl.NumberFormat(t,{style:"unit",unit:e.unit,unitDisplay:"long"}).format(e.count)}catch{n=`${e.count} ${e.unit}${e.count>1?"s":""}`}let s=Tw[t.split("-")[0].toLowerCase()];return s?s(n):n}function Cw(e,t){try{return new Intl.NumberFormat(t,{style:"percent",maximumFractionDigits:0}).format(e)}catch{return`${Math.round(e*100)}%`}}function Ac(e,t){let n=new Map,s=new Map;for(let r of e){let i=Rr(r.period);if(!i)continue;let o=r.price.amount/i.months;n.set(r.key,o),s.set(r.price.currency,Math.max(s.get(r.price.currency)??0,o))}let a={};for(let r of e){let i={product_name:r.productName,price:Pc(r.price.amount,r.price.currency,t)},o=Rr(r.period);if(o){i.period=Rw(o,t),i.price_per_month=Pc(Math.round(n.get(r.key)),r.price.currency,t);let p=s.get(r.price.currency)??0,d=p>0?Math.floor((1-n.get(r.key)/p)*100)/100:0;d>=.01&&(i.savings=Cw(d,t))}let l=r.intro?.mode==="free_trial"?Rr(r.intro.duration):null;l&&(i.trial=Ew(l,t)),a[r.key]=i}return a}var S=(e,t=!1)=>({kind:"value",value:"plain",def:e,required:t}),je=e=>({kind:"value",value:"color",def:e}),Rc=e=>({kind:"value",value:"spacing",def:e}),jw=e=>({kind:"value",value:"radius",def:e}),xt=(e=!1)=>({kind:"text",required:e}),Tr=(e,t=!1)=>({kind:"list",item:e,required:t}),qn=(e,t=!1)=>({kind:"object",item:e,required:t}),Tc=(e=!1)=>({kind:"component",required:e}),Er={kind:"components"},Ow={stack:{axis:S("vertical"),spacing:Rc(0),align:S("center"),distribution:S("start"),scroll:S(!1),children:Er},text:{text:xt(),spans:Tr("span"),font:S(),size:S(),weight:S(),line_height:S(),letter_spacing:S(),color:{kind:"value",value:"textColor",def:"text"},align:S("center"),max_lines:S(),uppercase:S(!1)},image:{src:S(void 0,!0),fit:S("fit"),aspect:S(),mask:qn("mask"),overlay:{kind:"value",value:"textColor"},alt:xt(),decorative:S(!1)},icon:{name:S(void 0,!0),color:je("accent"),size:S(24)},video:{src:S(void 0,!0),poster:S(),autoplay:S(!0),loop:S(!0),mute:S(!0),controls:S(!1),fit:S("fill"),aspect:S(),alt:xt()},button:{action:qn("action",!0),content:Tc(!0)},purchase_button:{content:Tc(!0),package:S()},package:{product:S(void 0,!0),plan:S(void 0,!0),selected:S(!1),children:Er},tabs:{},switch:{},carousel:{pages:Er,peek:S(0),spacing:S(8),loop:S(!1),auto_advance_ms:S(),indicator:qn("indicator"),height:S("")},countdown:{},timeline:{items:Tr("timeline_item",!0),line_color:je("accent"),line_width:S(2),icon_color:je("on_accent"),color:je("text"),spacing:S(16)},features:{items:Tr("feature",!0),icon_color:je("accent"),color:je("text"),spacing:S(12)},spacer:{size:Rc()},divider:{color:je("muted"),thickness:S(1)},custom_html:{src:S(void 0,!0),height:S("",!0)}},OR={style:{padding:{kind:"edges"},margin:{kind:"edges"},width:S(""),height:S(""),max_width:S(),background:{kind:"fill"},border:qn("border"),corners:{kind:"corners"},shadow:qn("shadow"),opacity:S(1)},border:{width:S(),color:je("")},shadow:{color:je(""),radius:S(0),x:S(0),y:S(0)},linear:{type:S(""),angle:S(180),stops:S()},radial:{type:S(""),center_x:S(.5),center_y:S(.5),radius:S(.5),stops:S()},image_fill:{type:S(""),src:S(),fit:S("fill")},video_fill:{type:S(""),src:S(),poster:S(),loop:S(!0)},animation:{type:S(""),duration_ms:S(300),delay_ms:S(0),easing:S("ease_out")},action:{type:S(""),url:S(),sheet:S(),package:S()},mask:{shape:S(""),radius:jw(0),depth:S(16)},span:{text:xt(!0),bold:S(!1),italic:S(!1),underline:S(!1),strikethrough:S(!1),color:je(""),link:S()},timeline_item:{icon:S(void 0,!0),title:xt(!0),text:xt(),color:je("")},feature:{icon:S(void 0,!0),text:xt(!0)},indicator:{style:S("dots"),color:je("muted"),active_color:je("accent"),size:S(8),spacing:S(6)},typography:{font:S("system"),size:S(),weight:S(400),line_height:S(),letter_spacing:S(0),text_style:S("body")}};var xR=Object.keys(Ow);var Cr=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)?e:{};function Kn(e){let t=[],n=a=>{if(Array.isArray(a))return a.forEach(n);if(typeof a!="object"||a===null)return;let r=a;if(r.type==="package"&&typeof r.product=="string"&&typeof r.plan=="string"){let i=`${r.product}.${r.plan}`;t.includes(i)||t.push(i)}for(let i of Object.keys(r).sort())n(r[i])},s=e;for(let a of["header","root","footer"])n(s[a]);for(let a of Object.keys(Cr(s.sheets)).sort())n(Cr(Cr(s.sheets)[a]).root);return t}var Ec=["ios","android","web"],Cc=["light","dark"],Nw={default:16,largest:24},Mw={ios:{width:393,height:852,top:54,bottom:34},android:{width:412,height:915,top:40,bottom:24},web:{width:1024,height:720,top:0,bottom:0}},Or=2,qw={terms:"https://example.com/terms",privacy:"https://example.com/privacy"};function jr(e,t,n,s){let a=e.flags[t];if(a===void 0)return s;if(!n.includes(a))throw m(`--${t} is one of ${n.join(", ")}`);return a}function jc(e,t){let n=e.flags[t];if(n===void 0)return;let s=Number(n);if(!Number.isInteger(s)||s<200||s>4e3)throw m(`--${t} is a whole number of CSS pixels, 200 to 4000`);return s}function Kw(e){let t={};for(let n of e??[]){let s=n.indexOf("="),[a,r]=s<0?[n,""]:[n.slice(0,s),n.slice(s+1)];if(a==="tab"&&r!=="")t.tab=r;else if(a==="sheet"&&r!=="")t.sheet=r;else if(a==="switch"&&(r==="on"||r==="off"))t.switch=r;else throw m(`--state ${n}: it names tab=<id>, switch=on|off or sheet=<id>`)}return t}function Uw(e,t){if(e)return e[t]?.name??e[t.split("-")[0]]?.name??Object.values(e).find(n=>n.name)?.name}function zw(e){let t=e.split(".").slice(1).join(".").toLowerCase(),[n,s]=/year|annual|12m/.test(t)?["P1Y",5999]:/week/.test(t)?["P1W",499]:/life|forever|once|onetime|one_time/.test(t)?[void 0,14999]:["P1M",999];return{key:e,productName:e.split(".")[0],price:{amount:s,currency:"USD"},...n?{period:n}:{}}}function Gw(e,t,n,s){let a=[],r=[];for(let o of n){let[l,p]=o.split("."),d=e?.products.find(f=>f.key===l),c=d?.channels[t],g=c?.plans.find(f=>f.key===p);if(!d||!c||!g){a.push(zw(o)),r.push(o);continue}a.push({key:o,productName:Uw(c.locales,s)??d.key,price:{amount:g.price.amount,currency:g.price.currency},period:g.period,intro:g.intro})}let i=a.filter(o=>o.intro?.mode==="free_trial").map(o=>o.key);return{packages:Ac(a,s),eligible:i,sampled:r}}function Bw(e){return[...new Set([...JSON.stringify(e).matchAll(/asset:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/g)].map(t=>t[1]))]}async function Jw(e,t,n){if(n.length===0)return{assets:[],left:[]};let s=await un(e,t),a=Fe(e.cloud,t.env_ref),r=[],i=[];for(let o of n){let l=s.find(p=>p.id===o);if(!l){i.push(`asset:${o} is not one of the project's assets`);continue}try{let p;try{p=new URL(l.url)}catch{throw new Error("its address is not one")}if(p.origin!==a)throw new Error(`its address is not the environment's (${a}) \u2014 ${p.protocol==="https:"||p.protocol==="http:"?p.host:p.protocol}: the CLI does not ask another host`);let d=await e.io.fetch(p.href,{credentials:"omit",redirect:"manual"});if(!d.ok)throw new Error(`its address answered ${d.status}`);let c=Buffer.from(await d.arrayBuffer());if(xw("sha256").update(c).digest("hex")!==l.sha256.toLowerCase())throw new Error("its bytes are not the ones registered");r.push({id:o,kind:l.kind,type:l.content_type,data:c.toString("base64")})}catch(p){i.push(`${l.ref} could not be had: ${p instanceof Error?p.message:String(p)}`)}}return{assets:r,left:i}}function Fw(e,t,n){let s=JSON.stringify(n).replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029");return`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${t.replace(/[&<>"]/g,r=>`&#${r.charCodeAt(0)};`)} \u2014 paywall preview</title>
665
+ <style>html,body{margin:0}body{min-height:100vh;display:grid;place-items:center;background:#8E8E93}#screen{box-sizing:border-box;overflow:hidden}#paywall{height:100%}</style>
666
+ </head><body><div id="screen"><div id="paywall"></div></div>
667
+ <script>window.PALBASE_PREVIEW=${s};</script>
668
+ <script>${e.replace(/<\/script/gi,"<\\/script")}</script>
669
+ </body></html>
670
+ `}async function Fs(e,t,n){let s=await ye(e.io.cwd,t);await Lw(Iw(s),{recursive:!0}),await ye(e.io.cwd,t),await Dw(s,n)}var Lc={file:{type:"string",value:"paywall.json",description:"the definition to draw ('-': stdin)"},locale:{type:"string",value:"tag",description:"the reader's language (default: the definition's default_locale)"},package:{type:"string",value:"product.plan",description:"the package selected"},state:{type:"string",multiple:!0,value:"k=v,\u2026",description:"tab=<id> (every tabs component on it), switch=on|off, sheet=<id> (open)"},width:{type:"string",value:"px",description:"the screen's width in CSS pixels (default: the platform's)"},height:{type:"string",value:"px",description:"the screen's height (default: the platform's)"}};async function Dc(e,t,n,s){let a=e.args[0],r=e.flags.file;if(a===void 0==(r===void 0))throw m("draw a file (--file <paywall.json>) or a paywall's draft by its name, one of the two");let i=e.flags.platform!==void 0?[jr(e,"platform",Ec,"ios")]:n?[...Ec]:["ios"],o=e.flags.appearance!==void 0?[jr(e,"appearance",Cc,"light")]:n?[...Cc]:["light"],l=e.flags["text-size"]!==void 0?[jr(e,"text-size",["default","largest"],"default")]:n?["default","largest"]:["default"],p=jc(e,"width"),d=jc(e,"height"),c=Kw(Q(e.flags.state)),g;if(r!==void 0)g=await Gs(e,r);else{let j=await jt(e,t,a);g=(await u(e,t,"GET",`/purchases/paywalls/${v(j.id)}`)).draft}if(typeof g.default_locale!="string"||typeof g.root!="object")throw m("this is no paywall definition: it has no root or default_locale (palbase purchases:paywalls:validate says what it lacks)");let f=e.flags.locale??g.default_locale,b=Nn(a!==void 0?a:g.name??"paywall"),P=Kn(g);if(e.flags.package!==void 0){let j=e.flags.package;if(!P.includes(j))throw m(`--package ${j}: the paywall sells ${P.length===0?"no package":P.join(", ")}`);c.selected=j}let E=[];for(let j of i)for(let ne of o)for(let T of l){let V=Mw[j];E.push({platform:j,appearance:ne,fontSize:Nw[T],width:p??V.width,height:d??V.height,top:V.top,bottom:V.bottom,text:T})}let k=P.length===0?void 0:await u(e,t,"GET","/purchases/offerings"),$={},O={};for(let j of new Set(E.map(ne=>ne.platform))){let ne=Gw(k,j,P,f);$[j]={packages:ne.packages,eligible:ne.eligible},ne.sampled.length>0&&(O[j]=ne.sampled)}let{assets:A,left:L}=await Jw(e,t,Bw(g)),X={definition:g,locale:f,catalog:$,links:qw,assets:A,state:c,shots:E.map(({text:j,...ne})=>ne),...s?{check:!0}:{}},I=Fw(await Sc(),g.name??b,X);return{name:b,shots:E,html:I,sampled:O,left:L,said(){for(let j of Object.keys($)){let ne=O[j];P.length!==0&&(ne?e.out.note(`Prices on ${j}: sample prices for ${ne.join(", ")} \u2014 the catalog does not sell ${ne.length===1?"it":"them"} there${ne.length<P.length?"; the catalog's for the rest":""}.`):e.out.note(`Prices on ${j}: the catalog's (revision ${k.revision}).`))}for(let j of L)e.out.warn(`${j}: drawn without it`)}}}async function Ic(e,t,n){let s=vc(e.io.env);if(s.path)return s.path;let a=`${me}/.preview/${t.name}.html`;throw await Fs(e,a,t.html),t.said(),new h("no_browser",`no browser to ${n} with: open ${a}`,{exitCode:3,hint:s.named!==void 0?`PALBASE_BROWSER names ${s.named}, which is not a program here`:"install Chrome, Chromium, Edge or Brave, or name one: PALBASE_BROWSER=<path>"})}async function Nc(e,t,n,s){let a=`${me}/.preview/${t.name}.html`;await Fs(e,a,t.html);try{return{page:a,taken:await kc(n,await ye(e.io.cwd,a),t.shots.map((r,i)=>({query:`shot=${i}`,width:r.width,height:r.height,scale:Or,...s})),{args:_c(e.io.env)})}}catch(r){let i=r instanceof Mn&&r.noSandbox?"in a container the browser often has none: PALBASE_BROWSER_ARGS=--no-sandbox runs it without one":`the page is ${a}: open it in a browser to see it`;throw new h("preview_failed",`the paywall was not drawn: ${r instanceof Error?r.message:String(r)}`,{hint:i})}}var Oc=e=>`${e.platform} ${e.appearance}${e.text==="largest"?" largest":""}`,Mc={name:"purchases:paywalls:preview",summary:"a picture of a paywall as an app draws it \u2014 the SDK's own renderer, in a headless browser \u2014 to look at before it is saved",usage:"<name|id> | --file <paywall.json> [--platform ios|android|web] [--appearance light|dark] [--locale <tag>] [--text-size default|largest] [--package <key>] [--state tab=<id>,switch=on|off,sheet=<id>] [--full] [--all] [--out <png>] [--html <file>]",details:["It draws the file being edited (--file), or a paywall's draft by its name. The picture is the platform's screen: its status bar and home indicator kept clear, as on a phone. --full draws the whole paywall instead: the part that scrolls in full, the header and the footer once.","Prices are the environment's catalog's, as the platform sells each package; a package it does not sell there is drawn at a sample price, and named. The project's assets are downloaded and drawn when their bytes are the ones registered.","It needs Chrome, Chromium, Edge or Brave (PALBASE_BROWSER names another; PALBASE_BROWSER_ARGS adds to its arguments, as --no-sandbox in a container that has no sandbox for it). Without one the page is written as HTML to open, and it exits 3.",`--all draws every platform in both appearances at both text sizes into one folder (${me}/.preview/<name>/). --html writes the page alone, to open in a browser. purchases:paywalls:check measures what is drawn and lists what is wrong.`],args:{min:0,max:1},flags:{...Lc,platform:{type:"string",value:"platform",description:"ios (default), android or web: the screen and the platform's rules"},appearance:{type:"string",value:"appearance",description:"light (default) or dark"},"text-size":{type:"string",value:"size",description:"default, or largest: every text at the largest size a reader sets"},full:{type:"boolean",description:"the whole paywall, as tall as it is, not the screen alone"},all:{type:"boolean",description:"every platform \xD7 appearance \xD7 text size, into one folder"},out:{type:"string",value:"file",description:`the PNG under palbase/ (default: ${me}/.preview/<name>-<platform>-<appearance>.png); with --all, the folder`},html:{type:"string",value:"file",description:"write the page alone, as HTML under palbase/, to open in a browser"}},scope:"environment",async run(e){let t=await y(e),n=e.flags.all===!0;if(n&&(e.flags.platform!==void 0||e.flags.appearance!==void 0||e.flags["text-size"]!==void 0))throw m("--all draws every platform, appearance and text size: name none of them with it");let s=await Dc(e,t,n,!1),a=e.flags.html;if(a!==void 0){let f=Je(e,"html",a,".html");await Fs(e,f,s.html),s.said(),e.out.result({html:f},()=>e.out.line(`Wrote the page to ${f}: open it in a browser.`));return}let r=await Ic(e,s,"take the picture"),i=e.flags.full===!0,o=n?e.flags.out!==void 0?Je(e,"out",`${e.flags.out.replace(/\/+$/,"")}/x.png`,".png").replace(/\/x\.png$/,""):`${me}/.preview/${s.name}`:void 0,l=s.shots.map(f=>n?`${o}/${f.platform}-${f.appearance}-${f.text}${i?"-full":""}.png`:e.flags.out!==void 0?Je(e,"out",e.flags.out,".png"):`${me}/.preview/${s.name}-${f.platform}-${f.appearance}${f.text==="largest"?"-largest":""}${i?"-full":""}.png`),{page:p,taken:d}=await Nc(e,s,r,{full:i});for(let[f,b]of l.entries())await Fs(e,b,d[f].png);s.said();let c=d.flatMap((f,b)=>f.error?[`${l[b]}: ${f.error}`]:[]),g=s.shots;if(e.out.result({pictures:g.map((f,b)=>({file:l[b],platform:f.platform,appearance:f.appearance,text_size:f.text,width:f.width,height:d[b].height,scale:Or,full:i,selected:d[b].selected??null,...d[b].error?{error:d[b].error}:{}})),page:p,sampled_prices:s.sampled,assets_left_out:s.left},()=>{for(let[f,b]of l.entries())e.out.line(`${b} (${g[f].platform}, ${g[f].appearance}${g[f].text==="largest"?", largest text":""}, ${g[f].width}\xD7${d[f].height}${i?", whole":""} at ${Or}x${d[f].selected?`, ${d[f].selected} selected`:""})`)}),c.length>0)throw new h("preview_failed",`the renderer could not draw it: ${c.join("; ")}`)}};function xc(e){let t=new Map;for(let{screen:n,findings:s}of e)for(let{note:a,...r}of s){let i=`${r.code}\0${r.where}`,o=t.get(i)??{...r,screens:[]};o.screens.includes(n)||o.screens.push(n),t.set(i,o)}return[...t.values()]}var qc={name:"purchases:paywalls:check",summary:"measure a paywall as an app draws it, on every screen, and list what is wrong \u2014 code \u2014 where \u2014 what \u2014 exiting 1 while anything is",usage:"<name|id> | --file <paywall.json> [--platform \u2026] [--appearance \u2026] [--text-size \u2026] [--locale <tag>] [--package <key>] [--state \u2026]",details:["It draws the paywall with the SDK's own renderer in a headless browser \u2014 every platform, light and dark, at the default and the largest text size, unless a flag names one \u2014 and measures what shows:"," overflow_x: wider than the screen \xB7 text_cut: a text cut off \xB7 word_broken: a word (a price) broken across lines \xB7 purchase_hidden: no purchase button on the screen as it opens \xB7 covered_by_footer: an interactive part still under the fixed footer with the paywall scrolled to its end \xB7 large_gap: an empty band over 12% of the screen \xB7 contrast: text under WCAG's 4.5:1 (3:1 large) \xB7 selection_contrast: the selected package or tab under 3:1 from the others \xB7 small_target: a target under 44\xD744 on a phone, 24\xD724 on the web (a switch, a link inside a sentence and a carousel's dots aside) \xB7 image_missing: an image that did not load \xB7 repeated_text: the same words twice in one package.","Each names the component by its path in the definition (root.children[2]) and the words it shows. A text over a picture or a gradient is said apart, as not measured (unmeasured_contrast): a note, not a problem.","--json prints { problems: [{ code, where, message, screens }], notes: [ \u2026 ], screens }."],args:{min:0,max:1},flags:{...Lc,platform:{type:"string",value:"platform",description:"ios, android or web alone (default: all three)"},appearance:{type:"string",value:"appearance",description:"light or dark alone (default: both)"},"text-size":{type:"string",value:"size",description:"default or largest alone (default: both)"}},scope:"environment",async run(e){let t=await y(e),n=await Dc(e,t,!0,!0),s=await Ic(e,n,"measure it"),{taken:a}=await Nc(e,n,s,{measureOnly:!0});n.said();let r=a.flatMap((c,g)=>c.error?[`${Oc(n.shots[g])}: ${c.error}`]:[]);if(r.length>0)throw new h("preview_failed",`the renderer could not draw it: ${r.join("; ")}`);let i=n.shots.map(Oc),o=a.map(c=>c.findings??[]),l=xc(o.map((c,g)=>({screen:i[g],findings:c.filter(f=>!f.note)}))),p=xc(o.map((c,g)=>({screen:i[g],findings:c.filter(f=>f.note)}))),d=c=>c.screens.length===i.length?"on every screen":`on ${c.screens.join(", ")}`;return e.out.result({problems:l,notes:p,screens:i},()=>{for(let g of l)e.out.line(`${g.code} \u2014 ${g.where} \u2014 ${g.message}`),e.out.line(` ${d(g)}`);for(let g of p)e.out.line(`note: ${g.code} \u2014 ${g.where} \u2014 ${g.message} (${d(g)})`);let c=new Set(l.flatMap(g=>g.screens)).size;e.out.line(l.length===0?`Nothing wrong on ${i.length} screen(s)${p.length>0?`; ${p.length} thing(s) not measured`:""}.`:`${l.length} problem(s), on ${c} of ${i.length} screen(s).`)}),l.length>0?1:void 0}};import{createHash as Ww}from"node:crypto";import{mkdtemp as Vw,rm as Hw,stat as Xw,writeFile as Yw}from"node:fs/promises";import{tmpdir as Zw}from"node:os";import{join as Kc}from"node:path";var zc={"image/png":".png","image/jpeg":".jpg","image/webp":".webp","video/mp4":".mp4","video/quicktime":".mov","font/ttf":".ttf","font/otf":".otf","font/woff2":".woff2","application/zip":".zip"},Qw=new Set(["image","video","font","html"]),ev=/^[a-z0-9][a-z0-9-]{0,62}$/,tv=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,nv=/^[0-9a-f]{64}$/i,sv=50<<20;function av(e){let t=s=>{throw new h("unexpected_answer",`the environment answered a template the CLI does not take: ${s}`)},n=e;(typeof n!="object"||n===null)&&t("one is not an object"),(typeof n.key!="string"||!ev.test(n.key))&&t(`its key ${JSON.stringify(n.key)} is not lowercase letters, digits and dashes`),(typeof n.name!="string"||typeof n.description!="string")&&t(`${n.key} has no name or description`),(typeof n.definition!="object"||n.definition===null||Array.isArray(n.definition))&&t(`${n.key} has no definition`),Array.isArray(n.assets)||t(`${n.key} has no list of assets`);for(let s of n.assets)(typeof s?.id!="string"||!tv.test(s.id))&&t(`an asset of ${n.key} has the id ${JSON.stringify(s?.id)}, which is not a uuid`),Qw.has(s.kind)||t(`the asset ${s.id} of ${n.key} is a ${JSON.stringify(s.kind)}, not an image, a video, a font or an HTML block`),Object.hasOwn(zc,s.content_type)||t(`the asset ${s.id} of ${n.key} is ${JSON.stringify(s.content_type)}, which no paywall asset is`),(!Number.isInteger(s.size)||s.size<=0||s.size>sv)&&t(`the asset ${s.id} of ${n.key} is ${JSON.stringify(s.size)} bytes`),(typeof s.sha256!="string"||!nv.test(s.sha256))&&t(`the asset ${s.id} of ${n.key} has no SHA-256`);return n}async function Gc(e,t){let n=await u(e,t,"GET","/purchases/paywalls/templates");if(!Array.isArray(n?.templates))throw new h("unexpected_answer","the environment answered the templates without their list");return n.templates.map(av)}var Uc={monthly:1,yearly:12,weekly:12/52,quarterly:3,once:0};function rv(e){let t=/^P(\d+)([DWMY])$/.exec(e??"");if(!t)return 0;let n=Number(t[1]);return t[2]==="D"?n*12/365:t[2]==="W"?n*12/52:t[2]==="M"?n:n*12}function iv(e,t){let n=t?.products??[],s=new Set(n.flatMap(c=>c.plans.map(g=>`${c.key}.${g.key}`))),a=[],r=[];for(let c of n){if(c.type==="consumable")continue;let g=c.plans.map(f=>({key:f.key,months:rv(f.period)}));(g.some(f=>f.months>0)?a:r).push({key:c.key,plans:g})}let i={},o=new Set;for(let c of e)s.has(c)&&(i[c]=c,o.add(c));let l=[...new Set(e.map(c=>c.split(".")[0]))],p={},d=new Set;for(let c of l){let g=[...a,...r].find(f=>f.key===c);g&&(p[c]=g,d.add(g.key))}for(let c of l){if(p[c])continue;let f=(e.some(b=>b.startsWith(`${c}.`)&&(Uc[b.split(".")[1]]??0)>0)?a:r).find(b=>!d.has(b.key));f&&(p[c]=f,d.add(f.key))}for(let c of e){if(i[c])continue;let[g,f]=c.split("."),b=p[g],P=Uc[f],E=b?.plans.find(k=>P!==void 0&&Math.abs(k.months-P)<.01&&!o.has(`${b.key}.${k.key}`));b&&E&&(i[c]=`${b.key}.${E.key}`,o.add(i[c]))}return i}function ov(e,t){let n=s=>{if(Array.isArray(s))return s.map(n);if(typeof s!="object"||s===null)return s;let a=s,r={};for(let[i,o]of Object.entries(a))(i==="package"||i==="on_package"||i==="off_package")&&typeof o=="string"?r[i]=t[o]??o:i==="selected_package"&&Array.isArray(o)?r[i]=o.map(l=>typeof l=="string"?t[l]??l:l):r[i]=n(o);if(a.type==="package"&&typeof a.product=="string"&&typeof a.plan=="string"){let i=t[`${a.product}.${a.plan}`];i&&([r.product,r.plan]=i.split("."))}return r};return n(e)}function lv(e,t){let n=s=>{if(Array.isArray(s))return s.map(n);if(typeof s=="string"){let a=/^asset:(.+)$/.exec(s)?.[1];return a&&t[a]?t[a]:s}return typeof s!="object"||s===null?s:Object.fromEntries(Object.entries(s).map(([a,r])=>[a,n(r)]))};return n(e)}function pv(e,t,n){let s={};for(let a of e??[]){let r=a.indexOf("="),[i,o]=r<0?[a,""]:[a.slice(0,r),a.slice(r+1)];if(!t.includes(i))throw m(`--package ${a}: the template sells ${t.join(", ")||"no package"}, not ${i}`);if(!n.has(o))throw m(`--package ${a}: the catalog sells no package ${o||"(none named)"}`,n.size>0?`the catalog's: ${[...n].join(", ")}`:"the catalog sells nothing yet: palbase purchases:catalog:put");s[i]=o}return s}var dv={name:"purchases:paywalls:templates",summary:"the module's designed paywalls to start from: each one's key, what it looks like, and the packages it sells",details:["palbase purchases:paywalls:new <name> --template <key> writes one to a file, selling the catalog's own packages."],scope:"environment",async run(e){let t=await y(e),s=(await Gc(e,t)).map(a=>({key:a.key,name:a.name,description:a.description,packages:Kn(a.definition),assets:a.assets}));e.out.result({templates:s},()=>{e.out.table(s,[{header:"template",value:a=>a.key},{header:"name",value:a=>a.name},{header:"sells",value:a=>a.packages.join(", ")},{header:"what it looks like",value:a=>a.description}]),e.out.line("Make a paywall from one: palbase purchases:paywalls:new <name> --template <key>")})}};async function cv(e,t,n,s){let a=await ja(e,t),r=`${Fe(e.cloud,t.env_ref)}/purchases/paywalls/templates/${v(n)}/assets/${v(s.id)}`,i;try{i=await e.io.fetch(r,{headers:{apikey:a.key},redirect:"manual"})}catch(l){throw new h("network_error",`the template's ${s.kind} could not be downloaded: ${l instanceof Error?l.message:String(l)}`)}if(!i.ok)throw new h("template_asset_unavailable",`the template's ${s.kind} (${s.id}) could not be downloaded: the environment answered ${i.status}`);let o=Buffer.from(await i.arrayBuffer());if(o.length!==s.size||Ww("sha256").update(o).digest("hex")!==s.sha256.toLowerCase())throw new h("template_asset_changed",`the template's ${s.kind} (${s.id}) did not arrive as the bytes the template names: nothing was uploaded`);return o}var uv={name:"purchases:paywalls:new",summary:"write a new paywall's definition to a file from one of the module's templates, selling the catalog's own packages, its pictures uploaded to the project",usage:`<name> --template <key> [--package <template package>=<product.plan> \u2026] [--out <${me}/name.json>]`,details:["A template sells pro.monthly, pro.yearly, family.monthly, family.yearly and lifetime.once: each is renamed to the catalog's package that stands for it \u2014 the same one when the catalog sells it, else a plan of the same period. One that nothing stands for is said, and stays: --package pro.yearly=<product>.<plan> names it.","A template's pictures are uploaded to the project's public bucket paywall-assets (made when there is none; a private one is left as it is, and nothing is written) and registered; the definition names the project's assets in their place.","Nothing is saved to the environment but those assets: the file is edited, checked and looked at (purchases:paywalls:validate, :preview, :check), then saved with purchases:paywalls:put <name> --file <file> --revision 0."],args:{min:1,max:1},flags:{template:{type:"string",value:"key",description:"the template, as purchases:paywalls:templates lists them"},package:{type:"string",multiple:!0,value:"from=to",description:"a template's package and the catalog's that stands for it (repeat it, or a comma list)"},out:{type:"string",value:"file",description:`where under ${U}/ (default: ${me}/<name>.json)`}},scope:"environment",async run(e){let t=await y(e),n=e.args[0].trim();if(n===""||[...n].length>100)throw m("a paywall's name is 1 to 100 characters");let s=e.flags.template;if(s===void 0||s.trim()==="")throw m("--template names the template to start from","palbase purchases:paywalls:templates lists them");let a=e.flags.out!==void 0?Je(e,"out",e.flags.out,".json"):`${me}/${Nn(n)}.json`;try{throw await Xw(await ye(e.io.cwd,a)),new h("file_exists",`${a} is there already: a template is not written over a file`,{hint:"name another with --out, or remove the file"})}catch(k){if(k.code!=="ENOENT")throw k}let r=await Gc(e,t),i=r.find(k=>k.key===s);if(!i)throw new h("template_not_found",`no template ${JSON.stringify(s)}`,{hint:`the module's: ${r.map(k=>k.key).join(", ")}`});let o=Kn(i.definition),l=(await u(e,t,"GET","/purchases/catalog")).catalog,p=new Set((l?.products??[]).flatMap(k=>k.plans.map($=>`${k.key}.${$.key}`))),d={...iv(o,l),...pv(Q(e.flags.package),o,p)},c=o.filter(k=>!d[k]),g=new Set([...JSON.stringify(i.definition).matchAll(/asset:([0-9a-f-]{36})/g)].map(k=>k[1])),f=i.assets.filter(k=>g.has(k.id)),b={},P=[];if(f.length>0){let k=await un(e,t),$=f.filter(O=>{let A=k.find(L=>L.sha256.toLowerCase()===O.sha256.toLowerCase()&&L.kind===O.kind);return A&&(b[O.id]=A.ref),!A});if($.length>0){await w(e,t,`upload the template's ${$.length} asset(s) to the project's paywall-assets bucket and register ${$.length===1?"it":"them"}`),await Pr(e,t);let O=await Vw(Kc(Zw(),"palbase-template-"));try{for(let[A,L]of $.entries()){let X=Kc(O,`asset-${A}${zc[L.content_type]}`);await Yw(X,await cv(e,t,i.key,L));let I=await Ar(e,t,X,L.kind);b[L.id]=I.ref,P.push(I)}}finally{await Hw(O,{recursive:!0,force:!0})}}}let E=lv(ov({...i.definition,name:n},d),b);await Ks(e,a,E),e.out.result({file:a,template:i.key,name:n,packages:d,unmapped:c,assets:b,uploaded:P.map(k=>k.ref)},()=>{e.out.line(`Wrote ${n} to ${a}, from the template ${i.key}.`);for(let k of o)d[k]&&d[k]!==k&&e.out.line(` ${k} is the catalog's ${d[k]}`);for(let k of c)e.out.warn(`the catalog sells nothing that stands for ${k}: it stays ${k} in the file \u2014 a draft is warned of it, a publish refuses it. Name the catalog's: --package ${k}=<product>.<plan>, or change it in the file`);for(let k of P)e.out.line(` uploaded and registered its ${k.kind}: ${k.ref}`);e.out.line(`Edit it, then look at it and save it: palbase purchases:paywalls:preview --file ${a} \xB7 palbase purchases:paywalls:put ${JSON.stringify(n)} --file ${a} --revision 0`)})}},Bc=[dv,uv];import{writeFile as mv,mkdir as hv}from"node:fs/promises";import{dirname as gv}from"node:path";var fv=/^[a-z0-9_-]{1,64}$/;function Un(e){let t=e.args[0];if(t===void 0||t.trim()==="")throw m("the placement's key is required");if(!fv.test(t))throw m(`${JSON.stringify(t)} is not a placement's key: lowercase letters, digits, _ and -, at most 64`);return t}async function Vs(e,t){return new Map((await it(e,t)).map(n=>[n.id,n.name]))}var Ws=(e,t)=>t===void 0?"\u2014":e.get(t)??t;function Fc(e){let t=e.field==="attribute"?`attribute ${e.key??"?"}`:e.field==="sdk_version"?`sdk_version (${e.sdk??"?"})`:e.field,n=e.values!==void 0?e.values.join(", "):e.value!==void 0?String(e.value):"";return`${t} ${e.op}${n===""?"":` ${n}`}`}function yv(e,t){return e.paywall_id!==void 0?`paywall ${Ws(t,e.paywall_id)}`:`experiment ${e.experiment_id??"?"}`}function bv(e){let t=[e.starts_at?`from ${D(e.starts_at)}`:"",e.ends_at?`until ${D(e.ends_at)}`:""].filter(Boolean);return t.length===0?"":` (${t.join(" ")} UTC)`}function Wc(e,t,n){t.forEach((s,a)=>{e.out.line(`${a+1}. ${s.name} \u2014 ${s.status??s.state}${bv(s)} \u2192 ${yv(s.target,n)}`);let r=s.conditions??[];r.length===0&&e.out.line(" everyone");for(let i of r)e.out.line(` ${Fc(i)}`)})}var wv={name:"purchases:placements",summary:"the environment's placements: each one's default paywall, its rules and the experiment running on it",details:['An app asks for a placement by its key (pb.purchases.paywall("onboarding")): an experiment running for everyone there decides first, then the first of its rules that holds for the viewer, then its default paywall.'],scope:"environment",async run(e){let t=await y(e),[{placements:n},s]=await Promise.all([u(e,t,"GET","/purchases/placements"),Vs(e,t)]),a=n.map(r=>({...r,paywall:r.paywall_id===void 0?null:Ws(s,r.paywall_id)}));e.out.result({placements:a},()=>{if(a.length===0){e.out.line("No placement yet."),e.out.line("Give one a paywall: palbase purchases:placements:set <placement> --paywall <name>");return}e.out.table(a,[{header:"placement",value:r=>r.key},{header:"default paywall",value:r=>r.paywall??"\u2014"},{header:"rules",value:r=>String(r.rule_count)},{header:"experiment",value:r=>r.experiment_id??"\u2014"},{header:"changed",value:r=>D(r.updated_at)}])})}};function Vc(e,t,n){throw e instanceof x&&e.code==="experiment_running"?new h("experiment_running",`an experiment runs on ${t}: ${n} while it runs`,{hint:"palbase purchases:experiments lists it; stop it or declare its winner first"}):e}var vv={name:"purchases:placements:set",summary:"give a placement its default paywall \u2014 what it shows whoever none of its rules holds for \u2014 making the placement when it is new",usage:"<placement> --paywall <name|id>",details:["The apps asking for it get the paywall's newest version they can draw; a paywall never published is nothing yet: they draw their own until it is."],args:{min:1,max:1},flags:{paywall:{type:"string",value:"name|id",description:"the paywall it shows"}},scope:"environment",async run(e){let t=await y(e),n=Un(e),s=e.flags.paywall;if(s===void 0||s.trim()==="")throw m("--paywall names the paywall the placement shows");let a=await jt(e,t,s);await w(e,t,`show ${a.name} at ${n}`);let r;try{r=await u(e,t,"PUT",`/purchases/placements/${v(n)}`,{paywall_id:a.id},R())}catch(i){if(i instanceof x&&i.code==="too_many_placements")throw new h("too_many_placements","the project has 200 placements, the most it holds: nothing was made",{hint:"delete one no app asks for: palbase purchases:placements:delete <placement>"});Vc(i,n,"the experiment decides what it shows, and its end the default \u2014 it is not changed")}e.out.result({...r,paywall:a.name},()=>{e.out.line(`${n} shows ${a.name}${r.rule_count>0?` to whoever none of its ${r.rule_count} rule(s) holds for`:""}.`),a.published_version===0&&e.out.warn(`${a.name} was never published: the apps draw their own paywall until it is (palbase purchases:paywalls:publish ${JSON.stringify(a.name)})`)})}},_v={name:"purchases:placements:delete",summary:"forget a placement and its rules: the apps that ask for it draw their own paywall",usage:"<placement>",args:{min:1,max:1},scope:"environment",async run(e){let t=await y(e),n=Un(e);await w(e,t,`delete the placement ${n} and its rules`);try{await u(e,t,"DELETE",`/purchases/placements/${v(n)}`,void 0,R())}catch(s){if(s instanceof x&&s.code==="placement_not_found")throw new h("placement_not_found",`the environment has no placement ${n}`,{hint:"palbase purchases:placements lists them"});Vc(s,n,"it is not deleted")}e.out.result({deleted:n},()=>e.out.line(`Deleted ${n}: the apps that ask for it draw their own paywall.`))}};async function Hc(e,t){let n=ge(await _e(e,t,"the rules"),"the rules"),s=Array.isArray(n)?n:typeof n=="object"&&n!==null&&Array.isArray(n.rules)?n.rules:void 0;if(s===void 0)throw m('the rules are { "rules": [ \u2026 ] }, as purchases:placements:rules:get --out writes them, or the list alone');return s.map(a=>{let{status:r,...i}=a;return i})}function Xc(e,t,n){if(t instanceof x&&t.code==="invalid_rules"){let s=(t.body??{}).problems??[];if(s.length>0){e.out.note(`The rules break ${s.length} rule(s):`);for(let a of s)e.out.note(` ${a.path}: ${a.code} \u2014 ${a.message}`)}throw new h("invalid_rules",`${n}: ${t.message}`,{hint:"fix each problem in the file; purchases:placements:preview --rules <file> tries them without saving"})}throw t}var kv={name:"purchases:placements:rules:get",summary:"a placement's rules, in the order they are read \u2014 each with its conditions, schedule and target \u2014 and their revision",usage:"<placement> [--out <palbase/\u2026.json>]",details:['--out writes them to a file to edit ({ "rules": [ \u2026 ] }); rules:put saves the edit on the revision printed.'],args:{min:1,max:1},flags:{out:{type:"string",value:"file",description:"write the rules to this file, under palbase/"}},scope:"environment",async run(e){let t=await y(e),n=Un(e),s;try{s=await u(e,t,"GET",`/purchases/placements/${v(n)}/rules`)}catch(i){throw i instanceof x&&i.code==="placement_not_found"?new h("placement_not_found",`the environment has no placement ${n}`,{hint:`give it a paywall first: palbase purchases:placements:set ${n} --paywall <name>`}):i}let a=e.flags.out;if(a!==void 0){let i=Je(e,"out",a,".json"),o=await ye(e.io.cwd,i);await hv(gv(o),{recursive:!0}),await ye(e.io.cwd,i),await mv(o,`${JSON.stringify({rules:s.rules.map(({status:l,...p})=>p)},null,2)}
671
+ `),e.out.result({file:i,placement:n,revision:s.revision},()=>{e.out.line(`Wrote revision ${s.revision} of ${n}'s ${s.rules.length} rule(s) to ${i}.`),e.out.line(`Save an edit of them: palbase purchases:placements:rules:put ${n} --file ${i} --revision ${s.revision}`)});return}let r=await Vs(e,t);e.out.result(s,()=>{e.out.line(`${n}: revision ${s.revision} of its rules`),s.rules.length===0&&e.out.line("No rule: it shows its default paywall to everyone."),Wc(e,s.rules,r)})}},Sv={name:"purchases:placements:rules:put",summary:"replace a placement's rules \u2014 all of them, in the order they are to be read \u2014 on the revision they were edited from",usage:"<placement> --file <rules.json> --revision <n>",details:["The first rule in effect whose conditions all hold for a viewer shows its target: a published paywall, or the variant of the experiment running on the placement for the rules. At most 50 rules of at most 10 conditions.","A condition reads country, platform, app, app_version, sdk_version, locale, entitlement, first_seen_days or an attribute the app sends; purchases:placements:preview tries rules against a viewer before they are saved (--rules).","Of two people editing at once, the second is told \u2014 the rules moved past the revision they edited \u2014 never overwritten."],args:{min:1,max:1},flags:{file:{type:"string",value:"rules.json",description:"the rules, as rules:get --out wrote them ('-' or none: stdin)"},revision:{type:"string",value:"n",description:"the revision they were edited from, as rules:get printed it"}},scope:"environment",async run(e){let t=await y(e),n=Un(e),s=fe(e.flags.revision,"revision");if(s===void 0)throw m("--revision is the revision the rules were edited from: of two edits at once, the second is told, not saved over the first");let a=await Hc(e,e.flags.file);await w(e,t,`replace ${n}'s rules with ${a.length} rule(s) over revision ${s}`);let r;try{r=await u(e,t,"PUT",`/purchases/placements/${v(n)}/rules`,{rules:a},{"if-match":Us(s),...R()})}catch(o){if(o instanceof x&&o.status===412)throw new h("rules_changed",`${n}'s rules are no longer at revision ${s}: someone saved newer ones since they were read \u2014 nothing was saved`,{hint:`read them again (palbase purchases:placements:rules:get ${n} --out <file>), make the edit on them, and save with that revision`});if(o instanceof x&&o.code==="placement_not_found")throw new h("placement_not_found",`the environment has no placement ${n}`,{hint:`give it a paywall first: palbase purchases:placements:set ${n} --paywall <name>`});Xc(e,o,"the rules were not saved")}let i=await Vs(e,t);e.out.result(r,()=>{e.out.line(`Saved revision ${r.revision} of ${n}'s rules:`),Wc(e,r.rules,i),r.rules.length===0&&e.out.line("No rule: it shows its default paywall to everyone.")})}};function Jc(e){let t=Q(e);if(t===void 0||t.length===0)return;let n={};for(let s of t){let a=s.indexOf("=");if(a<=0)throw m(`--attr ${s}: an attribute is key=value`);let r=Ve(s.slice(a+1));n[s.slice(0,a)]=typeof r=="number"||typeof r=="boolean"?r:s.slice(a+1)}return n}var $v={matched:"decided",unmet:"did not hold",unavailable:"its experiment no longer runs",not_reached:"not reached"},Pv={name:"purchases:placements:preview",summary:"what a placement shows a viewer, and why: the experiment, the rule that decided \u2014 every rule, condition by condition \u2014 or the default",usage:"<placement> [--platform ios|android|web] [--country TR] [--app-version 2.1.0] [--sdk palbase-swift@0.2.0] [--locale tr-TR] [--attr key=value \u2026] [--user <id>] [--rules <file>] [--at <RFC3339>]",details:["The viewer is what its app would send. With --user, the user's entitlements and the days since they were first seen are the project's; the user (else an installation) is who an experiment's variant is drawn for.","--rules tries rules not yet saved (a rules:put file) instead of the saved ones; --at, a moment (a scheduled rule's start). Nothing is counted or changed."],args:{min:1,max:1},flags:{platform:{type:"string",value:"platform",description:"ios, android or web"},country:{type:"string",value:"code",description:"the store's country: ISO 3166-1, two or three letters"},"app-version":{type:"string",value:"version",description:"the app's version"},sdk:{type:"string",value:"name@version",description:"the SDK the app draws with (palbase-swift@0.2.0)"},locale:{type:"string",value:"tag",description:"the viewer's language (BCP 47)"},attr:{type:"string",multiple:!0,value:"key=value",description:"what the app says of the viewer (repeat it, or a comma list)"},user:{type:"string",value:"id",description:"the signed-in user"},rules:{type:"string",value:"file",description:"rules to try instead of the saved ones"},at:{type:"string",value:"RFC 3339",description:"the moment (default: now)"}},scope:"environment",async run(e){let t=await y(e),n=Un(e),s=e.flags.platform;if(s!==void 0&&!["ios","android","web"].includes(s))throw m("--platform is one of ios, android, web");let a,r=e.flags.sdk;if(r!==void 0){let b=r.lastIndexOf("@");if(b<=0||b===r.length-1)throw m(`--sdk ${r}: it is the SDK's name and version, palbase-swift@0.2.0`);a={sdk:r.slice(0,b),sdk_version:r.slice(b+1)}}let i=e.flags.at;if(i!==void 0&&Number.isNaN(Date.parse(i)))throw m(`--at ${i}: a moment, as RFC 3339 writes it (2026-11-01T09:00:00Z)`);let o={...s?{platform:s}:{},...e.flags.country!==void 0?{country:e.flags.country}:{},...e.flags["app-version"]!==void 0?{app_version:e.flags["app-version"]}:{},...a??{},...e.flags.locale!==void 0?{locale:e.flags.locale}:{},...Jc(e.flags.attr)?{attributes:Jc(e.flags.attr)}:{},...e.flags.user!==void 0?{user_id:e.flags.user}:{}},l=e.flags.rules!==void 0?await Hc(e,e.flags.rules):void 0,p={context:o,...i?{at:i}:{},...l?{rules:l}:{}},d;try{d=(await we(e,t,"POST",`/purchases/placements/${v(n)}/resolve-preview`,p,R())).body}catch(b){if(b instanceof x&&b.code==="placement_not_found")throw new h("placement_not_found",`the environment has no placement ${n}`,{hint:"palbase purchases:placements lists them"});if(b instanceof x&&b.code==="invalid_context")throw new h("invalid_context",`the viewer is not one the module reads: ${b.message}`);Xc(e,b,"the rules cannot be tried")}let[c,g]=await Promise.all([Vs(e,t),l??u(e,t,"GET",`/purchases/placements/${v(n)}/rules`).then(b=>b.rules)]),f={experiment:`the experiment running for everyone there (${d.experiment_id??"?"}), variant ${d.variant??"?"}`,rule:`the rule ${d.steps.find(b=>b.rule_id===d.rule_id)?.name??d.rule_id??"?"}${d.variant?`, variant ${d.variant} of its experiment`:""}`,default:"its default paywall: no rule held",none:"nothing: it has no paywall for this viewer, and the app draws its own"};e.out.result({...d,paywall:d.paywall_id===void 0?null:Ws(c,d.paywall_id)},()=>{e.out.line(d.source==="none"?`${n} shows ${f.none}.`:`${n} shows ${Ws(c,d.paywall_id)} \u2014 from ${f[d.source]}.`),d.steps.length>0&&e.out.line(`Its ${l?"rules tried":"rules"}, at ${D(d.at)} UTC:`),d.steps.forEach((b,P)=>{e.out.line(`${P+1}. ${b.name} \u2014 ${b.status}: ${$v[b.outcome]??b.outcome}`);let E=(g.find(k=>k.id!==void 0&&k.id===b.rule_id)??g[P])?.conditions;for(let k of b.conditions??[]){let $=E?.[k.index];e.out.line(` ${k.met?"\u2713":"\u2717"} ${$?Fc($):k.field}${k.unknown?" \u2014 the viewer carries none":""}`)}})})}},Yc=[wv,vv,_v,kv,Sv,Pv];var Av=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Rv=/^[a-z][a-z0-9_]{0,19}$/,Tv=/^[a-z0-9][a-z0-9_-]{0,63}$/;async function Zc(e,t,n){let s=[],a;do{let r=new URLSearchParams({limit:"100",...n?{placement:n}:{},...a?{cursor:a}:{}}),i=await u(e,t,"GET",`/purchases/experiments?${r}`);s.push(...i.experiments),a=i.next_cursor}while(a&&s.length<1e3);return s}async function Lr(e,t){let n=e.args[0];if(n===void 0||n.trim()==="")throw m("the experiment's id or name is required");if(Av.test(n))try{return await u(e,t,"GET",`/purchases/experiments/${v(n.toLowerCase())}`)}catch(i){throw i instanceof x&&i.code==="experiment_not_found"?new h("experiment_not_found",`no experiment ${n}`,{hint:"palbase purchases:experiments lists them"}):i}let s=(await Zc(e,t)).filter(i=>i.name===n),a=s.filter(i=>i.status==="running"),r=a.length===1?a[0]:s.length===1?s[0]:void 0;if(r)return r;throw s.length===0?new h("experiment_not_found",`no experiment ${JSON.stringify(n)}`,{hint:"palbase purchases:experiments lists them"}):new h("ambiguous_experiment",`${s.length} experiments are named ${JSON.stringify(n)}`,{hint:`name one by its id: ${s.map(i=>i.id).join(", ")}`})}function Qc(e,t){let n=e.variants.reduce((s,a)=>s+a.weight,0);return e.variants.map(s=>`${s.key}=${t.get(s.paywall_id)??s.paywall_id} ${Math.round(s.weight/n*100)}%`).join(", ")}function eu(e,t){throw e instanceof x&&e.code==="experiment_ended"?new h("experiment_ended",`${t.name} has ended (${t.status}): nothing was changed`):e}var Ev={name:"purchases:experiments",summary:"the environment's paywall experiments: the placement each runs on, its variants and their shares, and how it ended",usage:"[--placement <placement>]",flags:{placement:{type:"string",value:"placement",description:"one placement's alone"}},scope:"environment",async run(e){let t=await y(e),[n,s]=await Promise.all([Zc(e,t,e.flags.placement),it(e,t)]),a=new Map(s.map(r=>[r.id,r.name]));e.out.result({experiments:n},()=>{if(n.length===0){e.out.line("No experiment yet."),e.out.line("Start one: palbase purchases:experiments:start <placement> --variants <paywall>=50,<paywall>=50");return}e.out.table(n,[{header:"name",value:r=>r.name},{header:"id",value:r=>r.id},{header:"placement",value:r=>`${r.placement}${r.audience==="rules"?" (rules)":""}`},{header:"variants",value:r=>Qc(r,a)},{header:"status",value:r=>r.status==="won"?`won by ${r.winner}`:r.status},{header:"started",value:r=>D(r.started_at)}])})}};function Cv(e){let t=e??[];if(t.length<2||t.length>4)throw m("--variants names 2 to 4 paywalls, each with its weight: --variants aurora=50,cards=50");let n=t.map((s,a)=>{let r=/^(?:([^:=]+):)?([^=]+)=(\d+)$/.exec(s.trim());if(!r)throw m(`--variants ${s}: a variant is <paywall>=<weight>, or <key>:<paywall>=<weight>`);let i=r[1]??"abcd"[a],o=Number(r[3]);if(!Rv.test(i))throw m(`--variants ${s}: a variant's key is lowercase letters, digits and _, at most 20, starting with a letter`);if(o<1||o>1e3)throw m(`--variants ${s}: a weight is 1 to 1000`);return{key:i,paywall:r[2],weight:o}});if(new Set(n.map(s=>s.key)).size!==n.length)throw m("--variants: every variant has a key of its own");return n}var jv={name:"purchases:experiments:start",summary:"start an experiment on a placement: each viewer is shown one of 2 to 4 published paywalls, by weight, the same one every time",usage:"<placement> --variants <paywall>=<weight>,<paywall>=<weight>[,\u2026] [--audience placement|rules] [--name <name>]",details:["audience placement (the default): everyone at the placement, decided before its rules; the placement is made when new and keeps its default for when the experiment ends.","audience rules: only the viewers the placement's rules send to it \u2014 a rule's target names the experiment (purchases:placements:rules:put).","The first variant is what a stop keeps. One experiment runs on a placement at a time; each variant's paywall must be published."],args:{min:1,max:1},flags:{variants:{type:"string",multiple:!0,value:"paywall=weight,\u2026",description:"the paywalls (name or id) and their weights; key:paywall=weight names a key (else a, b, c, d)"},audience:{type:"string",value:"audience",description:"placement (default) or rules"},name:{type:"string",value:"name",description:"the experiment's name (default: the placement and its paywalls)"}},scope:"environment",async run(e){let t=await y(e),n=e.args[0];if(!Tv.test(n))throw m(`${JSON.stringify(n)} is not a placement's key: lowercase letters, digits, _ and -, at most 64`);let s=e.flags.audience;if(s!==void 0&&s!=="placement"&&s!=="rules")throw m("--audience is placement or rules");let a=Cv(Q(e.flags.variants)),r=await it(e,t),i=a.map(d=>{let c=zs(r,d.paywall);if(!c)throw new h("paywall_not_found",`no paywall ${JSON.stringify(d.paywall)}`,{hint:`the project's: ${r.map(g=>g.name).join(", ")||"none yet"}`});if(c.published_version===0)throw new h("unpublished_paywall",`${c.name} was never published: a variant shows a published paywall`,{hint:`palbase purchases:paywalls:publish ${JSON.stringify(c.name)}`});return{key:d.key,paywall_id:c.id,weight:d.weight}});if(new Set(i.map(d=>d.paywall_id)).size!==i.length)throw m("--variants: each variant is a paywall of its own");let o=new Map(r.map(d=>[d.id,d.name])),l=(e.flags.name??`${n}: ${i.map(d=>o.get(d.paywall_id)).join(" vs ")}`).slice(0,100);await w(e,t,`start ${JSON.stringify(l)} on ${n} (${s??"placement"}): ${i.map(d=>`${d.key}=${o.get(d.paywall_id)} \xD7${d.weight}`).join(", ")}`);let p;try{p=await u(e,t,"POST","/purchases/experiments",{name:l,placement:n,variants:i,...s?{audience:s}:{}},R())}catch(d){throw d instanceof x&&d.code==="experiment_running"?new h("experiment_running",`an experiment runs on ${n} already: one runs on a placement at a time`,{hint:"palbase purchases:experiments lists it; stop it or declare its winner first"}):d instanceof x&&d.code==="placement_not_found"?new h("placement_not_found",`the environment has no placement ${n}: an experiment for its rules runs on a placement that is there`,{hint:`palbase purchases:placements:set ${n} --paywall <name>`}):d instanceof x&&d.code==="invalid_experiment"?new h("invalid_experiment",`the experiment was not started: ${d.message}`):d}e.out.result(p,()=>{e.out.line(`Started ${p.name} (${p.id}) on ${n}: ${Qc(p,o)}.`),p.audience==="rules"&&e.out.line(`It shows only the viewers a rule sends it: a rule's target is { "experiment_id": "${p.id}" } (palbase purchases:placements:rules:put ${n}).`),e.out.line(`Its results: palbase purchases:experiments:results ${p.id}`)})}},Ov={name:"purchases:experiments:stop",summary:"stop a running experiment, at once: what it showed becomes its first variant's paywall; its results stay",usage:"<id|name>",args:{min:1,max:1},scope:"environment",async run(e){let t=await y(e),n=await Lr(e,t);await w(e,t,`stop ${n.name} on ${n.placement}: its first variant (${n.variants[0]?.key}) stays`);let s;try{s=await u(e,t,"POST",`/purchases/experiments/${v(n.id)}/stop`,void 0,R())}catch(a){eu(a,n)}e.out.result(s,()=>e.out.line(`Stopped ${s.name}: ${s.placement} shows variant ${s.variants[0]?.key}'s paywall${s.audience==="rules"?" where its rules sent the experiment":""}.`))}},xv={name:"purchases:experiments:winner",summary:"end a running experiment with its winner: the winner's paywall becomes the placement's default \u2014 or, with --as-rule, a rule shows it to everyone",usage:"<id|name> --variant <key> [--as-rule]",details:["For an experiment for everyone at the placement, --as-rule rolls the winner out as a live rule at the top of its rules (to narrow later) and leaves the default as it is. For one for the rules, the rules that targeted it show the winner's paywall.","purchases:experiments:results says which variant leads and whether calling it is sound yet."],args:{min:1,max:1},flags:{variant:{type:"string",value:"key",description:"the winning variant's key"},"as-rule":{type:"boolean",description:"roll the winner out as a rule for everyone, at the top of the placement's rules"}},scope:"environment",async run(e){let t=await y(e),n=e.flags.variant;if(n===void 0||n.trim()==="")throw m("--variant is the winning variant's key");let s=await Lr(e,t);if(!s.variants.some(i=>i.key===n))throw m(`${s.name} has no variant ${n}: its variants are ${s.variants.map(i=>i.key).join(", ")}`);let a=e.flags["as-rule"]===!0;await w(e,t,`end ${s.name} with ${n} as its winner${a?", as a rule for everyone":""}`);let r;try{r=await u(e,t,"POST",`/purchases/experiments/${v(s.id)}/winner`,{variant:n,...a?{as_rule:!0}:{}},R())}catch(i){if(i instanceof x&&i.code==="too_many_rules")throw new h("too_many_rules",`${s.placement} has 50 rules, the most it holds: the winner cannot be added as one \u2014 nothing was changed`,{hint:"declare it without --as-rule, or take a rule out first"});eu(i,s)}e.out.result(r,()=>e.out.line(a?`${r.name} is won by ${n}: a rule at the top of ${r.placement}'s rules shows its paywall to everyone.`:`${r.name} is won by ${n}: ${r.placement} shows its paywall${r.audience==="rules"?" where its rules sent the experiment":""}.`))}};function Lv(e){let t=e.amount_micros/1e6;try{return new Intl.NumberFormat("en",{style:"currency",currency:e.currency}).format(t)}catch{return`${t.toFixed(2)} ${e.currency}`}}var xr=e=>`${(e*100).toFixed(1)}%`,Dv={name:"purchases:experiments:results",summary:"what each variant's viewers did \u2014 exposures, viewers, purchases, conversion, revenue \u2014 each one's chance to convert best, and whether calling the leader is sound",usage:"<id|name> [--window-days <n>] [--sandbox]",details:["A purchase counts for the first variant its buyer saw, within the window after seeing it (7 days unless said): the purchases the stores confirmed, never what an app reports. Revenue is what was paid to date, renewals in, refunds out.","The chance to win is Bayesian (each variant's rate a Beta distribution): how likely each one is best. The leader is called soundly once every variant has been seen enough and its chance is high enough; until then the reasons are said."],args:{min:1,max:1},flags:{"window-days":{type:"string",value:"n",description:"the days after first seeing a variant a purchase counts for it, 1 to 90 (default 7)"},sandbox:{type:"boolean",description:"count the stores' test purchases too"}},scope:"environment",async run(e){let t=await y(e),n=fe(e.flags["window-days"],"window-days");if(n!==void 0&&(n<1||n>90))throw m("--window-days is 1 to 90");let s=await Lr(e,t),a=new URLSearchParams({...n!==void 0?{window_days:String(n)}:{},...e.flags.sandbox===!0?{sandbox:"true"}:{}}),[r,i]=await Promise.all([u(e,t,"GET",`/purchases/experiments/${v(s.id)}/results${a.size>0?`?${a}`:""}`),it(e,t)]),o=new Map(i.map(l=>[l.id,l.name]));e.out.result({...r,name:s.name},()=>{if(e.out.line(`${s.name} on ${s.placement}: ${r.status}${s.winner?`, won by ${s.winner}`:""} \u2014 purchases within ${r.window_days} day(s) of first seeing a variant${r.sandbox?", test purchases included":""}, counted ${D(r.computed_at)}`),e.out.table(r.variants,[{header:"variant",value:l=>`${l.variant}${l.variant===r.leader?" (leads)":""}`},{header:"paywall",value:l=>o.get(l.paywall_id)??l.paywall_id},{header:"weight",value:l=>String(l.weight)},{header:"exposures",value:l=>String(l.exposures)},{header:"viewers",value:l=>String(l.viewers)},{header:"purchases",value:l=>String(l.purchases)},{header:"converted",value:l=>`${l.converters} (${xr(l.conversion_rate)})`},{header:"revenue",value:l=>l.revenue.length===0?"\u2014":l.revenue.map(Lv).join(" + ")},{header:"chance to win",value:l=>xr(l.chance_to_win)}]),r.confident)e.out.line(`Calling ${r.leader} the winner is sound: palbase purchases:experiments:winner ${s.id} --variant ${r.leader}`);else{e.out.line(`Not sound to call yet (each variant seen by ${r.confidence.min_viewers}, the leader at ${xr(r.confidence.min_chance)} to win):`);for(let l of r.confidence.reasons)e.out.line(` ${l.message}`)}})}},tu=[Ev,jv,Ov,xv,Dv];import{X509Certificate as Iv}from"node:crypto";import{mkdir as Nv,readFile as Ir,stat as su,writeFile as au}from"node:fs/promises";import{basename as Mv,dirname as qv,relative as Kv,resolve as Xs,sep as Uv}from"node:path";var Hs={stripe:"Stripe",apple:"App Store",google:"Google Play"};function Lt(e,t,n){let s=e.args[t];if(s===void 0||s.trim()==="")throw m(`${n} is required`);return s}function zv(e){return`"${e}"`}async function ru(e){let t=ge(await _e(e,e.flags.file,"the catalog"),"the catalog");if(typeof t!="object"||t===null||Array.isArray(t))throw m("the catalog is a JSON object: { base_locale, products, entitlements, credits, limits }");if("catalog"in t&&"revision"in t)throw m('the file is a saved revision (catalog:get --json): give its "catalog" alone, as catalog:get --out writes it');return t}function iu(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 ou(e,t,n){if(t instanceof x&&t.code==="invalid_catalog"){let s=(t.body??{}).problems??[];throw s.length>0&&(e.out.note(`The catalog breaks ${s.filter(a=>!a.warning).length} rule(s):`),iu(e,s)),new h("invalid_catalog",`${n}: ${t.message}`,{hint:"fix each problem in the file (palbase purchases:catalog:validate checks it without saving)"})}throw t}function Gv(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 ${D(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(a=>a.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 Bv={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 y(e),n=await u(e,t,"GET","/purchases/catalog"),s=e.flags.out;if(s!==void 0){await au(Xs(e.io.cwd,s),`${JSON.stringify(n.catalog,null,2)}
672
+ `),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,()=>Gv(e,n))}},Jv={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 y(e),n=fe(e.flags.revision,"revision");if(n===void 0)throw m("--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 ru(e);await w(e,t,`save the catalog (${s.products?.length??0} products) over revision ${n}`);let a;try{a=await u(e,t,"PUT","/purchases/catalog",s,{"if-match":zv(n),...R()})}catch(r){if(r instanceof x&&r.status===412)throw new h("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"});ou(e,r,"the catalog was not saved")}e.out.result(a,()=>{e.out.line(`Saved revision ${a.revision} of the catalog.`),e.out.line("Bring the stores to it: palbase purchases:catalog:plan")})}},Fv={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 y(e),n=await ru(e),s;try{s=await u(e,t,"POST","/purchases/catalog/validate",n,R())}catch(a){ou(e,a,"the catalog cannot be read")}if(e.out.result(s,()=>{s.problems.length>0&&iu(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 h("invalid_catalog",`the catalog breaks ${s.problems.filter(a=>!a.warning).length} rule(s)`)}};function Dr(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 Wv(e,t){if(e.out.line(`Plan ${t.id} for revision ${t.revision}: ${t.status}`),t.stripe?Dr(e,Hs.stripe,t.stripe):e.out.line(`${Hs.stripe}: no account connected (palbase purchases:stripe:grant connects one)`),Dr(e,Hs.apple,t.apple),Dr(e,Hs.google,t.google),t.problems.length>0){e.out.line("Warnings of the revision:");for(let a of t.problems)e.out.line(` ${a.path}: ${a.message}${a.channel?` (${a.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 a of n.steps)e.out.line(` ${a.op} ${a.store_id}${a.state?`: ${a.state}`:""}${a.error?` \u2014 ${a.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 Vv={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 y(e),n=await u(e,t,"POST","/purchases/catalog/plans",void 0,R());e.out.result(n,()=>Wv(e,n))}},Hv={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 y(e),n=Lt(e,0,"the plan's id"),s=e.flags.hash;if(s===void 0||s.trim()==="")throw m("--hash is the hash purchases:catalog:plan printed: a confirmation names the plan it saw");let a=await u(e,t,"GET",`/purchases/catalog/plans/${v(n)}`);if(a.status!=="planned")throw new h("plan_not_open",`the plan ${n} is ${a.status}: only a planned one is confirmed`,{hint:"make a new one: palbase purchases:catalog:plan"});let r=a.apple.steps.length;if(a.stripe)await w(e,t,`write ${a.stripe.steps.length} step(s) of revision ${a.revision} to Stripe${r>0?`, and ${r} to App Store Connect`:""}`,"It writes the project's Stripe account, where a price is never changed or removed");else{if(!a.apple.hash||r===0)throw new h("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 w(e,t,`write ${r} step(s) of revision ${a.revision} to App Store Connect`,"It makes the products in App Store Connect, where a product id never goes away")}let i=await u(e,t,"POST",`/purchases/catalog/plans/${v(n)}/confirm`,{hash:s},R());if(e.out.result(i,()=>{e.out.line(`Plan ${i.id}: ${i.status}${i.applied_at?` (${D(i.applied_at)})`:""}.`),i.status==="failed"&&i.error&&e.out.line(`Stripe answered: ${i.error}`),r>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 h("plan_failed",`the plan ${i.id} failed: a new plan picks up where it stopped`)}},Xv={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 y(e),n=Lt(e,0,"the plan's id");await w(e,t,`decline the plan ${n}`);let s=await u(e,t,"POST",`/purchases/catalog/plans/${v(n)}/decline`,void 0,R());e.out.result(s,()=>e.out.line(`Declined the plan ${s.id}.`))}};function nu(e){return e===null?"for life":D(e)}function Yv(e,t){e.out.line(`Customer ${t.customer_id}${t.guest?" (guest)":""}: revision ${t.revision}, as of ${D(t.requested_at)}`);let n=Object.entries(t.entitlements).sort(([s],[a])=>s<a?-1:s>a?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])=>nu(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=>nu(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=>D(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=>D(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=>D(s.resets_at)}]))}var Zv={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 y(e),n=Lt(e,0,"the customer's id (the user's id)"),s=await u(e,t,"GET",`/purchases/customers/${v(n)}`);e.out.result(s,()=>Yv(e,s))}};function Qv(e){let t=e.flags.until,n=e.flags.for;if(t!==void 0&&n!==void 0)throw m("--until or --for, not both");if(t!==void 0){let s=Date.parse(t);if(!Number.isFinite(s))throw m(`--until ${t}: a date or time (2026-12-31, 2026-12-31T18:00:00Z)`);if(s<=e.io.now())throw m(`--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 m(`--for ${n}: a number of hours, days or weeks (12h, 30d, 2w)`);let a=Number(s[1])*{h:1,d:24,w:168}[s[2]];return new Date(e.io.now()+a*36e5).toISOString()}}var e0={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 y(e),n=Lt(e,0,"the customer's id (the user's id)"),s=Lt(e,1,"the entitlement"),a=e.flags.reason?.trim();if(!a)throw m("--reason says why it is given: the grant's record");if(a.length>500)throw m("--reason is 500 characters at most");let r=Qv(e);await w(e,t,`give ${n} ${s} ${r?`until ${D(r)}`:"for life"}`);let i={entitlement:s,reason:a,...r?{expires_at:r}:{}},o=await u(e,t,"POST",`/purchases/customers/${v(n)}/grants`,i,R());e.out.result(o,()=>{e.out.line(`Gave ${o.customer_id} ${o.entitlement} ${o.expires_at?`until ${D(o.expires_at)}`:"for life"} (grant ${o.id}).`),e.out.line(`End it: palbase purchases:customer:revoke ${o.customer_id} ${o.id}`)})}},t0={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 y(e),n=Lt(e,0,"the customer's id (the user's id)"),s=Lt(e,1,"the grant's id");await w(e,t,`end the grant ${s} of ${n}`),await u(e,t,"DELETE",`/purchases/customers/${v(n)}/grants/${v(s)}`,void 0,R()),e.out.result({revoked:s,customer_id:n},()=>e.out.line(`Ended the grant ${s} of ${n}.`))}},n0={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 y(e),n=await u(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 ${D(n.checked_at)})`:""}`),n.connected_at&&e.out.line(`connected ${D(n.connected_at)}${n.connected_by?` by ${n.connected_by}`:""}`),n.action&&e.out.line(`To do: ${n.action}`)})}},s0={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 y(e);await w(e,t,"make a link that connects a Stripe account");let n=await u(e,t,"POST","/purchases/stripe/grants",void 0,R());on(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 ${D(n.expires_at)}.`)})}};function a0(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 Nr(e,t){let n=(await Ce(e,t.project_id)).filter(o=>o.deleted_at===null),s=n.filter(o=>o.platform==="ios"),a=e.flags.app;if(a!==void 0){let o=Ge(n,a);if(o.platform!=="ios")throw m(`${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 r=(await cr(e.io.cwd))?.apps.filter(o=>o.platform==="ios").map(o=>o.id)??[],i=s.filter(o=>r.includes(o.id));if(i.length===1)return i[0];if(s.length===1)return s[0];throw s.length===0?new h("no_ios_app",`${t.project_name} has no iOS app`,{hint:"register it: palbase apps:create ios <name> --team-id <team id>"}):m("name the iOS app the file is for: --app <app>",`the project's iOS apps: ${s.map(o=>o.slug).join(", ")}`)}function r0(e,t){let n=Kv(e.io.cwd,Xs(e.io.cwd,t)).split(Uv).join("/");if(!n.startsWith(`${U}/`)||n.split("/").includes(".."))throw m(`--out ${t}: the file goes under ${U}/, as the CLI writes nothing else in the checkout`,`palbase purchases:storekit --out ${U}/<name>.storekit`);if(!n.endsWith(".storekit"))throw m(`--out ${t}: Xcode takes a StoreKit configuration by its .storekit extension`);return n}function i0(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],[a,r])=>r-s||(n<a?-1:1))[0]?.[0]}var o0={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 y(e),n=e.flags.storefront?.trim().toUpperCase();if(n!==void 0&&!rn.has(n))throw m(`--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 Nr(e,t);if(!/^[A-Za-z][A-Za-z0-9-]{0,38}$/.test(t.env_slug))throw new h("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 h("unexpected_answer",`the project answered app ${JSON.stringify(s.slug)}, which is not one`);let a=e.flags.out!==void 0?r0(e,e.flags.out):`${U}/environments/${t.env_slug}/${s.slug}.storekit`,r=await u(e,t,"GET","/purchases/offerings?channel=ios");if(!r.products.some($=>($.channels.ios?.plans.length??0)>0))throw new h("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 ye(e.io.cwd,a),o,l;try{o=await Ir(i,"utf8");let $=JSON.parse(o);typeof $.settings=="object"&&$.settings!==null&&!Array.isArray($.settings)&&(l=$.settings)}catch{}let p=typeof l?._storefront=="string"&&rn.has(l._storefront)?l._storefront:void 0,d=i0(r);n??=p??(d!==void 0?qd(d):void 0)??"USA";let c=zd(r,{seed:`${t.env_ref}/${s.id}`,storefront:n,settings:l}),g=o!==c.text;g&&(await Nv(qv(i),{recursive:!0}),await ye(e.io.cwd,a),await au(i,c.text));for(let $ of c.notes)e.out.warn($);let f=t.env_slug===xe,b=a0(e),P=[`In Xcode: Product \u203A Scheme \u203A Edit Scheme\u2026 \u203A Run \u203A Options \u203A StoreKit Configuration \u2014 choose ${a} (not listed yet? add it from that menu).`,f?`${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>${b}`,`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 ${a}`:""}${b}`],E=c.counts,k=[E.subscriptions>0?`${E.subscriptions} subscription${E.subscriptions===1?"":"s"} in ${E.groups} group${E.groups===1?"":"s"}`:"",E.products>0?`${E.products} in-app purchase${E.products===1?"":"s"}`:"",E.non_renewing>0?`${E.non_renewing} non-renewing subscription${E.non_renewing===1?"":"s"}`:""].filter($=>$!=="").join(", ");e.out.result({file:a,app:s.slug,environment:t.env_slug,revision:r.revision,storefront:c.storefront,currency:c.currency,locale:c.locale,...E,written:g,next:P},()=>{e.out.line(`${g?"Wrote":"Up to date:"} ${a} \u2014 ${k}, priced in ${c.currency} (storefront ${c.storefront}), catalog revision ${r.revision}.`),e.out.line(""),P.forEach(($,O)=>e.out.line(`${O+1}. ${$}`))})}},l0=/^SubscriptionKey_([A-Z0-9]{10})\.p8$/,p0=/^AuthKey_[A-Z0-9]{10}\.p8$/,d0=8192,c0=16384;function lu(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 ${D(t.checked_at)})`:""}`);let s=t.notifications;s&&e.out.line(`notifications: ${s.url} \u2014 ${s.bound?`bound ${D(s.bound_at)}`:`not bound${s.error?`: ${s.error}`:""}`}; Apple's TEST notification ${s.verified_at?`arrived ${D(s.verified_at)}`:"not arrived yet"}`),t.connected_at&&e.out.line(`connected ${D(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 ${D(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 u0={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 y(e),n=await u(e,t,"GET","/purchases/apple");e.out.result(n,()=>lu(e,n))}};function m0(e,t){let n=e.environments.find(s=>s.ref===t.env_ref)?.ios?.bundle;if(n===void 0)throw new h("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 h0={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 y(e);if(e.flags["print-link"]===!0){if(e.flags["key-file"]!==void 0)throw m("--print-link or --key-file, not both");await w(e,t,"make a link that connects an In-App Purchase key");let f=await u(e,t,"POST","/purchases/apple/grants",void 0,R());on(e,t,f.connect_url,"the connect link"),e.out.result({connect_url:f.connect_url,expires_at:f.expires_at},()=>{e.out.line(f.connect_url),e.out.note(`Open it in a browser, pick the app and the .p8 file; it works once, until ${D(f.expires_at)}.`)});return}let n=e.flags["key-file"];if(n===void 0)throw m("--key-file names the In-App Purchase key's .p8 file","or --print-link for a link a browser opens");let s=Mv(n),a=e.flags["key-id"];if(a===void 0){if(p0.test(s))throw m(`${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(a=l0.exec(s)?.[1],a===void 0)throw m(`--key-id is needed: ${s} is not named SubscriptionKey_<KEYID>.p8`)}let r;try{let f=Xs(e.io.cwd,n);if((await su(f)).size>d0)throw new h("not_a_key",`${n} is larger than a .p8 key`);r=await Ir(f,"utf8")}catch(f){throw f instanceof h?f:new h("unreadable_file",`${n} could not be read: ${f instanceof Error?f.message:String(f)}`)}if(!r.includes("-----BEGIN PRIVATE KEY-----"))throw new h("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 f=await u(e,t,"GET","/stores/apple");if(i=f.connected?f.issuer_id:void 0,i===void 0)throw m("--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 Nr(e,t),l=m0(o,t);await w(e,t,`connect the In-App Purchase key ${a} for ${l}`);let p=await u(e,t,"POST","/purchases/apple/grants",void 0,R()),d=on(e,t,p.connect_url,"the connect link"),c={grant:p.grant,issuer_id:i,key_id:a,private_key:r,bundle_id:l},g=await Ds(e,`${d}/purchases/apple/connect`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json"},body:JSON.stringify(c)},"connecting the In-App Purchase key");e.out.result(g,()=>{e.out.line(`Connected key ${a} for ${l} to ${B(t)}.`),lu(e,g),e.out.line("Check what else the App Store needs: palbase purchases:doctor")})}},g0={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 y(e);await w(e,t,"disconnect the In-App Purchase key"),await u(e,t,"DELETE","/purchases/apple",void 0,R()),e.out.result({disconnected:!0},()=>e.out.line(`Disconnected the In-App Purchase key from ${B(t)}.`))}};async function f0(e,t){let n=(await Ce(e,t.project_id)).find(s=>s.deleted_at===null&&s.environments.some(a=>a.ref===t.env_ref));return n===void 0?t.env_slug===xe:(await e.studio.get(`/environments/${t.env_ref}/apps/${n.id}/config`)).environment.production}async function y0(e,t){let n;try{let r=Xs(t,e);if((await su(r)).size>c0)throw new h("not_a_certificate",`${e} is larger than Xcode's certificate`);n=await Ir(r)}catch(r){throw r instanceof h?r:new h("unreadable_file",`${e} could not be read: ${r instanceof Error?r.message:String(r)}`)}let s;try{s=new Iv(n)}catch{throw new h("not_a_certificate",`${e} is not a certificate`,{hint:"the one Xcode saves: select the .storekit file, then Editor \u203A Save Public Certificate"})}let a=n.toString("latin1");return{certificate:a.includes("-----BEGIN CERTIFICATE-----")?a.trim():n.toString("base64"),x509:s}}var b0={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 y(e),n=e.flags.remove===!0,s=e.args[0];if(n===(s!==void 0))throw m("name the certificate file, or --remove","palbase purchases:apple:xcode-certificate StoreKitTestCertificate.cer");if(n){await w(e,t,"stop taking Xcode's StoreKit Testing purchases"),await u(e,t,"DELETE","/purchases/apple/xcode-certificate",void 0,R()),e.out.result({removed:!0},()=>e.out.line(`${B(t)} no longer takes Xcode's purchases; those it took stay.`));return}let a=await y0(s,e.io.cwd);if(await f0(e,t))throw new h("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 w(e,t,`take the purchases Xcode signs with ${a.x509.subject.replaceAll(`
673
+ `,", ")}`);let r=await u(e,t,"PUT","/purchases/apple/xcode-certificate",{certificate:a.certificate},R());e.out.result(r,()=>{e.out.line(`${B(t)} takes the purchases Xcode signs with ${r.subject}, until ${D(r.not_after)} (sha256 ${r.fingerprint.slice(0,16)}\u2026).`),e.out.line("Run the app from Xcode and buy: each purchase is a test purchase of this environment.")})}},w0=new Set(["sandbox_access","xcode_certificate"]),v0={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 y(e),n=e.flags.app!==void 0?await Nr(e,t):void 0,s=n?`?${new URLSearchParams({app:n.id})}`:"",a=c=>c.then(g=>({value:g}),g=>({error:g})),[r,i]=await Promise.all([a(u(e,t,"GET",`/stores/apple/readiness${s}`)),a(u(e,t,"GET","/purchases/apple/readiness"))]),o=(c,g)=>{if(!(g instanceof x))throw g;return{module:c,id:`${c}_unanswered`,ok:!1,informational:!1,message:`The ${c} module could not answer: ${g.message}`,fix:g.status===503||g.status===502?"palbase purchases:doctor (again, in a moment)":""}},l=[..."value"in r?r.value.checks.map(c=>({module:"stores",id:c.id,ok:c.ok,informational:!1,message:c.message,fix:c.fix})):[o("stores",r.error)],..."value"in i?i.value.checks.map(c=>({module:"purchases",id:c.id,ok:c.ok,informational:w0.has(c.id),message:c.message,fix:c.fix})):[o("purchases",i.error)]],p=l.filter(c=>!c.ok&&!c.informational),d=p.length===0;return e.out.result({ready:d,checks:l,stores:"value"in r?r.value:null,purchases:"value"in i?i.value:null},()=>{let c=(g,f)=>{e.out.line(f);for(let b of l.filter(P=>P.module===g))e.out.line(` ${b.informational?"i":b.ok?"\u2713":"\u2717"} ${b.message}`),(!b.ok||b.informational)&&b.fix&&e.out.line(` \u2192 ${b.fix}`)};c("stores","App Store Connect (stores)"),c("purchases","In-App Purchases (purchases)"),e.out.line(""),e.out.line(d?`Ready: ${B(t)} sells in the App Store.`:`${p.length} of ${l.filter(g=>!g.informational).length} checks fail: fix each, then run palbase purchases:doctor again.`)}),d?void 0:1}},pu=[Bv,Jv,Fv,Vv,Hv,Xv,Zv,e0,t0,n0,s0,o0,u0,h0,g0,b0,v0];import{readdir as _0,readFile as k0,stat as S0}from"node:fs/promises";import{join as $0,resolve as P0}from"node:path";var A0=1e3,du=900*1024,R0="palbase-argon2id";function T0(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 m(`${t} holds a pepper: it goes on stdin, not in a file`);let a=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:a}}}catch(s){if(s instanceof Error&&s.name==="CliError")throw s}return{users:C0(e,t)}}function E0(e){let t=[],n=[],s=0;for(let a of e){let r=Buffer.byteLength(JSON.stringify(a))+1;if(r>du)throw new h("user_too_large",`the user ${String(a.id??"")} is larger than one call takes (${r} bytes)`);(n.length===A0||s+r>du)&&(t.push(n),n=[],s=0),n.push(a),s+=r}return n.length>0&&t.push(n),t}function C0(e,t){let n=e.trim();if(n==="")return[];if(n.startsWith("["))try{let s=JSON.parse(n);if(Array.isArray(s)&&s.every(a=>typeof a=="object"&&a!==null&&!Array.isArray(a)))return s}catch{}return n.split(`
674
+ `).flatMap((s,a)=>{if(s.trim()==="")return[];try{let r=JSON.parse(s);if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("not an object");return[r]}catch(r){throw m(`${t}:${a+1} is not a user (one JSON object a line): ${r instanceof Error?r.message:String(r)}`)}})}var cu={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 y(e),n=e.args[0],s=P0(e.io.cwd,n),a;try{a=(await S0(s)).isDirectory()?(await _0(s)).filter(c=>/^users-\d+\.json$/.test(c)).sort().map(c=>$0(s,c)):[s]}catch(c){throw m(`${n} could not be read: ${c instanceof Error?c.message:String(c)}`)}let r=[],i;for(let c of a){let g=T0(await k0(c,"utf8"),c);if(r.push(...g.users),g.hash){if(i&&JSON.stringify(i)!==JSON.stringify(g.hash))throw m(`${c} names another hash than the files before it`);i=g.hash}}if(r.length===0)throw m(`${n} holds no user`);let o=r.some(c=>c.password_hash!==void 0||Array.isArray(c.recovery_code_hashes)&&c.recovery_code_hashes.length>0),l;if(o){if(e.flags["pepper-stdin"]!==!0)throw m("these users carry password hashes: give the source's pepper with --pepper-stdin",`palbase auth:import ${n} --pepper-stdin < pepper.txt`);l={...i??{algorithm:R0},pepper:await $e(e,"the source's pepper")}}await w(e,t,`bring ${r.length} user${r.length===1?"":"s"} into ${B(t)}'s auth`);let p={imported:0,errors:[],sessions_imported:0,sessions_skipped:0},d=0;for(let c of E0(r)){let g=await u(e,t,"POST","/admin/users/import",{...l?{hash:l}:{},users:c});p.imported+=g.imported,p.sessions_imported+=g.sessions_imported,p.sessions_skipped+=g.sessions_skipped;for(let f of g.errors??[])p.errors.push({...f,index:f.index+d});d+=c.length,d<r.length&&e.out.note(`${d} of ${r.length}\u2026`)}if(e.out.result(p,()=>{e.out.line(`Imported ${p.imported} of ${r.length} users (${p.sessions_imported} open sessions; ${p.sessions_skipped} ended ones left behind).`);for(let c of p.errors.slice(0,50))e.out.line(` ${c.id??`#${c.index+1}`}: ${c.error} \u2014 ${c.message}`);p.errors.length>50&&e.out.line(` \u2026and ${p.errors.length-50} more (--json lists every one)`)}),p.errors.length>0)throw new h("import_incomplete",`${p.errors.length} user${p.errors.length===1?" was":"s were"} not imported`)}};import{writeFile as uu}from"node:fs/promises";import{resolve as mu}from"node:path";var j0=/^usr_[A-Za-z0-9-]{1,64}$/;function mn(e){if(!j0.test(e))throw m(`${JSON.stringify(e)} is not a user's id (usr_\u2026)`,"palbase auth:users:list shows them");return e}function zn(e,t,n,s){if(e===void 0)return;let a=Number(e);if(!Number.isInteger(a)||a<n||a>s)throw m(`--${t} is a whole number from ${n} to ${s}`);return a}function hu(e,t){if(e===void 0)return;let n=/^(\d+)([smhd])$/.exec(String(e));if(!n)throw m(`--${t} is a span: 30m, 2h, 7d`);return Number(n[1])*{s:1,m:60,h:3600,d:86400}[n[2]]}var ot=e=>e?e.slice(0,16).replace("T"," "):"";function Mr(e){let t=[e.email_verified===!1?"unverified":"",e.banned?`banned${e.ban_reason?` (${N(e.ban_reason)})`:""}`:"",e.is_test?"test":"",e.has_mfa?"mfa":""].filter(Boolean);return`${N(e.id)} ${N(e.email??e.phone??"")}${t.length?` [${t.join(", ")}]`:""}`}var O0={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 y(e),n=zn(e.flags.limit,"limit",1,1e4)??100,s=[],a;do{let r=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...e.flags.banned===!0?{banned:"true"}:{}}),i=await u(e,t,"GET",`/admin/users?${r}`);s.push(...i.users),a=i.next_cursor||void 0}while(a&&s.length<n);e.out.result({users:s},()=>{s.length===0?e.out.line("No user yet."):e.out.table(s,[{header:"id",value:r=>r.id},{header:"email",value:r=>N(r.email??r.phone??"")},{header:"verified",value:r=>r.email_verified?"yes":"no"},{header:"last sign-in",value:r=>ot(r.last_login_at)},{header:"notes",value:r=>[r.banned?"banned":"",r.is_test?"test":"",r.has_mfa?"mfa":""].filter(Boolean).join(", ")}])})}},x0={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 y(e),n=mn(e.args[0]),[s,a]=await Promise.all([u(e,t,"GET",`/admin/users/${v(n)}`),u(e,t,"GET",`/admin/users/${v(n)}/roles`).then(r=>Array.isArray(r)?r:r.roles??[])]);e.out.result({...s,roles:a},()=>{e.out.line(Mr(s)),e.out.line(` signs in with ${s.providers?.length?N(s.providers.join(", ")):"a password"}; made ${N(ot(s.created_at))}, last ${N(ot(s.last_login_at))||"never"}`),e.out.line(` roles: ${a.length?a.map(N).join(", "):"none"}`)})}},L0={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 y(e),n=await $e(e,"the password");await w(e,t,`make the user ${e.args[0]}`);let s=await u(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 ${Mr(s)}.`))}};function Gn(e,t,n,s,a,r={}){return{name:e,summary:t,usage:"<user-id>"+(r&&"reason"in r?" [--reason <text>]":""),args:{min:1,max:1},flags:r,scope:"environment",async run(i){let o=await y(i),l=mn(i.args[0]);await w(i,o,n(l)),await s(i,o,l),i.out.result({user:l,done:e},()=>i.out.line(a(l)))}}}var D0=Gn("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)=>u(e,t,"DELETE",`/admin/users/${v(n)}`),e=>`Deleted ${e}.`),I0=Gn("auth:users:ban","ban a user: their sessions end and they cannot sign in until unbanned",e=>`ban the user ${e}`,(e,t,n)=>u(e,t,"POST",`/admin/users/${v(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)"}}),N0=Gn("auth:users:unban","let a banned user sign in again",e=>`unban the user ${e}`,(e,t,n)=>u(e,t,"POST",`/admin/users/${v(n)}/unban`,{}),e=>`${e} may sign in again.`),M0=Gn("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)=>u(e,t,"POST",`/admin/users/${v(n)}/sessions/revoke-all`,{}),e=>`Every session of ${e} is ended.`),q0={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 y(e),n=await u(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 a of s)e.out.line(Mr(a))})}},K0={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 y(e),n=zn(e.flags.count,"count",1,100)??1;await w(e,t,`make ${n} test user(s)`);let s=await u(e,t,"POST","/admin/test-users",{count:n,with_tokens:e.flags.token===!0},R());e.out.result(s,()=>{for(let a of s.users)e.out.line(`${N(a.user_id)} ${N(a.email??"")} password ${N(a.password??"")}`),a.access_token&&e.out.line(` access token: ${N(a.access_token)}`);e.out.line("Shown this once.")})}},U0=Gn("auth:test-users:delete","delete a test user",e=>`delete the test user ${e}`,(e,t,n)=>u(e,t,"DELETE",`/admin/test-users/${v(n)}`),e=>`Deleted the test user ${e}.`),z0={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 y(e),n=mn(e.args[0]),s=await u(e,t,"GET",`/flags/users/${v(n)}`);e.out.result({user:n,values:s.values},()=>{let a=Object.keys(s.values??{});a.length===0&&e.out.line("The environment has no flag yet.");for(let r of a.sort())e.out.line(`${N(r)} = ${JSON.stringify(s.values[r])}`)})}},G0={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 y(e),n=mn(e.args[0]),s=e.args[1],a=Ve(e.args[2]);await w(e,t,`give ${n} the value ${JSON.stringify(a)} of ${s}`),await u(e,t,"PUT",`/flags/users/${v(n)}/${v(s)}`,{value:a}),e.out.result({user:n,key:s,value:a},()=>e.out.line(`${n} gets ${s} = ${JSON.stringify(a)}.`))}},B0={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 y(e),n=mn(e.args[0]),s=e.args[1];await w(e,t,s?`take ${n}'s own value of ${s} away`:`take every value of ${n}'s own away`),await u(e,t,"DELETE",s?`/flags/users/${v(n)}/${v(s)}`:`/flags/users/${v(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 J0(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 a=s.indexOf("=");if(a<=0)throw m(`--var is name=value, not ${JSON.stringify(s)}`);n[s.slice(0,a)]=s.slice(a+1)}return n}var F0={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 m("notify:send email <to> or notify:send sms <to>");let s=await y(e),a=e.flags,r=J0(a.var),i={to:n,...a.template?{template:a.template}:{},...r?{variables:r}:{},...a.locale?{locale:a.locale}:{}};if(t==="email"){if(!a.template&&(a.subject===void 0||a.text===void 0&&a.html===void 0))throw m("an email is a --template, or a --subject with --text or --html");Object.assign(i,a.subject!==void 0?{subject:a.subject}:{},a.text!==void 0?{text:a.text}:{},a.html!==void 0?{html:a.html}:{})}else{if(!a.template&&a.body===void 0)throw m("a text is a --template or a --body");a.body!==void 0&&(i.body=a.body)}await w(e,s,`send ${t==="email"?"an email":"a text"} to ${n}`);let o=await u(e,s,"POST",`/notify/${t}`,i,R());e.out.result(o,()=>e.out.line(o.skipped?`Not sent to ${N(n)}: the user does not take this category (${N(o.message_id??"")}).`:`Queued to ${N(n)} (${N(o.message_id??"")}): palbase notify:messages says what became of it.`))}},W0={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 y(e),n=zn(e.flags.limit,"limit",1,100)??20,s=await u(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:a=>ot(a.created_at)},{header:"channel",value:a=>a.channel},{header:"to",value:a=>N(a.recipient_hint??"")},{header:"template",value:a=>N(a.template??"")},{header:"status",value:a=>`${a.status}${a.error?` (${N(a.error)})`:""}`},{header:"id",value:a=>a.id}])})}},V0={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 y(e),n=await u(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=>N(s.via.join(" \u2192 "))||"-"},{header:"reports",value:s=>s.reports?"yes":"no"},{header:"why / what to do",value:s=>N([s.because,s.remedy].filter(Boolean).join(" \u2014 "))}]))}},H0={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 y(e),n=zn(e.flags.limit,"limit",1,1e4)??50,s=[],a;do{let r=new URLSearchParams({limit:String(Math.min(100,n-s.length)),...a?{cursor:a}:{},...typeof e.flags.event=="string"?{event_type:e.flags.event}:{}}),i=await u(e,t,"GET",`/admin/audit?${r}`);s.push(...i.events),a=i.next_cursor||void 0}while(a&&s.length<n);e.out.result({events:s},()=>{s.length===0?e.out.line("Nothing recorded yet."):e.out.table(s,[{header:"when",value:r=>ot(r.created_at)},{header:"event",value:r=>N(r.event_type)},{header:"result",value:r=>N(r.result??"")},{header:"by",value:r=>N(r.actor?.email??r.actor?.user_id??"")},{header:"from",value:r=>N(r.actor?.ip??"")},{header:"on",value:r=>N([r.target_type,r.target_id].filter(Boolean).join(" "))}])})}},X0={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 y(e),n=await u(e,t,"GET","/admin/audit/verify");if(!n.valid){let s=n.broken_at?` at event ${N(n.broken_at.event_id??"?")} (number ${(n.broken_at.index??0)+1} of ${n.total}, oldest first)`:"";throw new h("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.`))}},Y0={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 m("--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 m(`--${i} is a time: 2026-10-01T00:00:00Z`)}let n=await y(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 a=await u(e,n,"GET",`/admin/audit/export?${s}`),r=typeof a=="string"?a:t==="ndjson"&&Array.isArray(a)?a.map(i=>JSON.stringify(i)).join(`
675
+ `):JSON.stringify(a);typeof e.flags.out=="string"?(await uu(mu(e.io.cwd,e.flags.out),r.endsWith(`
676
+ `)||r===""?r:`${r}
677
+ `,{mode:384}),e.out.result({file:e.flags.out,bytes:Buffer.byteLength(r)},()=>e.out.line(`Wrote the audit trail to ${e.flags.out} (${Buffer.byteLength(r)} bytes).`))):e.io.stdout(r.endsWith(`
678
+ `)||r===""?r:`${r}
679
+ `)}},Z0={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 y(e),n=await u(e,t,"GET","/admin/signing-keys");e.out.result(n,()=>{e.out.table(n.keys,[{header:"kid",value:s=>N(s.kid)+(s.kid===n.active_kid?" (signing)":"")},{header:"status",value:s=>N(s.status)},{header:"algorithm",value:s=>N(s.algorithm)},{header:"created",value:s=>ot(s.created_at)},{header:"retired",value:s=>ot(s.retired_at)}]),n.next_rotation&&e.out.line(`The next key starts signing ${ot(n.next_rotation)}.`)})}},Q0={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 y(e);await w(e,t,"rotate the token signing key");let n=await u(e,t,"POST","/admin/signing-keys/rotate");e.out.result(n,()=>e.out.line(`Tokens are signed with ${N(n.new_kid)} from now on; the old key goes on verifying the tokens it signed.`))}},e_={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 y(e),n=mn(e.args[0]),s=await u(e,t,"GET",`/admin/users/${v(n)}/export`),a=`${JSON.stringify(s,null,2)}
680
+ `;typeof e.flags.out=="string"?(await uu(mu(e.io.cwd,e.flags.out),a,{mode:384}),e.out.result({file:e.flags.out},()=>e.out.line(`Wrote ${n}'s data to ${e.flags.out}.`))):e.io.stdout(a)}},t_={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 y(e),[n,s,a]=e.args,r=a===void 0?null:Ve(a);await w(e,t,`broadcast ${s} on ${n}`);let i=await u(e,t,"POST","/realtime/broadcast",{messages:[{topic:n,event:s,payload:r}]});e.out.result(i??{sent:1},()=>e.out.line(`Broadcast ${N(s)} on ${N(n)}.`))}},n_={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 y(e),n=new URLSearchParams({window_seconds:String(hu(e.flags.window,"window")??86400),limit:String(zn(e.flags.limit,"limit",1,1e3)??50),...e.flags.name?{name:e.flags.name}:{},...e.flags.user?{distinct_id:e.flags.user}:{}}),s=await u(e,t,"GET",`/analytics/events?${n}`);e.out.result(s,()=>{s.events.length===0&&e.out.line("No event in the window.");for(let a of s.events)e.out.line(`${N(ot(a.timestamp??a.ts))} ${N(a.name)} ${N(a.distinct_id??"")}${a.properties&&Object.keys(a.properties).length?` ${N(JSON.stringify(a.properties)).slice(0,200)}`:""}`)})}},s_={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 y(e),n=await u(e,t,"GET",`/analytics/summary?window_seconds=${hu(e.flags.window,"window")??86400}`);e.out.result(n,()=>{for(let[s,a]of Object.entries(n))e.out.line(`${N(s)}: ${typeof a=="object"?JSON.stringify(a):N(String(a))}`)})}},gu=[O0,x0,L0,D0,I0,N0,M0,e_,H0,X0,Y0,Z0,Q0,q0,K0,U0,z0,G0,B0,F0,W0,V0,t_,n_,s_];import{mkdtemp as a_,rm as r_}from"node:fs/promises";import{tmpdir as i_}from"node:os";import{extname as o_,join as fu}from"node:path";async function Bn(e,t,n){if(n!==void 0)return n;let{body:s}=await Oe(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 h("no_database","the environment has no database",{hint:"palbase databases:create opens one"}):m("which database? name it",`the environment's databases: ${s.databases.map(a=>a.name).join(", ")}`)}async function lt(e,t,n,s,a){let{body:r}=await Oe(e.studio,t.env_ref,"GET",`/databases/${encodeURIComponent(n)}/credentials`),i=(await e.state.databasePasswords(t.env_ref,n))[s];if(!i)throw new h("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"?r.owner:r.app,l=new URL(a?o.pooled_url:o.url);return l.password="",{url:l.toString(),password:i,role:s}}function l_(e){let t=new URL(e.url);return t.password=encodeURIComponent(e.password),t.toString()}var yu={type:"string",value:"role",description:"owner (migrations, dumps) or app (what the machines use); default owner"};function bu(e){let t=e.flags.role??"owner";if(t!=="owner"&&t!=="app")throw m("--role is owner or app");return t}var wu={PGSSLROOTCERT:"system"};async function hn(e,t,n,s,a=!1){let r=await e.io.run(t,n,{env:{PGPASSWORD:s.password,...wu,PGAPPNAME:"palbase-cli"},inherit:a});if(r.code===127)throw new h("missing_tool",`${t} is not installed here`,{hint:"install the Postgres 18 client tools (brew install postgresql@18, apt install postgresql-client-18)"});return r}var vu={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:yu,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 y(e),n=await Bn(e,t,e.args[0]),s=await lt(e,t,n,bu(e),e.flags.pooled===!0),a=e.flags["with-password"]===!0?l_(s):s.url;e.out.result({database:n,role:s.role,url:a,libpq_env:wu},()=>{e.io.stdout(`${a}
681
+ `),e.out.note("psql, pg_dump and other libpq tools: PGSSLROOTCERT=system (Postgres 16+), or they cannot check the certificate")})}},_u={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:yu,command:{type:"string",short:"c",value:"sql",description:"run this and end"}},scope:"environment",async run(e){let t=await y(e),n=await Bn(e,t,e.args[0]),s=await lt(e,t,n,bu(e),!1),a=e.flags.command;if(a!==void 0){let i=await hn(e,"psql",[s.url,"-v","ON_ERROR_STOP=1","-X","-c",a],s);if(i.stdout&&e.io.stdout(i.stdout),i.code!==0)throw new h("psql_failed",i.stderr.trim()||`psql ended with ${i.code}`);return}if(!e.interactive)throw m("db:shell with no person at the terminal needs --command <sql>");return e.out.note(`${B(t)} \u203A ${n} as ${s.role}`),(await hn(e,"psql",[s.url],s,!0)).code}},ku={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 y(e),n=await Bn(e,t,e.args[0]),s=await lt(e,t,n,"owner",!1),a=e.flags.file??`${n}-${new Date(e.io.now()).toISOString().replaceAll(":","-").slice(0,19)}.dump`,r=await hn(e,"pg_dump",["--format=custom","--no-owner","--no-acl",...e.flags["schema-only"]===!0?["--schema-only"]:[],"--file",a,s.url],s);if(r.code!==0)throw new h("pg_dump_failed",r.stderr.trim()||`pg_dump ended with ${r.code}`);e.out.result({database:n,file:a},()=>e.out.line(`Wrote ${a}.`))}},Su={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 y(e),n=await Bn(e,t,e.flags.db),s=e.args[0];await w(e,t,`load ${s} into the database ${n}`);let a=await lt(e,t,n,"owner",!1);await $u(e,a,s,e.flags.clean===!0),e.out.result({database:n,loaded:s},()=>e.out.line(`Loaded ${s} into ${n}.`))}};async function $u(e,t,n,s){let a=o_(n).toLowerCase()===".sql",r=a?await hn(e,"psql",[t.url,"-v","ON_ERROR_STOP=1","-X","--single-transaction","-f",n],t):await hn(e,"pg_restore",["--no-owner","--no-acl","--exit-on-error","--single-transaction",...s?["--clean","--if-exists"]:[],"--dbname",t.url,n],t);if(r.code!==0)throw new h("restore_failed",r.stderr.trim()||`${a?"psql":"pg_restore"} ended with ${r.code}`)}var Pu={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 m("db:copy needs --from <env> and --to <env>");if(t===n)throw m("--from and --to are two environments");let s=await nt(e),a=s.find(f=>f.slug===t),r=s.find(f=>f.slug===n);if(!a||!r)throw new h("no_such_environment",`the project has no environment ${JSON.stringify(a?n:t)}`);let i=be(a),o=be(r),l=await Bn(e,i,e.flags.db),p=e.flags["to-db"]??l;await w(e,o,`overwrite the database ${p} with ${i.env_slug}'s ${l}`);let d=await lt(e,i,l,"owner",!1),c=await lt(e,o,p,"owner",!1),g=await a_(fu(i_(),"palbase-copy-"));try{let f=fu(g,"copy.dump"),b=await hn(e,"pg_dump",["--format=custom","--no-owner","--no-acl","--file",f,d.url],d);if(b.code!==0)throw new h("pg_dump_failed",b.stderr.trim()||`pg_dump ended with ${b.code}`);await $u(e,c,f,!0)}finally{await r_(g,{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 p_,mkdir as d_,readFile as c_,rename as u_,rm as m_,writeFile as h_}from"node:fs/promises";import{dirname as Au,resolve as g_}from"node:path";var Ru="// palbase:stack:begin",qr="// palbase:stack:end",f_=/^[A-Za-z_$][A-Za-z0-9_$]*$/,Ys=e=>f_.test(e)?e:JSON.stringify(e),Dt=e=>[...new Set(e)].sort();function Zs(e,t){return t.length===0?[` interface ${e} {}`]:[` interface ${e} {`,...t.map(n=>` ${n}`)," }"]}function Kr(e){let t=[...e.buckets].sort((n,s)=>n.name<s.name?-1:n.name>s.name?1:0);return[Ru,'declare module "palbase/backend/stack" {',...Zs("Secrets",Dt(e.secrets).map(n=>`${Ys(n)}: true;`)),"",...Zs("Flags",Dt(e.flags).map(n=>`${Ys(n)}: true;`)),"",...Zs("Buckets",t.map(n=>{let s=Dt(n.variants);return`${Ys(n.name)}: { variants: ${s.length===0?"never":s.map(a=>JSON.stringify(a)).join(" | ")} };`})),"",...Zs("Roles",Dt(e.roles).map(n=>`${Ys(n)}: true;`)),"}",qr].join(`
682
+ `)}function Ur(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
683
  ${t}
663
684
 
664
685
  export {};
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)?"":`
686
+ `;let n=e.indexOf(Ru),s=e.indexOf(qr);if(n>=0&&s>n)return`${e.slice(0,n)}${t}${e.slice(s+qr.length)}`;let a=/^\s*export\s*\{\s*\}\s*;?\s*$/m.test(e)?"":`
666
687
  export {};
667
688
  `;return`${e}${e===""||e.endsWith(`
668
689
  `)?"":`
669
690
  `}${t}
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 Nb(u)).isSymbolicLink())throw new g("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 Kb(o,"utf8")}catch(u){if(u.code!=="ENOENT")throw u}await qb(Cd(o),{recursive:!0});let p=`${o}.${process.pid}.${Date.now()}.tmp`;await Gb(p,Wb(l,zb(i)),{flag:"wx"});try{await Mb(p,o)}catch(u){throw await Ub(p,{force:!0}),u}e.out.result({file:o,...i},()=>{e.out.line(`Wrote ${o} for ${q(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=`{
691
+ ${a}`}var Tu={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 y(e),[n,s,a,r]=await Promise.all([mr(e,t,"production"),u(e,t,"GET","/flags/system"),u(e,t,"GET","/storage/buckets"),u(e,t,"GET","/admin/roles")]),i={secrets:Dt(n.map(d=>d.name).filter(d=>!Ta(d))),flags:Dt(s.map(d=>d.key)),buckets:a.buckets.map(d=>({name:d.name,variants:(d.variants??[]).map(c=>c.name)})),roles:Dt(r.roles.map(d=>d.name))},o=g_(e.io.cwd,e.flags.file??"palbase/palbase-env.d.ts"),l;for(let d of[Au(o),o])try{if((await p_(d)).isSymbolicLink())throw new h("symlink",`${d} is a link: types:pull writes into a file of its own`,{hint:"remove the link, or pass --file <path>"})}catch(c){if(c.code!=="ENOENT")throw c}try{l=await c_(o,"utf8")}catch(d){if(d.code!=="ENOENT")throw d}await d_(Au(o),{recursive:!0});let p=`${o}.${process.pid}.${Date.now()}.tmp`;await h_(p,Ur(l,Kr(i)),{flag:"wx"});try{await u_(p,o)}catch(d){throw await m_(p,{force:!0}),d}e.out.result({file:o,...i},()=>{e.out.line(`Wrote ${o} for ${B(t)}: ${i.secrets.length} secret(s), ${i.flags.length} flag(s), ${i.buckets.length} bucket(s), ${i.roles.length} role(s).`)})}};import{mkdir as y_,stat as b_,writeFile as w_}from"node:fs/promises";import{basename as v_,join as zr,resolve as __}from"node:path";var k_=`{
671
692
  "compilerOptions": {
672
693
  "target": "es2023",
673
694
  "module": "esnext",
@@ -682,22 +703,25 @@ ${r}`}var Od={name:"types:pull",summary:"write the environment's secret, flag, b
682
703
  },
683
704
  "include": ["src/**/*.ts", "tests/**/*.ts", "palbase/*.d.ts"]
684
705
  }
685
- `,Qb=`# The backend as a machine builds it: its dependencies, then its source, run by Bun.
706
+ `,S_=`# The backend as a machine builds it: its dependencies, then its source, run by Bun.
686
707
  FROM oven/bun:1.4.2-alpine
687
708
  WORKDIR /app
688
709
  COPY package.json bun.lock* ./
689
710
  RUN bun install --production
690
711
  COPY tsconfig.json ./
712
+ # The environment's names as types (palbase types:pull): the contract the
713
+ # platform reads from the image type-checks the app with them.
714
+ COPY palbase ./palbase
691
715
  COPY src ./src
692
716
  # Its tests: the platform runs them against each new build before it takes
693
717
  # traffic, when the machine names its test command (bun test ./tests).
694
718
  COPY tests ./tests
695
719
  USER bun
696
720
  CMD ["bun", "run", "src/main.ts"]
697
- `,ew=`node_modules/
721
+ `,$_=`node_modules/
698
722
  # palbase env:pull writes the address and your own key here: never committed.
699
723
  .env.local
700
- `,tw=`import { Controller, Get } from "palbase/backend";
724
+ `,P_=`import { Controller, Get } from "palbase/backend";
701
725
 
702
726
  // What the platform asks before it sends a new version traffic.
703
727
  @Controller("/health", { auth: false })
@@ -707,7 +731,7 @@ export class HealthController {
707
731
  return { ok: true };
708
732
  }
709
733
  }
710
- `,nw=`// The app's own tests. \`bun test ./tests\` runs them here; \`palbase test
734
+ `,A_=`// The app's own tests. \`bun test ./tests\` runs them here; \`palbase test
711
735
  // --users alice,bob --base-url http://localhost:8080 -- bun test ./tests\` runs
712
736
  // them against the app you started, as test users made for the run and deleted
713
737
  // after; the platform runs them against each new build before it takes traffic
@@ -728,7 +752,7 @@ describe.skipIf(!testRun.stack)("the app, over HTTP", () => {
728
752
  expect(await api.get("/health")).toEqual({ ok: true });
729
753
  });
730
754
  });
731
- `,sw=`// Each user's notes are theirs alone: row-level security says so, not the
755
+ `,R_=`// Each user's notes are theirs alone: row-level security says so, not the
732
756
  // routes. Run as two of the run's test users (palbase test --users alice,bob;
733
757
  // on the platform, the machine's test users). Their notes go with them: the
734
758
  // run deletes its users, and a deleted user's rows are erased (ownedByUser()).
@@ -745,13 +769,13 @@ describe.skipIf(!testRun.stack)("notes", () => {
745
769
  expect((await api.get<{ title: string }[]>("/notes")).map((n) => n.title)).not.toContain(title);
746
770
  });
747
771
  });
748
- `,rw=e=>`import { Module } from "palbase/backend";
772
+ `,T_=e=>`import { Module } from "palbase/backend";
749
773
  import { HealthController } from "./health.controller.ts";
750
774
  ${e?`import { NotesController } from "./notes/notes.controller.ts";
751
775
  `:""}
752
776
  @Module({ controllers: [HealthController${e?", NotesController":""}] })
753
777
  export class AppModule {}
754
- `,aw=e=>e?`import { PalbaseFactory } from "palbase/backend";
778
+ `,E_=e=>e?`import { PalbaseFactory } from "palbase/backend";
755
779
  import { AppModule } from "./app.module.ts";
756
780
  import schema from "./db/public.ts";
757
781
 
@@ -764,7 +788,7 @@ import { AppModule } from "./app.module.ts";
764
788
  // Serves on $PORT; the project's address and key come from the environment
765
789
  // (palbase env:pull on your computer, the machine's settings on the platform).
766
790
  await PalbaseFactory.create(AppModule);
767
- `,iw=`import { defineSchema, defineTable, index, ownedByUser, policy, text, timestamp, uuid, type TablesOf } from "palbase/backend";
791
+ `,C_=`import { defineSchema, defineTable, index, ownedByUser, policy, text, timestamp, uuid, type TablesOf } from "palbase/backend";
768
792
 
769
793
  // Each user's notes: theirs alone \u2014 row-level security says so, not the routes.
770
794
  const notes = defineTable("notes", {
@@ -785,7 +809,7 @@ export default schema;
785
809
  declare module "palbase/backend/env" {
786
810
  interface Tables extends TablesOf<typeof schema> {}
787
811
  }
788
- `,ow=`import { Body, Controller, Database, Get, Post, User, z, type UserT } from "palbase/backend";
812
+ `,j_=`import { Body, Controller, Database, Get, Post, User, z, type UserT } from "palbase/backend";
789
813
 
790
814
  const CreateNote = z.object({ title: z.string().min(1).max(200) });
791
815
  type CreateNote = z.infer<typeof CreateNote>;
@@ -803,13 +827,13 @@ export class NotesController {
803
827
  return Database.public.notes.findMany({ orderBy: { column: "created_at", direction: "desc" }, limit: 20 });
804
828
  }
805
829
  }
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 g("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 m("--url or --db, not both");let l;try{l=new URL(n)}catch{throw m("--url is a postgres://\u2026 address")}if(l.protocol!=="postgres:"&&l.protocol!=="postgresql:")throw m("--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 m(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:`${q(s)} \u203A ${r}`,ownerUrl:o(a),...i&&{appUrl:o(i)},local:!1,production:s.env_slug===$e,target:s,db:r}}async function Ps(e){let t=e.flags.entry,n=ks(e.io.cwd,t??"src/main.ts");try{await Wr(n)}catch{throw new g("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 g("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 g("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 g(`${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 g(`${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 Ps(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 Ps(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}`:""}`)})}},xd={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 g("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 Ps(e),s=await As(e);if(e.out.note(`Target: ${t.said}`),t.local){if(await gw(e,t),e.flags.reset===!0){if(!e.global.yes){if(!e.interactive)throw new g("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 g("cancelled","nothing was changed")}await mw(e,t)}let o=await dt(e,n,t,"start",s);if(o.code!==0){let p=Ze("start",o,t);throw new g(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 m("--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 g("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.")})}},Nd={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 m("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 Ps(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.`))}},qd={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 g("pull_failed",(r.stderr||r.stdout).trim().split(`
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))}},Kd={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 g("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 g("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 g("seed_failed",`${n} ended with ${s.code}`);e.out.result({target:t.said,seeded:n},()=>e.out.line(`Seeded ${t.said}.`))}};async function gw(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 g("missing_tool","psql is not installed here",{hint:"install the Postgres 18 client tools"});if(s.code!==0)throw new g("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 mw(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 g("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 g("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 Md=[ba,wa,va,io,oo,lo,Dd,ja,Od,Oa,xa,zi,Wi,Fi,Vi,Hi,Yi,Xi,Zi,Qi,eo,to,no,so,ro,ao,po,uo,co,...Ji,hd,...wd,...Tp,...Cp,...Up,...ad,...gd,gp,mp,hp,fp,yp,bp,wp,vp,Sp,Rp,_p,zo,Wo,Fo,Vo,go,ho,fo,vo,_o,So,Po,Ao,$o,Ro,To,Eo,Co,jo,Oo,Do,Io,mo,Lo,xo,qo,Ko,Mo,Uo,Go,Bo,Jo,Pd,Ad,$d,Rd,Ed,Id,Ld,xd,Nd,qd,Kd];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 Ud(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=>$w(t,e),env:e,cwd:process.cwd(),interactive:!!(process.stdin.isTTY&&process.stderr.isTTY)&&e.CI===void 0,configDir:Yr(e,vw()),fetch:Ud(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 Pw=["PATH","Path","PATHEXT","HOME","USERPROFILE","TMPDIR","TEMP","TMP","SystemRoot","LANG","LC_ALL"];function Aw(e){return Object.fromEntries(Pw.flatMap(t=>e[t]===void 0?[]:[[t,e[t]]]))}function $w(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
- `),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===`
830
+ `;function O_(e){let t=v_(e).toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[._-]+|[._-]+$/g,"");return t===""?"backend":t.slice(0,214)}function x_(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:`^${da}`},devDependencies:{"@types/bun":"^1.4.2"}};return{"package.json":`${JSON.stringify(n,null,2)}
831
+ `,"tsconfig.json":k_,Dockerfile:S_,"palbase/palbase-env.d.ts":Ur(void 0,Kr({secrets:[],flags:[],buckets:[],roles:[]})),".gitignore":$_,"src/main.ts":E_(t),"src/app.module.ts":T_(t),"src/health.controller.ts":P_,"tests/health.test.ts":A_,...t?{"src/db/public.ts":C_,"src/notes/notes.controller.ts":j_,"tests/notes.test.ts":R_}:{}}}async function L_(e){try{return await b_(e),!0}catch{return!1}}var Eu={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=__(e.io.cwd,e.args[0]??"."),n=e.flags.db===!0,s=x_(O_(t),n),a=[];for(let i of Object.keys(s))await L_(zr(t,i))&&a.push(i);if(a.length>0)throw new h("already_there",`${a.join(", ")} ${a.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 y_(zr(t,i,".."),{recursive:!0}),await w_(zr(t,i),o,{flag:"wx"});let r=["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: ${r.join(" \u2192 ")}.`)})}};import{access as Br}from"node:fs/promises";import{dirname as D_,join as I_,resolve as Qs}from"node:path";var gn={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 fn(e,t=!1){let n=e.flags.url;if(n!==void 0){if(e.flags.db!==void 0)throw m("--url or --db, not both");let l;try{l=new URL(n)}catch{throw m("--url is a postgres://\u2026 address")}if(l.protocol!=="postgres:"&&l.protocol!=="postgresql:")throw m("--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 y(e),a=e.flags.db;if(a===void 0){let{body:l}=await Oe(e.studio,s.env_ref,"GET","/databases?limit=100");if(l.databases.length!==1)throw m(l.databases.length===0?"the environment has no database: pass --url for one of your own":"which database? --db <name>");a=l.databases[0].name}let r=await lt(e,s,a,"owner",!1),i=t?await lt(e,s,a,"app",!1):void 0,o=l=>{let p=new URL(l.url);return p.password=encodeURIComponent(l.password),p.toString()};return{said:`${B(s)} \u203A ${a}`,ownerUrl:o(r),...i&&{appUrl:o(i)},local:!1,production:s.env_slug===xe,target:s,db:a}}async function ea(e){let t=e.flags.entry,n=Qs(e.io.cwd,t??"src/main.ts");try{await Br(n)}catch{throw new h("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 ta(e){let t=await e.state.readSession();return t?`cli:${t.user.email??t.user.id}`:"cli:local"}async function _t(e,t,n,s,a){let r=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:a}});if(r.code===127)throw new h("missing_tool","bun is not installed here: a Palbase backend runs on Bun",{hint:"https://bun.sh"});let i,o=[];for(let l of r.stdout.split(`
832
+ `))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(...r.stderr.split(`
833
+ `).filter(l=>l.trim()!=="")),{code:r.code,result:i,tail:o.slice(-8)}}function Gr(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 pt(e,t,n){return t.code===75?new h("cut_off",`${e} was cut off: ${Gr(t)}`,{hint:"run it again: it goes on from where it stopped"}):n?.db!==void 0&&/password authentication failed for user "owner"/.test(Gr(t))?new h(`${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 h(`${e}_failed`,`${e}: ${Gr(t)}`)}var Cu={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:{...gn,check:{type:"boolean",description:"end with 1 when the plan has refusals"}},scope:"environment",async run(e){let t=await fn(e),n=await _t(e,await ea(e),t,"plan",await ta(e));if(n.code!==0&&n.code!==1)throw pt("plan",n,t);let s=n.result;if(!s)throw pt("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 a of s.adoption.unforced)e.out.line(` ~ ${a}: row-level security no longer FORCEd on its owner, as v2 made it`);for(let a of s.adoption.unmanaged)e.out.line(` = ${a}: 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 a of s.changes)e.out.line(` + ${a}`);for(let a of s.refusals)e.out.line(` \u2717 ${a}`)}),s.refusals.length>0)return e.flags.check===!0?1:0}},ju={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:gn,scope:"environment",async run(e){let t=await fn(e),n=await _t(e,await ea(e),t,"status",await ta(e));if(n.code!==0)throw pt("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}`:""}`)})}},Ou={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:{...gn,reset:{type:"boolean",description:"with --url: make the schema anew, its data gone"}},scope:"environment",async run(e){let t=await fn(e);if(t.production)throw new h("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 ea(e),s=await ta(e);if(e.out.note(`Target: ${t.said}`),t.local){if(await N_(e,t),e.flags.reset===!0){if(!e.global.yes){if(!e.interactive)throw new h("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 h("cancelled","nothing was changed")}await M_(e,t)}let o=await _t(e,n,t,"start",s);if(o.code!==0){let p=pt("start",o,t);throw new h(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 _t(e,n,t,"complete",s);if(l.code!==0)throw pt("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 m("--reset is for a database of your own (--url): the environment's keeps its data");let a=await _t(e,n,t,"status",s);if(a.code!==0)throw pt("status",a,t);let r=a.result.open;if(r&&r.opened_by!==s)throw new h("migration_open",`${t.said} has a migration open (${r.from??"none"} \u2192 ${r.to}) that ${r.opened_by??"a release"} opened`,{hint:r.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(r){e.out.note(`Rolling back your own open migration (${r.from??"none"} \u2192 ${r.to}) first: stop a backend of yours still on it, or this waits for it.`);let o=await _t(e,n,t,"rollback",s);if(o.code!==0)throw pt("rollback",o,t)}await w(e,t.target,"start the app's schema beside the version serving");let i=await _t(e,n,t,"start",s);if(i.code!==0)throw pt("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.")})}},xu={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:gn,scope:"environment",async run(e){let t=e.args[0];if(t!=="start"&&t!=="complete"&&t!=="rollback")throw m("db:migrate start, complete or rollback");let n=await fn(e);n.target&&await w(e,n.target,`${t} the migration of ${n.said}`);let s=await _t(e,await ea(e),n,t,await ta(e));if(s.code!==0)throw pt(t,s,n);e.out.result({target:n.said,[t]:s.result},()=>e.out.line(`${n.said}: ${t} done.`))}},Lu={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:{...gn,dir:{type:"string",value:"dir",description:"where db/ is written (default: here)"}},scope:"environment",async run(e){let t=await fn(e),n=await q_(e),s=Qs(e.io.cwd,e.flags.dir??"."),a=await e.io.run("bun",[n,"pull",s],{cwd:e.io.cwd,env:{PALGRES_DATABASE_URL:t.ownerUrl}});if(a.code!==0)throw new h("pull_failed",(a.stderr||a.stdout).trim().split(`
834
+ `).pop()??`palgres pull ended with ${a.code}`);e.out.result({target:t.said,written:a.stdout.split(`
835
+ `).filter(r=>r.startsWith("wrote ")).map(r=>r.slice(6))},()=>e.io.stdout(a.stdout))}},Du={name:"db:seed",summary:"run db/seed.ts against a database that is not production",usage:"[--url <postgres-url> | --db <name>] [--file <path>]",flags:{...gn,file:{type:"string",value:"path",description:"the seed script (default db/seed.ts)"}},scope:"environment",async run(e){let t=await fn(e,!0);if(t.production)throw new h("production","seeding writes made-up rows: never into production's database");let n=Qs(e.io.cwd,e.flags.file??"db/seed.ts");try{await Br(n)}catch{throw new h("no_seed",`${n} is not there`)}t.target&&await w(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 h("seed_failed",`${n} ended with ${s.code}`);e.out.result({target:t.said,seeded:n},()=>e.out.line(`Seeded ${t.said}.`))}};async function N_(e,t){let n=[["anon","NOLOGIN"],["authenticated","NOLOGIN"],["service_role","NOLOGIN BYPASSRLS"]].map(([a,r])=>`DO $$ BEGIN CREATE ROLE ${a} ${r}; EXCEPTION WHEN duplicate_object THEN NULL; END $$;`).join(`
836
+ `),s=await e.io.run("psql",[t.ownerUrl,"-v","ON_ERROR_STOP=1","-X","-q","-c",n],{});if(s.code===127)throw new h("missing_tool","psql is not installed here",{hint:"install the Postgres 18 client tools"});if(s.code!==0)throw new h("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 M_(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 h("reset_failed",n.stderr.trim())}async function q_(e){let t=Qs(e.io.cwd);for(;;){let n=I_(t,"node_modules","palgres","src","cli.ts");try{return await Br(n),n}catch{let s=D_(t);if(s===t)throw new h("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 Iu=[ui,mi,hi,tl,nl,sl,Eu,Pi,Tu,Ai,Ci,Ko,Uo,zo,Go,Bo,Fo,Jo,Wo,Vo,Ho,Xo,Yo,Zo,Qo,el,al,rl,il,...qo,cu,...gu,...kd,...$d,...Dd,...Qd,...pu,...wc,Mc,qc,...Bc,...Yc,...tu,od,ld,pd,dd,cd,ud,md,hd,fd,_d,gd,ql,Kl,Ul,zl,ol,pl,dl,hl,gl,fl,bl,wl,vl,_l,kl,Sl,$l,Pl,Al,Rl,Tl,ll,El,Cl,Ol,xl,Ll,Dl,Il,Nl,Ml,vu,_u,ku,Su,Pu,Cu,ju,Ou,xu,Lu,Du];import{spawn as Mu}from"node:child_process";import{constants as G_,homedir as B_}from"node:os";import{createInterface as J_}from"node:readline/promises";var K_=new Set(["UND_ERR_CONNECT_TIMEOUT","ECONNREFUSED","ENOTFOUND","EAI_AGAIN","ENETUNREACH","EHOSTUNREACH","ENETDOWN"]),U_=new Set(["ECONNRESET","UND_ERR_SOCKET","EPIPE","UND_ERR_CLOSED"]);function z_(e){let t=e?.cause,n=t?.code??t?.errors?.[0]?.code;return typeof n=="string"?n:""}function Nu(e,t,n=3){return(async(s,a={})=>{let r=(a.method??"GET").toUpperCase(),i=a.body instanceof ReadableStream;for(let o=1;;o++)try{return await e(s,a)}catch(l){let p=z_(l);if(!(K_.has(p)||U_.has(p)&&(r==="GET"||r==="HEAD"))||i||o>=n||a.signal?.aborted)throw l;await t(300*o)}})}function qu(){let e=process.env;return{stdout:t=>{process.stdout.write(t)},stderr:t=>{process.stderr.write(t)},async readStdin(){if(process.stdin.isTTY)return Y_();let t=[];for await(let n of process.stdin)t.push(n);return Buffer.concat(t).toString("utf8")},async prompt(t){let n=J_({input:process.stdin,output:process.stderr});try{return await n.question(t)}finally{n.close()}},openBrowser:t=>X_(t,e),env:e,cwd:process.cwd(),interactive:!!(process.stdin.isTTY&&process.stderr.isTTY)&&e.CI===void 0,configDir:Hr(e,B_()),fetch:Nu(globalThis.fetch.bind(globalThis),t=>new Promise(n=>setTimeout(n,t))),run:(t,n,s={})=>new Promise(a=>{let r=Mu(t,n,{cwd:s.cwd,env:{...s.envOnly?H_(process.env):process.env,...s.env??{}},stdio:s.inherit?"inherit":["ignore","pipe","pipe"]}),i="",o="",l=s.inherit?W_(r,process.stdin.isTTY===!0):()=>{};r.stdout?.on("data",p=>i+=p.toString("utf8")),r.stderr?.on("data",p=>o+=p.toString("utf8")),r.once("error",p=>{l(),a({code:p.code==="ENOENT"?127:1,stdout:i,stderr:p.message})}),r.once("close",(p,d)=>{l(),a({code:p??(d?128+(G_.signals[d]??0):1),stdout:i,stderr:o})})}),now:()=>Date.now(),sleep:t=>new Promise(n=>setTimeout(n,t))}}var F_=["SIGHUP","SIGINT","SIGTERM"];function W_(e,t){let n=F_.map(s=>{let a=()=>{s==="SIGINT"&&t?void 0:e.kill(s)};return process.on(s,a),[s,a]});return()=>{for(let[s,a]of n)process.off(s,a)}}var V_=["PATH","Path","PATHEXT","HOME","USERPROFILE","TMPDIR","TEMP","TMP","SystemRoot","LANG","LC_ALL"];function H_(e){return Object.fromEntries(V_.flatMap(t=>e[t]===void 0?[]:[[t,e[t]]]))}function X_(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(a=>{try{let r=Mu(n,s,{stdio:"ignore",detached:!0});r.once("error",()=>a(!1)),r.once("spawn",()=>{r.unref(),a(!0)})}catch{a(!1)}})}function Y_(){let e=process.stdin;return new Promise((t,n)=>{let s="",a=i=>{e.off("data",r),e.setRawMode(!1),e.pause(),process.stderr.write(`
837
+ `),i()},r=i=>{for(let o of i.toString("utf8")){if(o==="")return a(()=>t(s));if(o==="")return a(()=>n(new Error("cancelled: nothing was read")));o==="\r"||o===`
814
838
  `?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 ga(process.argv.slice(2),Bd(),Md);
839
+ `:o==="\x7F"||o==="\b"?s=s.slice(0,-1):s+=o}};e.setRawMode(!0),e.resume(),e.on("data",r)})}process.exitCode=await ci(process.argv.slice(2),qu(),Iu);