@vercel/board 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -17
- package/dist/{a2-D_28Pdip.mjs → a2-DngWY2Eb.mjs} +573 -361
- package/dist/{access-CQ39gqEV.mjs → access-CY5xVMIK.mjs} +19 -3
- package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
- package/dist/access.d.mts +4 -4
- package/dist/access.mjs +5 -4
- package/dist/{actors-xPiamLWk.mjs → actors-Bm-ojWWT.mjs} +2 -2
- package/dist/{auth-BPinwfN0.mjs → auth-CyO2aw5n.mjs} +127 -14
- package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
- package/dist/{backend-n3hajfrD.d.mts → backend-Bob-5CM7.d.mts} +86 -41
- package/dist/backend-a2/blob-vercel.d.mts +1 -1
- package/dist/backend-a2/contract.d.mts +37 -97
- package/dist/backend-a2/contract.mjs +1 -1
- package/dist/backend-a2/store-memory.d.mts +1 -1
- package/dist/backend-a2/store-memory.mjs +2 -2
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +1 -1
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +1 -1
- package/dist/backend-a2/store-redis.d.mts +1 -1
- package/dist/backend-a2/store-redis.mjs +1 -1
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +2 -2
- package/dist/backend-a2.d.mts +131 -240
- package/dist/backend-a2.mjs +2 -2
- package/dist/{backend-http-3PoBYQoo.d.mts → backend-http-BRrRbjRx.d.mts} +49 -45
- package/dist/{backend-http-BPaNYLWj.mjs → backend-http-CvUKOn-m.mjs} +11 -9
- package/dist/{backend-http-16SbvnAK.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-Kd8nvW5A.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
- package/dist/{backend-vercel-qznuDxnN.d.mts → backend-vercel-BaSEUdwg.d.mts} +49 -45
- package/dist/{backend-vercel-C4Kee2-K.mjs → backend-vercel-CmY9yZ9m.mjs} +80 -15
- package/dist/backend-vercel.d.mts +1 -1
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +1 -1
- package/dist/backend-vercel.node.mjs +1 -1
- package/dist/{blob-CqILvdmr.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
- package/dist/{blob-DQK5prXT.mjs → blob-WbB2DJKn.mjs} +30 -11
- package/dist/{chat-provider-D6jVT4wS.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
- package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
- package/dist/cli-commands.d.mts +1 -1
- package/dist/cli-commands.mjs +1 -1
- package/dist/cli-embedded.d.mts +2 -2
- package/dist/cli-embedded.mjs +1 -1
- package/dist/cli.mjs +4 -4
- package/dist/{client-CjAatpV4.d.mts → client-COfYrgfV.d.mts} +184 -221
- package/dist/{commands-BtH69yqt.d.mts → commands-DK4W3HCJ.d.mts} +30 -10
- package/dist/{commands-B5DHjZKK.mjs → commands-L1JSGz2Z.mjs} +162 -45
- package/dist/{conformance-C7fwIvmN.mjs → conformance-DK43HiHf.mjs} +224 -20
- package/dist/conformance.d.mts +8 -2
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-DkdkXQEy.mjs → engine-DurYIRt8.mjs} +29 -210
- package/dist/{errors-87TVtXeb.d.mts → errors-2vGs49Db.d.mts} +1 -1
- package/dist/{errors-C_5rB8Iz.mjs → errors-CJXhhzvT.mjs} +28 -23
- package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
- package/dist/{exports-CG0P8Qi4.mjs → exports-CWIdN0Ub.mjs} +146 -80
- package/dist/exports-FBOQWT7O.d.mts +5 -0
- package/dist/{folds-DGYkMW19.mjs → folds-Cu9Q9zyQ.mjs} +2 -22
- package/dist/{folds-8wRRXjAt.d.mts → folds-DLUzfibJ.d.mts} +13 -40
- package/dist/{index-BygAXP7t.d.mts → index-Ho-ayyQL.d.mts} +2 -2
- package/dist/index.d.mts +7 -7
- package/dist/{index.default-DhkgpfR2.mjs → index.default-BUcwDaAe.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.node.d.mts +7 -7
- package/dist/index.node.mjs +3 -3
- package/dist/inspect/app.js +62 -61
- package/dist/inspect/engine.js +3 -3
- package/dist/{main-D2s-QBOK.mjs → main-Dz-8komG.mjs} +189 -112
- package/dist/{middleware-DlJQYiwV.mjs → middleware-BavYSDUa.mjs} +1 -1
- package/dist/{node-D8Rtg1su.mjs → node-BgeGcwX0.mjs} +7 -3
- package/dist/{project-C_2xs57L.mjs → project-CQ4VUibA.mjs} +1 -1
- package/dist/{provider-Bm46LAdp.d.mts → provider-gynDUoyW.d.mts} +13 -43
- package/dist/{redis-tables-BWJZHUGg.mjs → redis-tables-DHf85ixI.mjs} +1 -19
- package/dist/{reducers-8jqLewgw.mjs → reducers-DcENwtwZ.mjs} +72 -75
- package/dist/{serve-lPoBDEds.mjs → serve-CTmow-YH.mjs} +6 -6
- package/dist/{server-D82a4uMe.mjs → server-DG8D6VV0.mjs} +4 -4
- package/dist/{serving-DKny38LK.mjs → serving-DBFRo6U_.mjs} +1 -1
- package/dist/{sql-Bj0fk6dn.mjs → sql-CT5xVuGr.mjs} +2 -8
- package/dist/{sqlite-db-Dc8G78XV.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
- package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
- package/dist/sync-chat-sdk.mjs +44 -0
- package/dist/sync-github.d.mts +5 -18
- package/dist/sync-github.mjs +4 -70
- package/dist/sync-linear.d.mts +3 -3
- package/dist/sync-linear.mjs +2 -3
- package/dist/sync-slack.d.mts +7 -7
- package/dist/sync-slack.mjs +33 -54
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +2 -4
- package/dist/{tokens-C5_yGRlL.mjs → tokens-Dnyd_y1n.mjs} +15 -6
- package/dist/{types-shZmGZ-q.d.mts → types-CmrFjpml.d.mts} +1 -3
- package/dist/{watchers-hash-B_5b0-ZG.mjs → watchers-hash-BjwlnGD9.mjs} +85 -105
- package/docs/01-board/01-mounting.md +2 -2
- package/docs/01-board/03-identity.md +5 -6
- package/docs/01-board/04-messages.md +3 -2
- package/docs/01-board/05-reading.md +2 -1
- package/docs/01-board/06-watchers.md +8 -6
- package/docs/01-board/08-middleware.md +1 -1
- package/docs/01-board/09-access.md +145 -14
- package/docs/02-sync/01-overview.md +19 -26
- package/docs/02-sync/02-functions.md +8 -9
- package/docs/02-sync/03-slack.md +5 -5
- package/docs/02-sync/04-github.md +2 -2
- package/docs/02-sync/06-providers.md +91 -62
- package/docs/03-guides/01-coding-agents.md +1 -1
- package/docs/03-guides/03-ci.md +7 -4
- package/docs/04-reference/01-cli.md +86 -41
- package/docs/04-reference/02-board.md +56 -23
- package/docs/04-reference/03-http.md +59 -32
- package/docs/04-reference/04-log.md +8 -11
- package/docs/04-reference/05-conformance.md +35 -6
- package/docs/index.md +2 -2
- package/package.json +6 -9
- package/sql/postgres/board.sql +1 -1
- package/sql/postgres/schema.sql +1 -1
- package/dist/events-qf5XG5VQ.mjs +0 -75
- package/dist/exports-CrGA5_yN.d.mts +0 -5
- package/dist/sync-kapso.mjs +0 -89
- package/dist/sync.chat.d.mts +0 -2
- package/dist/sync.chat.mjs +0 -2
- package/dist/users-yryHKFMo.mjs +0 -42
- package/docs/02-sync/05-whatsapp.md +0 -152
package/dist/inspect/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,
|
|
1
|
+
import{C as e,D as t,E as n,O as r,S as i,T as a,_ as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,l as h,m as g,n as _,o as v,p as y,r as b,s as ee,t as x,u as S,v as C,w,x as T,y as E}from"./app.js";const D=/^\[vc\]: #\S*\r?\n/,O=/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,k=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function A(e){if(!D.test(e))return null;let t=e.search(/<details><summary>\d+ Skipped Deployments?<\/summary>/),n=[],r=0;for(let i of e.split(/\r?\n/)){let a=r;if(r=e.indexOf(`
|
|
2
2
|
`,r)+1||e.length,!i.startsWith(`|`)||/^\|\s*(Project|Name)\s*\|/.test(i)||/^\|\s*:?-/.test(i))continue;let o=i.split(`|`).slice(1,-1).map(e=>e.trim());if(o.length<2)continue;let s=k.exec(o[1]);if(!s)continue;let c=[...o[0].matchAll(O)].at(-1),l=c?.[1]??o[0].replace(/<[^>]*>/g,``).trim();if(!l)continue;let u=o[2]?[...o[2].matchAll(O)].find(e=>/preview/i.test(e[1])):void 0,d=o[3]?.match(/<relative-time[^>]*>([^<]+)<\/relative-time>/)?.[1]?.trim()??(o[3]?.replace(/<[^>]*>/g,``).trim()||null);n.push({project:l,projectUrl:c?.[2]??null,status:s[1].trim(),deploymentUrl:s[2]??null,previewUrl:u?.[2]??null,updated:d,skipped:t!==-1&&a>t})}return n.length>0?n:null}const j={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},M=(e,t)=>t?`[${e}](${t})`:e;function N(e){return e.map(e=>`- ${[`${j[e.status.toLowerCase()]??`▲`} **${M(e.project,e.projectUrl)}**`,`${M(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[M(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
|
|
3
|
-
`)}function
|
|
3
|
+
`)}function te(e,t){let n=t===`github:user:35613825`?A(e):null;return n?`**▲ Vercel**\n${N(n)}`:e}const ne=e=>e>10;T({id:i().min(1),platform:i().min(1),key:i().min(1),...h.shape,raw:e()});const re=T({root:c(e=>typeof e==`function`).optional(),text:i().optional(),labels:g.optional(),unset:C(y).optional(),noSync:E().optional()}).strict();function P(e){if(e&&typeof e==`object`){for(let t of Object.values(e))P(t);Object.freeze(e)}return e}const ie=T({target:i().min(1),channel:i().min(1),kind:o([`channel`,`thread`])}).strict();var F=class extends Error{code;constructor(e,t){super(t),this.code=e}};const ae=e=>e instanceof F&&e.code===`channel_not_found`,oe=T({event:l,raw:e(),dedupe:i().min(1)}),se=/* @__PURE__ */ new WeakMap,ce=e=>se.get(e),I=(e,t=!1)=>{let n=f(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,p-1).toWellFormed()}…`:r},L=(e,t,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${n(e.size)})`} didn't come over: ${t}`,R=e=>e instanceof m&&e.code===`no_files`,z=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return L(e,`this board keeps no files`);let n=t.replace(/[[\]\\]/g,e=>`\\${e}`),r=e.url.replace(/[ ()]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`);return L(e,`this board keeps no files`,`[${n}](${r})`)},B=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
|
|
4
4
|
`)}`:e,le=e=>e.split(`
|
|
5
|
-
`).filter(e=>/^📎 .* didn't come over: /u.test(e)),H=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),ue=(e,t)=>e.length===t.length&&e.every((e,n)=>H(e)===H(t[n])),de=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,fe=6e4,pe=new AbortController().signal,me=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},U=e=>typeof e==`string`?o(e):Array.isArray(e)?e.map(U):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[o(e),U(t)])):e,W=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return o(n.slice(0,500))||`unknown error`},G=e=>e instanceof m&&e.status===404;function he(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=pe,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw s(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),g=t=>e[p(t)]!==void 0,_=e=>e.slice(e.indexOf(`:`)+1);async function C(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(G(e))return null;throw e}}async function w(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(G(e))return[];throw e}}async function T(e){return(await w(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>l(`syncFind`,e,t);async function O(){let{actors:e}=await l(`actors`,{}),t={byId:{},links:{}};for(let{person:n,...r}of e)t.byId[r.id]={id:r.id,kind:r.kind,handle:r.handle,name:r.name,avatar:r.avatar},n&&(t.links[r.id]=n.id);return t}let k=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),A=/* @__PURE__ */ new Map;function j(e,t){let n=`${e}:${t}`,r=A.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=oe(r);!a&&A.get(n)===i&&A.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),A.set(n,i),i.id}function M(e,t){if(!d(t))return _(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:_(n)}catch{return null}}async function N(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function I(e){let t=e.indexOf(`:`);if(t<=0)throw s(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ae.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw s(`can't sync to ${e}: ${t.message}`)}}async function H(e,t){let{event:n,raw:r,dedupe:i}=se.parse(U({...t,raw:me(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw s(`provider locations must belong to sync entry ${e}`);let a=ce(f(e));(!a||await a(_(n.channel),t=>j(e,t))||await he(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function he(e){try{return(await D({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function ge(t,n){let r=e[t];if(!r)throw y(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>H(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}let a=_e([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,W(e)));return x(a),i}async function _e(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>ve(e,t))),n+=t.length}return n}async function ve(e,t){let n;try{await xe(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,W(r)),n=W(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,W(e)))}async function ye(e,t,n,r=[]){if(t.length===0)return[];let i=[];for(let a of t){if(i.some(e=>e.url===a.url))continue;let t=(await D({thread:a.thread,message:a.message})).message,o=t===null?null:await T(t);if(o&&!o.deleted&&o.channel===n){i.push({url:a.url,message:o.id,unavailable:null,snapshot:{author:o.author?.name??o.by.actor,text:o.text,createdAt:o.createdAt,files:o.attachments.map(e=>({name:e.name,url:null}))}});continue}if(a.snapshot){i.push({url:a.url,message:null,snapshot:a.snapshot,unavailable:null});continue}let s=r.find(e=>e.url===a.url&&e.snapshot&&e.message===null);if(s){i.push(s);continue}let c=await e.fetchLink(a).catch(e=>({snapshot:null,unavailable:W(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return U(i)}async function be(e,t,i){let o=[],s=[],c=!1;for(let u of t.slice(0,10)){if(c){s.push(B(u));continue}if(u.size!==void 0&&u.size>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}let t;try{let n=await e.fetchFile(u);if(n===null){s.push(R(u,`${e.platform} can't hand it over`));continue}t=n}catch(t){if(i<3)throw t;console.warn(`[board] couldn't fetch ${u.name??`a file`} from ${e.platform}:`,W(t)),s.push(R(u,`${e.platform} wouldn't hand it over`));continue}if(t.byteLength>26214400){s.push(R(u,`over ${n/1024/1024} MB`));continue}if(t.byteLength===0){s.push(R(u,`it was empty`));continue}let d=u.name??`${u.type}`;try{o.push((await l(`upload`,{data:t,name:d,type:u.mimeType||r(d)})).attachment)}catch(e){if(e instanceof m&&(e.code===`too_large`||e.code===`no_direct_uploads`)){s.push(R(u,`over ${a/1024/1024} MB`));continue}if(!z(e))throw e;c=!0,s.push(B(u))}}for(let e of t.slice(10))s.push(R(e,`a message carries at most 10`));return{attachments:o,notes:s}}async function xe(e,t,n){let{event:r,raw:i}=t,a=f(e),c=(await D({thread:r.thread})).thread??void 0;if(r.kind===`message`){let t=(await D({thread:r.subthread??r.thread,message:r.message})).message;if(t!==null){let e=(await T(t))?.origin;if(!e||e.message!==r.message||(e.subthread??e.thread)!==(r.subthread??r.thread))return}else if(a.users&&n.attempt<6&&await we(c,r))throw Error(`holding ${r.message} until ${r.author.id}'s board message there has its receipt`);let i=r.author.isBot?void 0:de.exec(r.text)?.[1];if(i&&await Te(e,r,i))return}let l=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},u,d=()=>u??=P(a.event({id:n.id,key:e,event:r,raw:i})),p,m,h=!0,g={direction:`in`,key:e,channel:_(r.channel),get event(){return d()},board:o().client({author:l,key:e,...Ce(e,r)},n.id),import(r,i={}){if(!h)throw s(`the sync function has finished`);return p??=Se(e,t,n,c,r,i),p},permalink(){let e=m??a.permalink(d());return m=e,e.catch(()=>{m===e&&(m=void 0)}),e},resolve:t=>j(e,t)};try{await a.sync(g),await p}finally{h=!1}}async function Se(e,t,n,r,i,a){let o=i??(r?(await C(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await Ae(e,t,n,b.parse(o),ie.parse(a))}catch(e){if(!(e instanceof te))throw e;let n=(await D({thread:t.event.thread})).thread;return{root:n===null?null:(await C(n))?.root??null,message:null}}}function Ce(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(_(r),`thread`,{markdown:e})}}}async function we(e,t){let n=(await D({account:t.author.id})).person;if(!n||n.method!==`consent`)return!1;let r=Date.now(),i=(e,t,i)=>e.by.principal===n.id&&e.by.profile===null&&e.from===null&&!e.deleted&&!e.noSync&&r-Date.parse(e.createdAt)<fe&&!i[t]&&!e.undelivered[t];if(e!==void 0){let n=await C(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await w(n.root),o=await E(a.map(e=>e.id));return a.some(e=>r.some(t=>i(e,t,o[e.id]??{})))}let{roots:a}=await l(`syncRoots`,{by:n.id,since:r-fe}),o=await E(a.map(e=>e.id));for(let e of a){let n=await C(e.thread);if(n?.targets.includes(t.channel)&&!n.external[t.channel]&&i(e,t.channel,o[e.id]??{}))return!0}return!1}async function Te(e,t,n){try{await l(`syncAccount`,{sync:e,account:t.author,code:n,message:u(t.subthread??t.thread,t.message)})}catch(e){if(G(e))return!1;throw e}return await f(e).react(_(t.subthread??t.thread),t.message,`check`,!0).catch(t=>console.warn(`[board] couldn't confirm a linking code on ${e}:`,W(t))),!0}let Ee=e=>f(e).users!==null;async function De(e,t,n){let r=f(e);if(!r.users)throw s(`${e} can't sign people in: link it with a code`);return r.users.authorize(t,n?{callback:n}:{})}async function Oe(e,t){let n=f(e);if(!n.users)throw s(`${e} can't sign people in: link it with a code`);let r=await n.users.token(t);if(!r)return null;let i=await n.whoAmI(r);if(!i.id.startsWith(`${n.platform}:user:`))throw s(`${e} named an account that isn't a ${n.platform} user`);return i}async function ke(e,t,n){let r=await Oe(e,t);return r?(await l(`syncAccount`,{sync:e,account:r,code:n,method:`consent`})).link:null}async function Ae(e,t,n,r,i){let{event:a,raw:o}=t,c=Ce(e,a),u=f(e),d=(await D({thread:a.thread})).thread??void 0,p=d?await C(d):null,m=p?.root,g=e=>ee(`board-inbound`,n.id,String(e)),v={key:e,channel:c.via.channel,thread:c.via.thread},y=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await c.respond(t);throw new te(e.dropped)},b=async({attachments:t=[],...n})=>({id:await y(await l(`syncImport`,{op:`post`,from:e,source:v,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),x=e=>({root:e.re??e.id,message:e.id}),S=(e={})=>{let t={...e,...i.labels};for(let e of i.unset??[])delete t[e];return t},w=e=>{if(!i.root)return e;let t=i.root(P(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw s(`root overrides must be synchronous`);let n=ie.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},E=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:a.thread},set:n,unset:r}),O=async(e,t)=>{if(!i.labels&&!i.unset)return;let n=await T(t),r=Object.fromEntries(Object.entries(i.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),a=(i.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||a.length)&&await E(e,r,a)};switch(a.kind){case`message`:{let e=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(e!==null)return{root:m??e,message:e};let{attachments:t,notes:s}=a.files?.length?await be(u,a.files,n.attempt):{attachments:[],notes:[]},c=await ye(u,a.links??[],p?.channel??r),l={...c.length?{links:c}:{},author:a.author,text:L(V(i.text??a.text,s),t.length>0),...t.length?{attachments:t}:{},origin:{channel:a.channel,thread:a.thread,subthread:a.subthread,message:a.message,context:a.context},id:await g(0)},d={...l,...i.noSync?{noSync:!0}:{}};if(m!==void 0)return await O(a.author,m),x(await b({...d,re:m}));let f=await u.fetchRoot(_(a.thread));if(f===null){let e=w({text:a.text,labels:{},author:a.author,raw:o});return x(await b({...l,channel:r,text:L(V(i.text??e.text,s),t.length>0),labels:S(e.labels)}))}let h=w({...f,raw:f.raw??null}),v=await b({author:f.author,channel:r,text:L(h.text),labels:S(h.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`root:${n.id}`,context:null},id:await g(1)});return x(await b({...d,re:v.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await D({thread:a.subthread??a.thread,message:a.message})).message;if(t===null)return{root:m??null,message:null};let n=`author`in a?a.author:{id:`${u.platform}:system`,handle:u.platform,name:u.platform,avatar:null,isBot:!0},r={thread:a.subthread??a.thread,message:a.message};if(m!==void 0&&await O(n,m),a.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:r});else if(a.kind===`edited`){let o=await T(t),s=L(V(i.text??a.text,o?le(o.text):[]),(o?.attachments.length??0)>0);o&&!a.linksOnly&&o.text!==s&&await y(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:r,text:s,source:v}));let c=o?await ye(u,a.links??[],o.channel,o.links):[];o&&!ue(c,o.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:r,links:c})}else{if(u.singleReaction){let i=h(a.emoji);for(let[o,s]of Object.entries((await T(t))?.reactions??{}))s.includes(n.id)&&!(a.added&&o===i)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:o,remove:!0})}(!u.singleReaction||a.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:r,emoji:a.emoji,remove:!a.added})}return{root:m??null,message:t}}case`lifecycle`:{if(m!==void 0){let t=await T(m),r=t?.origin?.message===`lifecycle:${n.id}`,s=S((r?w({text:K(a),labels:a.labels,author:a.author,raw:o}):null)?.labels??a.labels),c=e=>a.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(s).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in s)&&(a.unset.includes(e)||i.unset?.includes(e)||c(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await E(a.author,u,d),a.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===a.thread){let n=L(i.text??w({text:K(a),labels:t.labels,author:a.author,raw:o}).text);t.text!==n&&await y(await l(`syncImport`,{op:`edit`,author:a.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return a.reply?x(await b({author:a.author,re:m,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):{root:m,message:r?m:null}}if(!a.createRoot)return{root:null,message:null};let t=w({text:K(a),labels:a.labels,author:a.author,raw:o}),s=await b({author:a.author,channel:r,text:L(a.reply?t.text:i.text??t.text),labels:S(t.labels),origin:{channel:a.channel,thread:a.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await g(0)});return a.reply?x(await b({author:a.author,re:s.id,text:L(i.text??a.reply),...i.noSync?{noSync:!0}:{},id:await g(1)})):x(s)}}}function je(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return v(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?Me(e,a.id,n)??a.name:null})}function Me(e,t,n){let r=t=>{let r=n.byId[t];return r?e.mention(r):null},i=n.links[t]??t,a=r(t)??(i===t?null:r(i));if(a)return a;for(let e of k(n,i)){let t=r(e);if(t)return t}return null}async function Ne(e,t,n){if(!e.users||t.from!==null||t.by.profile!==null||!t.by.principal.startsWith(`vercel:user:`))return null;if(n){let{accounts:n}=await D({person:t.by.principal});if(!n.some(t=>t.method===`consent`&&t.id.startsWith(`${e.platform}:user:`)))return null}try{return await e.users.token(t.by.principal)}catch(n){return console.warn(`[board] no ${e.platform} token for ${t.by.principal}; posting #${t.id} as the app:`,W(n)),null}}function Pe(e,t,n){let r=f(p(e)),i=je(r,t,n,ne(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Re(t)}`:i}}async function Fe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Ne(a,r,!0);if(!o)return null;try{return await a.postAs(o,_(t),n,Pe(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,W(t)),null}}function Ie(t,n,r,i,a){let o=i.byId[r.by.actor],c=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(c=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(c=`${c} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let l=f(t),u=(a??(e=>l.render(e)))(P({message:{...structuredClone(r),text:ne(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>Me(l,e,i),label:c,target:n}));if(typeof u!=`string`)throw s(`a provider renderer must return markdown`);let d=je(l,r,i,u);return r.by.profile&&!r.from?{markdown:d,as:{name:c,avatar:Le(r,o,i)}}:{markdown:d}}function Le(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Re=e=>e.attachments.map(n=>`\n📎 ${n.name} (${t(n.size)}, on the board: #${e.id})`).join(``),q=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=Ie(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Re(t)}`:o,...s?{as:s}:{}}};async function ze(e,t,n,r,i){let a=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,c=f(p(t)),l=q(t,e,r,!1,i),u,d;if(!l.markdown.trim()&&e.attachments.length&&c.uploadsFiles)try{let n=await Ve(e,t,o);d=n.message,u=n.files}catch(n){if(!c.refusesFiles(n)&&!z(n))throw n;l=q(t,e,r,!0,i),d=(await c.post(_(o),`thread`,l)).message,u=null}else{let n=await Fe(t,o,`thread`,e,r);if(n){let r={thread:o,message:n.message,asUser:!0};return await J(e,t,r),r}if(!l.markdown.trim())throw s(`a renderer must return text unless the message has media`);d=(await c.post(_(o),`thread`,l)).message}let m={thread:o,message:d,...u===void 0?{}:{files:u}};return await J(e,t,m),m}async function J(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message,...n.asUser?{asUser:!0}:{}},...n.files===void 0?{}:{files:n.files}})}async function Be(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function Ve(e,t,n){return f(p(t)).uploadFiles(_(n),await Be(e))}async function Y(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await Ve(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!z(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${W(s)}`),{cause:s,data:{error:`files: ${W(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${W(s)}); noting them in its text`),o=null,await a.edit(_(n.thread),n.message,q(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,W(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let X=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Z(e,t){if(t.length!==0){if(e<5||re(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,W(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:W(r)})}}async function Q(e,t,n,r,i){let a,o=!0,c=()=>s(`the sync function has finished`),l={direction:`out`,key:e,channel:r===null?null:M(e,r),event:P(structuredClone(t)),message:P(structuredClone(n)),target:r,deliver(e={}){if(!o)throw c();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw c();return He(e,n,t)},resolve:t=>j(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function He(e,t,n){let{target:r,kind:i}=await I(`${e}:${n}`),a=t.re===null?t:await T(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??S(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function Ue(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await T(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await Q(e,t,i,null,async()=>{})}catch(e){X(a,i.id,null,e)}await Z(n,a)}async function We(e,t){let{thread:n,target:r}=e.payload;if(!g(r)||(e.payload.kind??(d(r)?`thread`:`channel`))===`thread`)return;let i=await C(n),[a,...o]=i?await w(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await Q(p(r),e,a,r,e=>Ge(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function Ge(e,t,n,r,i,a){let o=await O(),c=await E([t.id,...n.map(e=>e.id)]),l=e=>(i.push({id:t.id,target:r,error:e}),e),u=e.external[r];if(u){let e=c[t.id]?.[r];if(e)try{await Y(t,r,e,o,a)}catch(e){throw l(e)}}else try{let e=f(p(r)),n=q(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw s(`a renderer must return text unless the message has media`);let c,l=!1,d=await(async()=>{if(!i){let i=await Fe(r,r,`channel`,t,o);if(i)return l=!0,i;if(t.attachments.length===0||!e.startsThreadsWithFiles)return e.post(_(r),`channel`,n)}try{let a=await e.post(_(r),`channel`,{...n,files:await Be(t)});return c=i?a.files??[]:a.files,a}catch(n){if(!e.refusesFiles(n)&&!z(n))throw n;return c=null,e.post(_(r),`channel`,q(r,t,o,!0,a))}})();u=`${p(r)}:${d.thread}`;let m={thread:u,message:d.message,...c===void 0?{}:{files:c},...l?{asUser:!0}:{}};await J(t,r,m),await Y(t,r,m,o,a)}catch(e){throw l(e)}let d=[];for(let e of n)if(!(e.deleted||e.noSync||Ke(e,r)))try{await Y(e,r,c[e.id]?.[r]??await ze(e,r,u,o,a),o,a)}catch(t){d.push({id:e.id,target:r,error:t})}if(i.push(...d),d.length)throw d[0].error}let Ke=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function qe(e,t){if(!e.message)return;if(e.message.re===null)return Ue(e,t);if(e.payload.noSync)return;let n=await C(e.payload.thread);if(!n||n.targets.length===0)return;let r=await T(e.message.id);if(!r||r.deleted)return;let i=await O(),a=[];for(let t of n.targets){let o=n.external[t];if(g(t)&&o&&!Ke(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await ze(r,t,o,i,e);await Y(r,t,n,i,e)})}catch(e){X(a,r.id,t,e)}}await Z(t,a)}async function Je(e){let t=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await T(e.payload.id):e.message??await T(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let r=await O();for(let[i,a]of Object.entries(t))if(!(!g(i)||e.payload.from!==null&&p(i)===e.payload.from))try{await Q(p(i),e,n,i,async t=>{let o=f(p(i)),s=_(a.thread),c=a.asUser&&(e.type===`message.edited`||e.type===`message.deleted`),l=c?await Ne(o,n,!1):null;if(c&&!l){console.warn(`[board] skipped ${e.type} on ${i}: #${n.id} was posted as its author, and their token is gone`);return}e.type===`message.edited`?l?await o.editAs(l,s,a.message,Pe(i,n,r)):await o.edit(s,a.message,q(i,n,r,a.files===null,t)):e.type===`message.deleted`?(l?await o.deleteAs(l,s,a.message):await o.delete(s,a.message),a.files?.length&&await o.deleteFiles(a.files)):e.type===`reaction.added`?await o.react(s,a.message,h(e.payload.emoji),!0):await o.react(s,a.message,h(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${i}:`,t.message)}}async function Ye(e){let{id:t}=e.payload,n=(await E([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await T(t);if(!i||i.deleted)return;let a=await O(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(g(t))try{await Q(p(t),e,o,t,async e=>{let n=q(t,o,a,r.files===null,e);n.markdown!==q(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(_(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,W(e))}}async function Xe(e,{attempt:t}){switch(e.type){case`thread.synced`:return We(e,t);case`message.posted`:return qe(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return Je(e);case`labels.changed`:return Ye(e)}}async function Ze(e,t){let n=e[0]?await C(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(g);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(_(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,W(e)),null}}))).filter(e=>e!==null)}async function Qe(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw s(`can't resolve inbox channel: ${e.message}`)}}let $=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function $e(e,t,n,r){let i=f(e);try{return await i.read(t,{limit:n,after:r})}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't read ${t} from ${e}: ${W(n)}`,`platform_error`)}}async function et(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof F?new m($(n.code),n.code,n.message):s(`can't join ${t} on ${e}: ${W(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Xe,receive:xe,pull:_e,normalizeTarget:I,findByEmail:N,handleWebhook:ge,inboxChannel:Qe,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Ze,readSource:$e,joinSource:et}}function K(e){let t=e.url?`${e.title}\n${e.url}`:e.title;return e.body?`${t}\n\n${e.body}`:t}export{he as createSyncEngine};
|
|
5
|
+
`).filter(e=>/^📎 .* didn't come over: /u.test(e)),V=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),ue=(e,t)=>e.length===t.length&&e.every((e,n)=>V(e)===V(t[n])),de=new AbortController().signal,fe=e=>{try{return JSON.parse(JSON.stringify(e??null))}catch{return null}},H=e=>typeof e==`string`?f(e):Array.isArray(e)?e.map(H):typeof e==`object`&&e?Object.fromEntries(Object.entries(e).map(([e,t])=>[f(e),H(t)])):e,U=e=>{let t=e?.data?.error,n=typeof t==`string`?t:e instanceof Error?e.message:String(e);return f(n.slice(0,500))||`unknown error`},W=e=>e instanceof m&&e.status===404;function G(e){let i=null,o=()=>{if(!i)throw Error(`sync used before board() bound it`);return i},c={self:`sync`,origin:{via:{kind:`engine`}}};function l(e,t,n=de,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw s(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),h=t=>e[p(t)]!==void 0,g=e=>e.slice(e.indexOf(`:`)+1);async function y(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(W(e))return null;throw e}}async function C(e){try{let{message:t,replies:n}=await l(`message`,{id:e});return[t,...n]}catch(e){if(W(e))return[];throw e}}async function T(e){return(await C(e)).find(t=>t.id===e)??null}async function E(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let D=(e,t)=>l(`syncFind`,e,t);async function O(){let{actors:e}=await l(`actors`,{}),t={byId:{}};for(let n of e)t.byId[n.id]={id:n.id,kind:n.kind,handle:n.handle,name:n.name,avatar:n.avatar};return t}let k=/* @__PURE__ */ new Map;function A(e,t){let n=`${e}:${t}`,r=k.get(n);if(r&&(!r.failed||Date.now()-r.at<6e4))return r.id;let i={id:f(e).resolveChannel(t),failed:!1,at:Date.now()};return i.id.catch(r=>{i.failed=!0;let a=ae(r);!a&&k.get(n)===i&&k.delete(n),console.warn(`[board] sync ${e}: ${a?`skipping`:`couldn't resolve`} ${t}: ${r.message}`)}),k.set(n,i),i.id}function j(e,t){if(!u(t))return g(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:g(n)}catch{return null}}async function M(t){for(let n of Object.values(e)){let e=await n.findByEmail(t).catch(()=>null);if(e)return{id:e.id,kind:`external`,handle:e.handle,name:e.name,avatar:e.avatar}}return null}async function N(e){let t=e.indexOf(`:`);if(t<=0)throw s(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=ie.parse(await r.resolveTarget(e.slice(t+1)));if(i.kind===`channel`&&!r.canStartThreads)throw Error(`${r.platform} cannot start threads; use an existing thread`);return{target:`${n}:${i.target}`,kind:i.kind}}catch(t){throw s(`can't sync to ${e}: ${t.message}`)}}async function se(e,t){let{event:n,raw:r,dedupe:i}=oe.parse(H({...t,raw:fe(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw s(`provider locations must belong to sync entry ${e}`);let a=ce(f(e));(!a||await a(g(n.channel),t=>A(e,t))||await V(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function V(e){try{return(await D({thread:e},AbortSignal.timeout(2e3))).thread!==null}catch(e){let t=e?.name;if(t===`TimeoutError`||t===`AbortError`)return!0;throw e}}async function G(t,n){let r=e[t];if(!r)throw v(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>se(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}let a=q([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,U(e)));return b(a),i}async function q(t=Object.keys(e)){let n=0,r=Date.now()+24e4;for(let e of t)for(;Date.now()<r;){let{items:t}=await l(`inboxNext`,{key:e,limit:10});if(t.length===0)break;await Promise.all(t.map(t=>pe(e,t))),n+=t.length}return n}async function pe(e,t){let n;try{await he(e,{event:t.event,raw:t.raw},{id:t.id,attempt:t.attempt+1})}catch(r){console.error(`[board] sync ${e} couldn't import ${t.id}:`,U(r)),n=U(r)}await l(`inboxAck`,{key:e,id:t.id,lease:t.lease,...n===void 0?{}:{error:n}}).catch(e=>console.error(`[board] couldn't ack an inbox event:`,U(e)))}async function J(e,t,n,r,i=[]){if(t.length===0)return[];let a=[];for(let o of t){if(a.some(e=>e.url===o.url))continue;let t=(await D({thread:o.thread,message:o.message})).message,s=t===null?null:await T(t);if(s&&!s.deleted&&s.channel===r){a.push({url:o.url,message:s.id,unavailable:null,snapshot:{author:s.author?.name??s.by.actor,text:s.text,createdAt:s.createdAt,files:s.attachments.map(e=>({name:e.name,url:null}))}});continue}if(o.snapshot){a.push({url:o.url,message:null,snapshot:o.snapshot,unavailable:null});continue}let c=i.find(e=>e.url===o.url&&e.snapshot&&e.message===null);if(c){a.push(c);continue}let l=await e.fetchLink(o,n).catch(e=>({snapshot:null,unavailable:U(e)}));a.push({url:o.url,message:null,snapshot:l.snapshot,unavailable:l.snapshot?null:(l.unavailable??`unknown`).slice(0,300)})}return H(a)}async function me(e,n,r){let i=[],o=[],s=!1;for(let c of n.slice(0,10)){if(s){o.push(z(c));continue}if(c.size!==void 0&&c.size>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}let n;try{let t=await e.fetchFile(c);if(t===null){o.push(L(c,`${e.platform} can't hand it over`));continue}n=t}catch(t){if(r<3)throw t;console.warn(`[board] couldn't fetch ${c.name??`a file`} from ${e.platform}:`,U(t)),o.push(L(c,`${e.platform} wouldn't hand it over`));continue}if(n.byteLength>26214400){o.push(L(c,`over ${a/1024/1024} MB`));continue}if(n.byteLength===0){o.push(L(c,`it was empty`));continue}let u=c.name??`${c.type}`;try{i.push((await l(`upload`,{data:n,name:u,type:c.mimeType||t(u)})).attachment)}catch(e){if(e instanceof m&&(e.code===`too_large`||e.code===`no_direct_uploads`)){o.push(L(c,`over ${w/1024/1024} MB`));continue}if(!R(e))throw e;s=!0,o.push(z(c))}}for(let e of n.slice(10))o.push(L(e,`a message carries at most 10`));return{attachments:i,notes:o}}async function he(e,t,n){let{event:r,raw:i}=t,a=f(e),c=(await D({thread:r.thread})).thread??void 0;if(r.kind===`message`){let e=(await D({thread:r.subthread??r.thread,message:r.message})).message;if(e!==null){let t=(await T(e))?.origin;if(!t||t.message!==r.message||(t.subthread??t.thread)!==(r.subthread??r.thread))return}}let l=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},u,d=()=>u??=P(a.event({id:n.id,key:e,event:r,raw:i})),p,m,h=!0,_={direction:`in`,key:e,channel:g(r.channel),get event(){return d()},board:o().client({author:l,key:e,..._e(e,r)},n.id),import(r,i={}){if(!h)throw s(`the sync function has finished`);return p??=ge(e,t,n,c,r,i),p},permalink(){let e=m??a.permalink(d());return m=e,e.catch(()=>{m===e&&(m=void 0)}),e},resolve:t=>A(e,t)};try{await a.sync(_),await p}finally{h=!1}}async function ge(e,t,n,r,i,a){let o=i??(r?(await y(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await ve(e,t,n,d.parse(o),re.parse(a))}catch(e){if(!(e instanceof x))throw e;let n=(await D({thread:t.event.thread})).thread;return{root:n===null?null:(await y(n))?.root??null,message:null}}}function _e(e,t){let n=f(e),r=(`subthread`in t?t.subthread:null)??t.thread;return{via:{kind:`sync`,key:e,platform:n.platform,channel:t.channel,thread:r},respond:async e=>{await n.post(g(r),`thread`,{markdown:e})}}}async function ve(e,t,n,i,a){let{event:o,raw:c}=t,u=_e(e,o),d=f(e),p=(await D({thread:o.thread})).thread??void 0,m=p?await y(p):null,h=m?.root,_=e=>ee(`board-inbound`,n.id,String(e)),v={key:e,channel:u.via.channel,thread:u.via.thread},b=async e=>{if(!e.dropped)return e.message;for(let t of e.dropped)await u.respond(t);throw new x(e.dropped)},S=async({attachments:t=[],...n})=>({id:await b(await l(`syncImport`,{op:`post`,from:e,source:v,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),C=e=>({root:e.re??e.id,message:e.id}),w=(e={})=>{let t={...e,...a.labels};for(let e of a.unset??[])delete t[e];return t},E=e=>{if(!a.root)return e;let t=a.root(P(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw s(`root overrides must be synchronous`);let n=re.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},O=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:o.thread},set:n,unset:r}),k=async(e,t)=>{if(!a.labels&&!a.unset)return;let n=await T(t),r=Object.fromEntries(Object.entries(a.labels??{}).filter(([e,t])=>n?.labels[e]!==t)),i=(a.unset??[]).filter(e=>n?.labels[e]!==void 0);(Object.keys(r).length||i.length)&&await O(e,r,i)};switch(o.kind){case`message`:{let e=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(e!==null)return{root:h??e,message:e};let{attachments:t,notes:r}=o.files?.length?await me(d,o.files,n.attempt):{attachments:[],notes:[]},s=await J(d,o.links??[],{thread:o.subthread??o.thread,message:o.message},m?.channel??i),l={...s.length?{links:s}:{},author:o.author,text:I(B(a.text??o.text,r),t.length>0),...t.length?{attachments:t}:{},origin:{channel:o.channel,thread:o.thread,subthread:o.subthread,message:o.message,context:o.context},id:await _(0)},u={...l,...a.noSync?{noSync:!0}:{}};if(h!==void 0)return await k(o.author,h),C(await S({...u,re:h}));let f=await d.fetchRoot(g(o.thread));if(f===null){let e=E({text:o.text,labels:{},author:o.author,raw:c});return C(await S({...l,channel:i,text:I(B(a.text??e.text,r),t.length>0),labels:w(e.labels)}))}let p=E({...f,raw:f.raw??null}),v=await S({author:f.author,channel:i,text:I(p.text),labels:w(p.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`root:${n.id}`,context:null},id:await _(1)});return C(await S({...u,re:v.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await D({thread:o.subthread??o.thread,message:o.message})).message;if(t===null)return{root:h??null,message:null};let n=`author`in o?o.author:{id:`${d.platform}:system`,handle:d.platform,name:d.platform,avatar:null,isBot:!0},i={thread:o.subthread??o.thread,message:o.message};if(h!==void 0&&await k(n,h),o.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:i});else if(o.kind===`edited`){let r=await T(t),s=I(B(a.text??o.text,r?le(r.text):[]),(r?.attachments.length??0)>0);r&&!o.linksOnly&&r.text!==s&&await b(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:i,text:s,source:v}));let c=r?await J(d,o.links??[],i,r.channel,r.links):[];r&&!ue(c,r.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:i,links:c})}else{if(d.singleReaction){let a=r(o.emoji);for(let[r,s]of Object.entries((await T(t))?.reactions??{}))s.includes(n.id)&&!(o.added&&r===a)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:r,remove:!0})}(!d.singleReaction||o.added)&&await l(`syncImport`,{op:`react`,author:n,from:e,origin:i,emoji:o.emoji,remove:!o.added})}return{root:h??null,message:t}}case`lifecycle`:{if(h!==void 0){let t=await T(h),r=t?.origin?.message===`lifecycle:${n.id}`,i=w((r?E({text:K(o),labels:o.labels,author:o.author,raw:c}):null)?.labels??o.labels),s=e=>o.owns?.some(t=>e.startsWith(t))??!1,u=Object.fromEntries(Object.entries(i).filter(([e,n])=>t?.labels[e]!==n)),d=Object.keys(t?.labels??{}).filter(e=>!(e in i)&&(o.unset.includes(e)||a.unset?.includes(e)||s(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await O(o.author,u,d),o.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===o.thread){let n=I(a.text??E({text:K(o),labels:t.labels,author:o.author,raw:c}).text);t.text!==n&&await b(await l(`syncImport`,{op:`edit`,author:o.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return o.reply?C(await S({author:o.author,re:h,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await _(1)})):{root:h,message:r?h:null}}if(!o.createRoot)return{root:null,message:null};let t=E({text:K(o),labels:o.labels,author:o.author,raw:c}),r=await S({author:o.author,channel:i,text:I(o.reply?t.text:a.text??t.text),labels:w(t.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await _(0)});return o.reply?C(await S({author:o.author,re:r.id,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await _(1)})):C(r)}}}function ye(e,t,n,r=t.text){let i=t.mentions.map(e=>n.byId[e]).filter(e=>e!==void 0);return _(r,(t,r)=>{let a=t.kind===`actor`?i.find(e=>e.id===t.actor):i.find(e=>e.handle.toLowerCase()===t.handle);return a?be(e,a.id,n)??a.name:null})}function be(e,t,n){let r=n.byId[t];return r?e.mention(r):null}function xe(t,n,r,i,a){let o=i.byId[r.by.actor],c=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(c=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(c=`${c} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let l=f(t),u=(a??(e=>l.render(e)))(P({message:{...structuredClone(r),text:te(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>be(l,e,i),label:c,target:n}));if(typeof u!=`string`)throw s(`a provider renderer must return markdown`);let d=ye(l,r,i,u);return r.by.profile&&!r.from?{markdown:d,as:{name:c,avatar:Se(r,o,i)}}:{markdown:d}}function Se(e,t,n){let r=e=>!e||/\/avatars\/[a-z0-9-]+$/.test(e),i=t?.avatar??null;if(!r(i))return i;let a=n.byId[e.by.principal]?.avatar;if(!r(a))return a;let o=t?.handle.split(`/`)[0]??``;return!e.by.principal.startsWith(`vercel:user:`)||!o||/^user-/.test(o)?i:`https://vercel.com/api/www/avatar?u=${encodeURIComponent(o)}&s=64&format=png`}let Ce=e=>e.attachments.map(t=>`\n📎 ${t.name} (${n(t.size)}, on the board: #${e.id})`).join(``),Y=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=xe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Ce(t)}`:o,...s?{as:s}:{}}};async function we(e,t,n,r,i){let a=e.replyTo===null?void 0:(await E([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,c=f(p(t)),l=Y(t,e,r,!1,i),u,d;if(!l.markdown.trim()&&e.attachments.length&&c.uploadsFiles)try{let n=await De(e,t,o);d=n.message,u=n.files}catch(n){if(!c.refusesFiles(n)&&!R(n))throw n;l=Y(t,e,r,!0,i),d=(await c.post(g(o),`thread`,l)).message,u=null}else{if(!l.markdown.trim())throw s(`a renderer must return text unless the message has media`);d=(await c.post(g(o),`thread`,l)).message}let m={thread:o,message:d,...u===void 0?{}:{files:u}};return await Te(e,t,m),m}async function Te(e,t,n){await l(`syncReceipt`,{kind:`message`,message:e.id,target:t,external:{thread:n.thread,message:n.message},...n.files===void 0?{}:{files:n.files}})}async function Ee(e){return Promise.all(e.attachments.map(async e=>{let{data:t}=await l(`attachment`,{id:e.id});return{data:t instanceof Uint8Array?t:new Uint8Array(await new Response(t).arrayBuffer()),filename:e.name,mimeType:e.type}}))}async function De(e,t,n){return f(p(t)).uploadFiles(g(n),await Ee(e))}async function X(e,t,n,r,i){let a=f(p(t));if(!a.uploadsFiles||e.attachments.length===0||n.files!==void 0)return;let o;try{o=(await De(e,t,n.thread)).files}catch(s){if(!a.refusesFiles(s)&&!R(s))throw Object.assign(/* @__PURE__ */ Error(`files: ${U(s)}`),{cause:s,data:{error:`files: ${U(s)}`}});console.warn(`[board] ${t} won't take #${e.id}'s files (${U(s)}); noting them in its text`),o=null,await a.edit(g(n.thread),n.message,Y(t,e,r,!0,i)).catch(n=>console.warn(`[board] couldn't note #${e.id}'s files on ${t}:`,U(n)))}await l(`syncReceipt`,{kind:`files`,message:e.id,target:t,files:o})}let Z=(e,t,n,r)=>{e.some(e=>e.error===r)||e.push({id:t,target:n,error:r})};async function Q(e,t){if(t.length!==0){if(e<5||ne(e))throw t[0].error;for(let{id:e,target:n,error:r}of t)console.error(`[board] gave up ${n===null?`syncing #${e}`:`delivering #${e} to ${n}`}:`,U(r)),n!==null&&await l(`syncReceipt`,{kind:`failed`,message:e,target:n,error:U(r)})}}async function $(e,t,n,r,i){let a,o=!0,c=()=>s(`the sync function has finished`),l={direction:`out`,key:e,channel:r===null?null:j(e,r),event:P(structuredClone(t)),message:P(structuredClone(n)),target:r,deliver(e={}){if(!o)throw c();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw c();return Oe(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function Oe(e,t,n){let{target:r,kind:i}=await N(`${e}:${n}`),a=t.re===null?t:await T(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??S(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function ke(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await T(t.message.id);if(!i||i.deleted)return;let a=[];for(let e of r)try{await $(e,t,i,null,async()=>{})}catch(e){Z(a,i.id,null,e)}await Q(n,a)}async function Ae(e,t){let{thread:n,target:r}=e.payload;if(!h(r)||(e.payload.kind??(u(r)?`thread`:`channel`))===`thread`)return;let i=await y(n),[a,...o]=i?await C(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await $(p(r),e,a,r,e=>je(i,a,o,r,s,e))}catch(e){Z(s,a.id,r,e)}await Q(t,s)}async function je(e,t,n,r,i,a){let o=await O(),c=await E([t.id,...n.map(e=>e.id)]),l=e=>(i.push({id:t.id,target:r,error:e}),e),u=e.external[r];if(u){let e=c[t.id]?.[r];if(e)try{await X(t,r,e,o,a)}catch(e){throw l(e)}}else try{let e=f(p(r)),n=Y(r,t,o,!1,a),i=!n.markdown.trim()&&t.attachments.length>0&&e.uploadsFiles;if(!n.markdown.trim()&&!i)throw s(`a renderer must return text unless the message has media`);let c,l=await(async()=>{if(!i&&(t.attachments.length===0||!e.startsThreadsWithFiles))return e.post(g(r),`channel`,n);try{let a=await e.post(g(r),`channel`,{...n,files:await Ee(t)});return c=i?a.files??[]:a.files,a}catch(n){if(!e.refusesFiles(n)&&!R(n))throw n;return c=null,e.post(g(r),`channel`,Y(r,t,o,!0,a))}})();u=`${p(r)}:${l.thread}`;let d={thread:u,message:l.message,...c===void 0?{}:{files:c}};await Te(t,r,d),await X(t,r,d,o,a)}catch(e){throw l(e)}let d=[];for(let e of n)if(!(e.deleted||e.noSync||Me(e,r)))try{await X(e,r,c[e.id]?.[r]??await we(e,r,u,o,a),o,a)}catch(t){d.push({id:e.id,target:r,error:t})}if(i.push(...d),d.length)throw d[0].error}let Me=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ne(e,t){if(!e.message)return;if(e.message.re===null)return ke(e,t);if(e.payload.noSync)return;let n=await y(e.payload.thread);if(!n||n.targets.length===0)return;let r=await T(e.message.id);if(!r||r.deleted)return;let i=await O(),a=[];for(let t of n.targets){let o=n.external[t];if(h(t)&&o&&!Me(r,t))try{await $(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await we(r,t,o,i,e);await X(r,t,n,i,e)})}catch(e){Z(a,r.id,t,e)}}await Q(t,a)}async function Pe(e){let t=(await E([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await T(e.payload.id):e.message??await T(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let i=await O();for(let[a,o]of Object.entries(t))if(!(!h(a)||e.payload.from!==null&&p(a)===e.payload.from))try{await $(p(a),e,n,a,async t=>{let s=f(p(a)),c=g(o.thread);e.type===`message.edited`?await s.edit(c,o.message,Y(a,n,i,o.files===null,t)):e.type===`message.deleted`?(await s.delete(c,o.message),o.files?.length&&await s.deleteFiles(o.files)):e.type===`reaction.added`?await s.react(c,o.message,r(e.payload.emoji),!0):await s.react(c,o.message,r(e.payload.emoji),!1)})}catch(t){console.warn(`[board] skipped ${e.type} on ${a}:`,t.message)}}async function Fe(e){let{id:t}=e.payload,n=(await E([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await T(t);if(!i||i.deleted)return;let a=await O(),o={...i,labels:r.labels},s={...i,labels:e.before?.labels??{}};for(let[t,r]of Object.entries(n))if(h(t))try{await $(p(t),e,o,t,async e=>{let n=Y(t,o,a,r.files===null,e);n.markdown!==Y(t,s,a,r.files===null,e).markdown&&await f(p(t)).edit(g(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,U(e))}}async function Ie(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ae(e,t);case`message.posted`:return Ne(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return Pe(e);case`labels.changed`:return Fe(e)}}async function Le(e,t){let n=e[0]?await y(e[0].thread):null;if(!n)return[];let r=[...new Set(Object.values(n.external))].filter(h);return(await Promise.all(r.map(async n=>{let r=p(n);try{let i=f(r),a=null;for(let t=e.length-1;t>=0&&a===null;t--){let r=e[t];!r.deleted&&r.origin?.thread===n&&(a=r.origin.message)}return await i.startTyping(g(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,U(e)),null}}))).filter(e=>e!==null)}async function Re(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw s(`can't resolve inbox channel: ${e.message}`)}}let ze=e=>e===`unsupported`||e===`invalid_target`?400:e===`channel_not_found`?404:e===`ratelimited`?429:[`no_client`,`list_failed`,`internal_error`,`fatal_error`,`service_unavailable`,`request_timeout`].includes(e)?502:403;async function Be(e,t,n,r){let i=f(e);try{return await i.read(t,{limit:n,after:r})}catch(n){throw n instanceof F?new m(ze(n.code),n.code,n.message):s(`can't read ${t} from ${e}: ${U(n)}`,`platform_error`)}}async function Ve(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof F?new m(ze(n.code),n.code,n.message):s(`can't join ${t} on ${e}: ${U(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Ie,receive:he,pull:q,normalizeTarget:N,findByEmail:M,handleWebhook:G,inboxChannel:Re,typing:Le,readSource:Be,joinSource:Ve}}function K(e){let t=e.url?`${e.title}\n${e.url}`:e.title;return e.body?`${t}\n\n${e.body}`:t}export{G as createSyncEngine};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { a as AttachmentId } from "./schemas-DBmUO6pr.mjs";
|
|
3
|
-
import { d as VERSION, i as withMentions } from "./watchers-hash-
|
|
3
|
+
import { d as VERSION, i as withMentions } from "./watchers-hash-BjwlnGD9.mjs";
|
|
4
4
|
import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
|
|
5
|
-
import { u as BoardError } from "./errors-
|
|
6
|
-
import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { t as
|
|
10
|
-
import { t as
|
|
11
|
-
import {
|
|
5
|
+
import { u as BoardError } from "./errors-CJXhhzvT.mjs";
|
|
6
|
+
import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-L1JSGz2Z.mjs";
|
|
7
|
+
import { i as parseScope } from "./access-CY5xVMIK.mjs";
|
|
8
|
+
import { a as handFetch, d as vercelProjectFile, n as boardUrl, o as handLogin, r as connect } from "./node-BgeGcwX0.mjs";
|
|
9
|
+
import { t as http } from "./backend-http-D8mUzuKQ.mjs";
|
|
10
|
+
import { t as board } from "./server-DG8D6VV0.mjs";
|
|
11
|
+
import { t as conformance } from "./conformance-DK43HiHf.mjs";
|
|
12
|
+
import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-Ba8JDtqW.mjs";
|
|
12
13
|
import { t as displayText } from "./bots-CGkO76qH.mjs";
|
|
13
14
|
import { spawn } from "node:child_process";
|
|
14
15
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -34,7 +35,6 @@ function authorName(message) {
|
|
|
34
35
|
if (!author) return message.by.actor;
|
|
35
36
|
const slash = author.handle.indexOf("/");
|
|
36
37
|
if (message.by.profile !== null && slash !== -1) return `${author.name} via ${author.handle.slice(0, slash)}`;
|
|
37
|
-
if (author.person && author.person.handle !== author.handle) return `${author.name} (${author.person.handle})`;
|
|
38
38
|
return author.name;
|
|
39
39
|
}
|
|
40
40
|
function ago(iso, now = Date.now()) {
|
|
@@ -161,6 +161,36 @@ function formatAccess(access) {
|
|
|
161
161
|
return `${pattern.padEnd(width)} ${permissions.includes("manage") ? "all" : permissions.length === 0 ? "nothing" : permissions.join(" ")}`;
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
+
/** What a grant or token may do, as `--can` takes it: `dev/=read,post;ci/=read`, or `all`. */
|
|
165
|
+
function formatCan(can) {
|
|
166
|
+
const patterns = Object.keys(can);
|
|
167
|
+
if (patterns.length === 1 && patterns[0] === "*" && can["*"].includes("manage")) return "all";
|
|
168
|
+
if (patterns.length === 0) return "nothing";
|
|
169
|
+
return patterns.map((pattern) => `${pattern}=${can[pattern].includes("manage") ? "all" : can[pattern].join(",")}`).join(";");
|
|
170
|
+
}
|
|
171
|
+
/** Rows of cells, each column padded to its widest, two spaces apart. */
|
|
172
|
+
const table = (rows) => {
|
|
173
|
+
const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => row[column].length)));
|
|
174
|
+
return rows.map((row) => row.map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column])).join(" ").trimEnd()).join("\n");
|
|
175
|
+
};
|
|
176
|
+
/** Each grant on a row: its ID, its issuer, the claims it matches, and what it lets do. */
|
|
177
|
+
function formatGrants(grants) {
|
|
178
|
+
return table(grants.map((grant) => [
|
|
179
|
+
grant.id,
|
|
180
|
+
grant.issuer,
|
|
181
|
+
Object.entries(grant.claims).map(([name, value]) => `${name}=${typeof value === "string" ? value : value.join("|")}`).join(" "),
|
|
182
|
+
formatCan(grant.can)
|
|
183
|
+
]));
|
|
184
|
+
}
|
|
185
|
+
/** Each token on a row: its ID, its name, what it may do, and when it expires. */
|
|
186
|
+
function formatTokens(tokens, now = Date.now()) {
|
|
187
|
+
return table(tokens.map((token) => [
|
|
188
|
+
token.id,
|
|
189
|
+
token.name,
|
|
190
|
+
formatCan(token.can),
|
|
191
|
+
`expires ${until(token.expiresAt, now)}${token.profile !== null ? ` · as ${token.profile}` : ""}${token.parent !== null ? ` · minted with ${token.parent}` : ""}`
|
|
192
|
+
]));
|
|
193
|
+
}
|
|
164
194
|
/** `90m` → `1h30m`, `1d`: milliseconds as a duration flag would say them. */
|
|
165
195
|
function span(ms) {
|
|
166
196
|
const parts = [];
|
|
@@ -448,9 +478,9 @@ function rows(entries, pad = padOf(entries)) {
|
|
|
448
478
|
/** An option as help writes it: `--limit <N>`, `-h, --help`, `--label <k[=v|w]>...`. */
|
|
449
479
|
const flag = (option) => `${option.short ? `-${option.short}, ` : ""}--${option.name}${option.value ? option.optionalValue ? ` [${option.value}]` : ` <${option.value}>` : ""}${option.multiple ? "..." : ""}`;
|
|
450
480
|
const shown = (list) => list.filter((option) => !option.hidden);
|
|
451
|
-
/** A command's rows in the list: itself, when it runs alone, and each of its subcommands. */
|
|
481
|
+
/** A command's rows in the list: itself, when it runs alone and no subcommand is what it runs, and each of its subcommands. */
|
|
452
482
|
function listed(command) {
|
|
453
|
-
return [...!command.subcommands || command.runsWithoutSubcommand ? [[usage$1([command]), command.description]] : [], ...(command.subcommands ?? []).filter((sub) => !sub.hidden).map((sub) => [usage$1([command, sub]), sub.description])];
|
|
483
|
+
return [...!command.subcommands || command.runsWithoutSubcommand && !command.subcommands.some((sub) => sub.default) ? [[usage$1([command]), command.description]] : [], ...(command.subcommands ?? []).filter((sub) => !sub.hidden).map((sub) => [usage$1([command, sub]), sub.description])];
|
|
454
484
|
}
|
|
455
485
|
/** Every command, by group, then what the app adds (`serve`, `inspect`), and the options they share. */
|
|
456
486
|
function rootHelp(app, version) {
|
|
@@ -497,7 +527,7 @@ function commandHelp(name, path) {
|
|
|
497
527
|
return lines.join("\n");
|
|
498
528
|
}
|
|
499
529
|
/**
|
|
500
|
-
* The command `words` name, and its subcommand when they name one (`
|
|
530
|
+
* The command `words` name, and its subcommand when they name one (`schedule cancel`, `sources slack
|
|
501
531
|
* read`): what `--help` shows the page of. Null when the first word names no command.
|
|
502
532
|
*/
|
|
503
533
|
function commandPath(words, all) {
|
|
@@ -735,7 +765,7 @@ let typed = [];
|
|
|
735
765
|
let warnings = [];
|
|
736
766
|
/** The board this run reached by its URL, for a failure to name. */
|
|
737
767
|
let reached = null;
|
|
738
|
-
/** The help `words` ask for: a command's page when they name one (`read`, `
|
|
768
|
+
/** The help `words` ask for: a command's page when they name one (`read`, `sources read`), or else every command. */
|
|
739
769
|
function help(words = []) {
|
|
740
770
|
const own = Object.values(app.commands).map((command) => command.spec);
|
|
741
771
|
const path = commandPath(words, [...commands, ...own]);
|
|
@@ -840,7 +870,7 @@ const result = (value) => io.write(`${JSON.stringify(warnings.length ? {
|
|
|
840
870
|
...value,
|
|
841
871
|
warnings
|
|
842
872
|
} : value, null, io.tty ? 2 : void 0)}\n`);
|
|
843
|
-
/** One line of a JSON stream (`tail
|
|
873
|
+
/** One line of a JSON stream (`tail`): compact, wherever it goes. */
|
|
844
874
|
const line = (value) => io.write(`${JSON.stringify(value)}\n`);
|
|
845
875
|
/** A message's ID as you'd write it: `#812` or `812`. It's passed to the board as it is. */
|
|
846
876
|
function messageId(value, what = "message ID") {
|
|
@@ -908,6 +938,31 @@ function scheduleId(value) {
|
|
|
908
938
|
if (!/^[^\s/]+$/.test(value)) throw usage(`${value} isn't a schedule ID like s12.`);
|
|
909
939
|
return value;
|
|
910
940
|
}
|
|
941
|
+
/** A grant's or token's ID as the board gave it: `g3`, `t12`, or another board's own. */
|
|
942
|
+
function trustId(what, value) {
|
|
943
|
+
const like = what === "grant" ? "g3" : "t12";
|
|
944
|
+
if (value === void 0) throw usage(`Missing the ${what} ID, like ${like}.`, { reason: "missing_arguments" });
|
|
945
|
+
if (!/^[^\s/]+$/.test(value)) throw usage(`${value} isn't a ${what} ID like ${like}.`);
|
|
946
|
+
return value;
|
|
947
|
+
}
|
|
948
|
+
/** `--can`, as `x-board-scope` writes it: `dev/=read,post;ci/=read`. */
|
|
949
|
+
function canFlag(value) {
|
|
950
|
+
try {
|
|
951
|
+
return parseScope(value);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
throw usage(`${error.message.replace(/x-board-scope/g, "--can")}.`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/** `--claim k=v`, each again: the same name twice is any of its values. */
|
|
957
|
+
function claimPairs(values) {
|
|
958
|
+
const claims = {};
|
|
959
|
+
for (const value of values ?? []) {
|
|
960
|
+
const at = value.indexOf("=");
|
|
961
|
+
if (at <= 0 || at === value.length - 1) throw usage(`Claims look like name=value, and ${value} isn't one.`);
|
|
962
|
+
(claims[value.slice(0, at)] ??= []).push(value.slice(at + 1));
|
|
963
|
+
}
|
|
964
|
+
return Object.fromEntries(Object.entries(claims).map(([name, list]) => [name, list.length === 1 ? list[0] : list]));
|
|
965
|
+
}
|
|
911
966
|
/** A time flag: anything `Date` reads, as ISO. */
|
|
912
967
|
function instant(flag, value) {
|
|
913
968
|
const time = new Date(value);
|
|
@@ -976,11 +1031,12 @@ function doing() {
|
|
|
976
1031
|
case "typing": return `show typing in ${ref(a)}`;
|
|
977
1032
|
case "sync": return b === void 0 ? `list where ${ref(a)} is synced` : `sync ${ref(a)} with ${b}`;
|
|
978
1033
|
case "schedule": return a === "cancel" ? `cancel schedule ${b}` : `schedule a message in ${message ? ref(a) : a ?? "a channel"}`;
|
|
1034
|
+
case "channels": return a === "create" ? `create ${b ?? "the channel"}` : "list channels";
|
|
979
1035
|
case "schedules":
|
|
980
|
-
case "channels":
|
|
981
1036
|
case "actors": return `list ${command}`;
|
|
1037
|
+
case "grants": return a === "create" ? `grant ${b ?? "an issuer"}` : a === "revoke" ? `revoke grant ${b}` : "list grants";
|
|
1038
|
+
case "tokens": return a === "create" ? `mint token ${b ?? ""}`.trimEnd() : a === "revoke" ? `revoke token ${b}` : "list tokens";
|
|
982
1039
|
case "sources": return b === void 0 ? "list sources" : `${b} ${c ?? "it"} on ${a}`;
|
|
983
|
-
case "identity": return a === "link" ? `link your account on ${b}` : a === "unlink" ? `unlink ${b}` : "list your linked accounts";
|
|
984
1040
|
case "download": return `download ${a ?? "the attachment"}`;
|
|
985
1041
|
case "inbox": return `read ${a}'s inbox`;
|
|
986
1042
|
case "tail": return `follow ${a ?? "the board"}`;
|
|
@@ -1013,6 +1069,7 @@ function missing() {
|
|
|
1013
1069
|
"schedule"
|
|
1014
1070
|
].includes(command) && isMessageRef(a)) return `message ${ref(a)}`;
|
|
1015
1071
|
if (command === "schedule" && a === "cancel" && b !== void 0) return `schedule ${b}`;
|
|
1072
|
+
if ((command === "grants" || command === "tokens") && a === "revoke" && b !== void 0) return `${command === "grants" ? "grant" : "token"} ${b}`;
|
|
1016
1073
|
if (command === "download") return `attachment ${a}`;
|
|
1017
1074
|
}
|
|
1018
1075
|
/**
|
|
@@ -1115,7 +1172,7 @@ function failureOf(error) {
|
|
|
1115
1172
|
});
|
|
1116
1173
|
const specific = (fallback) => /^[a-z]+(_[a-z]+)+$/.test(code) && code !== "invalid_request" && code !== "remote_error" ? code : fallback;
|
|
1117
1174
|
if (status === 400) return theirs("usage", specific("invalid_arguments"), `Can't ${doing()}.`);
|
|
1118
|
-
if (status === 401) return theirs("auth", "login_required", "The board didn't accept your credentials.", [{
|
|
1175
|
+
if (status === 401) return theirs("auth", "login_required", "The board didn't accept your credentials.", process.env.BOARD_TOKEN ? [{ instruction: "Pass a token it accepts in BOARD_TOKEN, or unset it" }] : [{
|
|
1119
1176
|
instruction: "Log in again",
|
|
1120
1177
|
argv: ["vercel", "login"]
|
|
1121
1178
|
}]);
|
|
@@ -1210,7 +1267,10 @@ function connection(url = flags.url, options = {}) {
|
|
|
1210
1267
|
const team = flags.scope ?? flags.team;
|
|
1211
1268
|
const configured = url ?? (team === void 0 ? configuredUrl() : null);
|
|
1212
1269
|
if (configured === null) {
|
|
1213
|
-
const board$1 = board({ backend: vercel(
|
|
1270
|
+
const board$1 = board({ backend: vercel({
|
|
1271
|
+
...team !== void 0 ? { team } : {},
|
|
1272
|
+
...sending(options.token)
|
|
1273
|
+
}) });
|
|
1214
1274
|
return {
|
|
1215
1275
|
url: null,
|
|
1216
1276
|
transport: { source: credentialSource },
|
|
@@ -1221,7 +1281,10 @@ function connection(url = flags.url, options = {}) {
|
|
|
1221
1281
|
reached = at;
|
|
1222
1282
|
const onVercel = vercelBoard(at);
|
|
1223
1283
|
if (onVercel) {
|
|
1224
|
-
const board$3 = board({ backend: vercel(
|
|
1284
|
+
const board$3 = board({ backend: vercel({
|
|
1285
|
+
...onVercel,
|
|
1286
|
+
...sending(options.token)
|
|
1287
|
+
}) });
|
|
1225
1288
|
return {
|
|
1226
1289
|
url: at,
|
|
1227
1290
|
transport: { source: credentialSource },
|
|
@@ -1240,6 +1303,15 @@ function connection(url = flags.url, options = {}) {
|
|
|
1240
1303
|
board: profile !== void 0 ? board$2.as(profile) : board$2
|
|
1241
1304
|
};
|
|
1242
1305
|
}
|
|
1306
|
+
/** For Vercel's API, `token` in place of your own credentials: a board token's secret. */
|
|
1307
|
+
function sending(token) {
|
|
1308
|
+
if (token === void 0) return {};
|
|
1309
|
+
return { fetch: (input, init) => {
|
|
1310
|
+
const request = new Request(input, init);
|
|
1311
|
+
request.headers.set("authorization", `Bearer ${token}`);
|
|
1312
|
+
return globalThis.fetch(request);
|
|
1313
|
+
} };
|
|
1314
|
+
}
|
|
1243
1315
|
/** A handle on the board this command talks to: `vb()`'s, as `--as` says, or the one `connection()` finds. */
|
|
1244
1316
|
function client() {
|
|
1245
1317
|
if (!io.board) return connection().board;
|
|
@@ -1323,94 +1395,6 @@ function portFlag() {
|
|
|
1323
1395
|
if (port !== void 0 && !(Number.isInteger(port) && port >= 0 && port <= 65535)) throw usage(`${flags.port} isn't a port.`, { reason: "invalid_port" });
|
|
1324
1396
|
return port;
|
|
1325
1397
|
}
|
|
1326
|
-
/** How long `identity link` waits for the sign-in or the code. */
|
|
1327
|
-
const LINK_WAIT_MS = 6e5;
|
|
1328
|
-
async function identity(args) {
|
|
1329
|
-
const board = client();
|
|
1330
|
-
const [sub, target] = args;
|
|
1331
|
-
if (sub === void 0) {
|
|
1332
|
-
const view = await board.identity();
|
|
1333
|
-
if (flags.json) return result(view), 0;
|
|
1334
|
-
if (view.links.length === 0) out("no linked accounts");
|
|
1335
|
-
for (const link of view.links) out(`${link.handle ? `@${link.handle} ` : ""}${link.actor} by ${link.method === "code" ? "code" : "sign-in"} · ${link.at}`);
|
|
1336
|
-
if (view.unlinked?.length) out(`not linked ${view.unlinked.join(", ")} (${named()} identity link <key>)`);
|
|
1337
|
-
return 0;
|
|
1338
|
-
}
|
|
1339
|
-
if (sub === "unlink") {
|
|
1340
|
-
if (!target) throw usage(`Missing the account to unlink: a handle or actor ID from \`${named()} identity\`.`, { reason: "missing_arguments" });
|
|
1341
|
-
if (args.length !== 2) throw usage("identity unlink takes one account.");
|
|
1342
|
-
const unlinked = await board.unlinkAccount(target);
|
|
1343
|
-
if (flags.json) result({
|
|
1344
|
-
...unlinked,
|
|
1345
|
-
outcome: "unlinked"
|
|
1346
|
-
});
|
|
1347
|
-
else out(`unlinked ${unlinked.actor}`);
|
|
1348
|
-
return 0;
|
|
1349
|
-
}
|
|
1350
|
-
if (sub !== "link") throw usage(`Unknown command ${sub} for ${named()} identity.`, { reason: "unknown_command" });
|
|
1351
|
-
if (!target) throw usage("Missing the sync entry to link your account on, such as slack.", { reason: "missing_arguments" });
|
|
1352
|
-
if (args.length !== 2) throw usage("identity link takes one sync entry.");
|
|
1353
|
-
const before = new Set((await board.identity()).links.map((link) => `${link.actor}|${link.at}`));
|
|
1354
|
-
const platform = (await board.discovery().catch(() => null))?.sync.find((entry) => entry.key === target)?.platform ?? target;
|
|
1355
|
-
const { code, expiresAt, signIn } = await board.syncCode(target);
|
|
1356
|
-
const consent = signIn !== void 0 && !flags.code;
|
|
1357
|
-
if (consent && browsable(signIn) === null) throw new Failed({
|
|
1358
|
-
class: "platform",
|
|
1359
|
-
reason: "invalid_sign_in",
|
|
1360
|
-
message: "The board's sign-in isn't an http or https URL.",
|
|
1361
|
-
details: [JSON.stringify(signIn)],
|
|
1362
|
-
data: { signIn }
|
|
1363
|
-
});
|
|
1364
|
-
if (flags.json) line({
|
|
1365
|
-
type: "code",
|
|
1366
|
-
sync: target,
|
|
1367
|
-
platform,
|
|
1368
|
-
code,
|
|
1369
|
-
expiresAt,
|
|
1370
|
-
...signIn !== void 0 ? { signIn } : {}
|
|
1371
|
-
});
|
|
1372
|
-
const until = Date.now() + LINK_WAIT_MS;
|
|
1373
|
-
const sleep = () => new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
1374
|
-
/** An account linked on that platform since this run began: where the code or the sign-in landed. */
|
|
1375
|
-
const linkedSince = async () => (await board.identity()).links.find((link) => link.actor.startsWith(`${platform}:user:`) && !before.has(`${link.actor}|${link.at}`));
|
|
1376
|
-
if (consent) {
|
|
1377
|
-
if (!flags.json) {
|
|
1378
|
-
out(`Sign in to ${platform} as yourself to link it (and to post as you there):\n\n ${signIn}\n`);
|
|
1379
|
-
out(`Only open it yourself: whoever completes it links their account to you.`);
|
|
1380
|
-
}
|
|
1381
|
-
if (!flags["no-open"] && !flags.json) openBrowser(signIn);
|
|
1382
|
-
} else if (!flags.json) {
|
|
1383
|
-
const minutes = Math.round((Date.parse(expiresAt) - Date.now()) / 6e4);
|
|
1384
|
-
out(`From your ${platform} account, post this as a message anywhere the board syncs, within ${minutes} minutes:\n\n ${code}\n`);
|
|
1385
|
-
}
|
|
1386
|
-
if (flags["no-wait"]) return 0;
|
|
1387
|
-
while (Date.now() < Math.min(until, consent ? Infinity : Date.parse(expiresAt))) {
|
|
1388
|
-
await sleep();
|
|
1389
|
-
const linked = await linkedSince();
|
|
1390
|
-
if (linked) {
|
|
1391
|
-
if (flags.json) line({
|
|
1392
|
-
type: "linked",
|
|
1393
|
-
account: linked
|
|
1394
|
-
});
|
|
1395
|
-
else out(`linked ${linked.handle ? `@${linked.handle} ` : ""}(${linked.actor})`);
|
|
1396
|
-
return 0;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
throw new Failed({
|
|
1400
|
-
class: "timeout",
|
|
1401
|
-
reason: "timeout",
|
|
1402
|
-
message: consent ? "Nothing linked an account within 10 minutes." : "The code expired before a message carried it.",
|
|
1403
|
-
next: [{
|
|
1404
|
-
instruction: "Try again",
|
|
1405
|
-
argv: [
|
|
1406
|
-
...cli(),
|
|
1407
|
-
"identity",
|
|
1408
|
-
"link",
|
|
1409
|
-
target
|
|
1410
|
-
]
|
|
1411
|
-
}]
|
|
1412
|
-
});
|
|
1413
|
-
}
|
|
1414
1398
|
async function run(command, args) {
|
|
1415
1399
|
switch (command) {
|
|
1416
1400
|
case void 0:
|
|
@@ -1446,12 +1430,31 @@ async function run(command, args) {
|
|
|
1446
1430
|
}
|
|
1447
1431
|
case "conformance": {
|
|
1448
1432
|
if (args.length > 0) throw usage("conformance takes no arguments: pass its channel with --channel.");
|
|
1449
|
-
const
|
|
1433
|
+
const withToken = io.board ? {} : { withToken: (secret) => connection(flags.url, { token: secret }).board };
|
|
1434
|
+
const report = await conformance(client(), {
|
|
1435
|
+
...flags.channel ? { channel: flags.channel } : {},
|
|
1436
|
+
...withToken
|
|
1437
|
+
});
|
|
1450
1438
|
if (flags.json) result(report);
|
|
1451
1439
|
else out(formatConformance(report));
|
|
1452
1440
|
return report.ok ? 0 : 1;
|
|
1453
1441
|
}
|
|
1454
1442
|
case "channels": {
|
|
1443
|
+
const [sub, ...rest] = args;
|
|
1444
|
+
if (sub === "create") {
|
|
1445
|
+
const [path, extra] = rest;
|
|
1446
|
+
if (!path) throw usage("Missing the channel to create, such as dev/alerts.", { reason: "missing_arguments" });
|
|
1447
|
+
if (extra !== void 0) throw usage(`channels create takes one channel: ${extra} is one too many.`);
|
|
1448
|
+
const { channel, created } = await client().createChannel(path);
|
|
1449
|
+
if (flags.json) result({
|
|
1450
|
+
channel,
|
|
1451
|
+
outcome: created ? "created" : "unchanged"
|
|
1452
|
+
});
|
|
1453
|
+
else out(created ? `created ${channel.path}` : `${channel.path} already exists`);
|
|
1454
|
+
return 0;
|
|
1455
|
+
}
|
|
1456
|
+
if (sub !== void 0 && sub !== "list") throw usage(`Unknown command ${sub} for ${named()} channels.`, { reason: "unknown_command" });
|
|
1457
|
+
if (rest.length > 0) throw usage(`channels list takes no arguments: ${rest[0]} is one too many.`);
|
|
1455
1458
|
const channels = await client().channels();
|
|
1456
1459
|
if (flags.json) return result({ channels }), 0;
|
|
1457
1460
|
if (channels.length === 0) return out("no channels yet"), 0;
|
|
@@ -1463,17 +1466,18 @@ async function run(command, args) {
|
|
|
1463
1466
|
group = prefix;
|
|
1464
1467
|
}
|
|
1465
1468
|
const count = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
1466
|
-
|
|
1469
|
+
const { threads, messages, lastIndex } = channel;
|
|
1470
|
+
const counted = threads !== void 0 && messages !== void 0 ? ` ${count(threads, "thread", "threads")} · ${count(messages, "message", "messages")}${lastIndex ? ` · last #${lastIndex}` : ""}` : "";
|
|
1471
|
+
out(`${prefix ? " " : ""}${channel.path}${counted}`);
|
|
1467
1472
|
}
|
|
1468
1473
|
return 0;
|
|
1469
1474
|
}
|
|
1470
1475
|
case "actors": {
|
|
1471
1476
|
const actors = await client().actors();
|
|
1472
1477
|
if (flags.json) return result({ actors }), 0;
|
|
1473
|
-
for (const actor of actors) out(`@${actor.handle} ${actor.name} ${actor.id}
|
|
1478
|
+
for (const actor of actors) out(`@${actor.handle} ${actor.name} ${actor.id}`);
|
|
1474
1479
|
return 0;
|
|
1475
1480
|
}
|
|
1476
|
-
case "identity": return identity(args);
|
|
1477
1481
|
case "read": {
|
|
1478
1482
|
const target = args[0];
|
|
1479
1483
|
const board = client();
|
|
@@ -1620,6 +1624,80 @@ async function run(command, args) {
|
|
|
1620
1624
|
else out(formatSchedule(schedule));
|
|
1621
1625
|
return 0;
|
|
1622
1626
|
}
|
|
1627
|
+
case "grants": {
|
|
1628
|
+
const [verb, ...rest] = args;
|
|
1629
|
+
if (verb === void 0 || verb === "list") {
|
|
1630
|
+
if (rest.length > 0) throw usage(`grants list takes no arguments: ${rest[0]} is one too many.`);
|
|
1631
|
+
const list = await client().grants();
|
|
1632
|
+
if (flags.json) return result({ grants: list }), 0;
|
|
1633
|
+
out(list.length === 0 ? "no grants" : formatGrants(list));
|
|
1634
|
+
return 0;
|
|
1635
|
+
}
|
|
1636
|
+
if (verb === "revoke") {
|
|
1637
|
+
const id = trustId("grant", rest[0]);
|
|
1638
|
+
const { revoked } = await client().revokeGrant(id);
|
|
1639
|
+
if (flags.json) result({
|
|
1640
|
+
grant: id,
|
|
1641
|
+
outcome: revoked ? "revoked" : "unchanged"
|
|
1642
|
+
});
|
|
1643
|
+
else out(revoked ? `revoked ${id}` : `${id} is already gone`);
|
|
1644
|
+
return 0;
|
|
1645
|
+
}
|
|
1646
|
+
if (verb !== "create") throw usage(`Unknown command ${verb} for ${named()} grants.`, { reason: "unknown_command" });
|
|
1647
|
+
const [issuer, extra] = rest;
|
|
1648
|
+
if (!issuer) throw usage("Missing the issuer to grant: vercel, github, or an https URL.", { reason: "missing_arguments" });
|
|
1649
|
+
if (extra !== void 0) throw usage(`grants create takes one issuer, and its claims as --claim name=value: ${extra} is one too many.`);
|
|
1650
|
+
const claims = claimPairs(flags.claim);
|
|
1651
|
+
if (Object.keys(claims).length === 0) throw usage(`Missing the claims ${issuer}'s tokens must carry: pass --claim name=value.`, { reason: "missing_arguments" });
|
|
1652
|
+
const { grant, created } = await client().createGrant({
|
|
1653
|
+
issuer,
|
|
1654
|
+
claims,
|
|
1655
|
+
...flags.can !== void 0 ? { can: canFlag(flags.can) } : {}
|
|
1656
|
+
});
|
|
1657
|
+
if (flags.json) result({
|
|
1658
|
+
grant,
|
|
1659
|
+
outcome: created ? "granted" : "updated"
|
|
1660
|
+
});
|
|
1661
|
+
else out(`${created ? "granted" : "updated"} ${grant.id}`);
|
|
1662
|
+
return 0;
|
|
1663
|
+
}
|
|
1664
|
+
case "tokens": {
|
|
1665
|
+
const [verb, ...rest] = args;
|
|
1666
|
+
if (verb === void 0 || verb === "list") {
|
|
1667
|
+
if (rest.length > 0) throw usage(`tokens list takes no arguments: ${rest[0]} is one too many.`);
|
|
1668
|
+
const list = await client().tokens();
|
|
1669
|
+
if (flags.json) return result({ tokens: list }), 0;
|
|
1670
|
+
out(list.length === 0 ? "no tokens" : formatTokens(list));
|
|
1671
|
+
return 0;
|
|
1672
|
+
}
|
|
1673
|
+
if (verb === "revoke") {
|
|
1674
|
+
const id = trustId("token", rest[0]);
|
|
1675
|
+
const { revoked } = await client().revokeToken(id);
|
|
1676
|
+
if (flags.json) result({
|
|
1677
|
+
token: id,
|
|
1678
|
+
outcome: revoked ? "revoked" : "unchanged"
|
|
1679
|
+
});
|
|
1680
|
+
else out(revoked ? `revoked ${id}` : `${id} isn't a live token of yours`);
|
|
1681
|
+
return 0;
|
|
1682
|
+
}
|
|
1683
|
+
if (verb !== "create") throw usage(`Unknown command ${verb} for ${named()} tokens.`, { reason: "unknown_command" });
|
|
1684
|
+
const [name, extra] = rest;
|
|
1685
|
+
if (!name) throw usage("Missing a name for the token, like feedback-widget.", { reason: "missing_arguments" });
|
|
1686
|
+
if (extra !== void 0) throw usage(`tokens create takes one name: ${extra} is one too many.`);
|
|
1687
|
+
const { token, secret } = await client().createToken({
|
|
1688
|
+
name,
|
|
1689
|
+
...flags.can !== void 0 ? { can: canFlag(flags.can) } : {},
|
|
1690
|
+
...flags.profile !== void 0 ? { profile: flags.profile } : {},
|
|
1691
|
+
...flags.session !== void 0 ? { session: flags.session } : {},
|
|
1692
|
+
...flags.expires !== void 0 ? { expires: flags.expires } : {}
|
|
1693
|
+
});
|
|
1694
|
+
if (flags.json) result({
|
|
1695
|
+
token,
|
|
1696
|
+
secret
|
|
1697
|
+
});
|
|
1698
|
+
else out(secret);
|
|
1699
|
+
return 0;
|
|
1700
|
+
}
|
|
1623
1701
|
case "schedules": {
|
|
1624
1702
|
const list = await client().schedules({
|
|
1625
1703
|
...args[0] ? { re: messageId(args[0]) } : {},
|
|
@@ -1935,7 +2013,6 @@ async function runOnce(argv, options) {
|
|
|
1935
2013
|
/** The commands about the machine the CLI runs on, not the board: none of them run in-process. */
|
|
1936
2014
|
const MACHINE = /* @__PURE__ */ new Set([
|
|
1937
2015
|
"whoami",
|
|
1938
|
-
"identity",
|
|
1939
2016
|
"hooks",
|
|
1940
2017
|
"hook",
|
|
1941
2018
|
"serve",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { m as MESSAGE_TEXT_MAX, p as Labels } from "./schemas-DBmUO6pr.mjs";
|
|
2
|
-
import { t as HttpError } from "./errors-
|
|
2
|
+
import { t as HttpError } from "./errors-CJXhhzvT.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/server/middleware.ts
|
|
5
5
|
/** A write the middleware didn't let through: the API answers 422 with what it responded. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as BoardError } from "./errors-
|
|
1
|
+
import { u as BoardError } from "./errors-CJXhhzvT.mjs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -496,12 +496,16 @@ function guarded(url, doFetch, suppliedToken) {
|
|
|
496
496
|
envRejected = true;
|
|
497
497
|
}
|
|
498
498
|
if (process.env.VERCEL === "1") {
|
|
499
|
+
let edgeless = false;
|
|
499
500
|
if (!headers.has("x-vercel-trusted-oidc-idp-token")) {
|
|
500
501
|
const token = headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1] ?? (typeof suppliedToken === "function" ? await suppliedToken() : suppliedToken) ?? (process.env.BOARD_TOKEN || await hostedToken(url));
|
|
501
|
-
|
|
502
|
+
const edge = token.startsWith("board_") ? await hostedToken(url).catch(() => null) : token;
|
|
503
|
+
if (edge === null) edgeless = true;
|
|
504
|
+
else headers.set("x-vercel-trusted-oidc-idp-token", edge);
|
|
502
505
|
}
|
|
503
506
|
return through(doFetch, input, init, null).catch((error) => {
|
|
504
|
-
|
|
507
|
+
if (!(error instanceof Wall)) throw error;
|
|
508
|
+
throw edgeless ? new BoardError("protected", `${url} is behind Vercel Deployment Protection, and this deployment has no Vercel OIDC token to get through it.`, error.status, { hint: "Enable OIDC on its project, and add it to the board project's Deployment Protection Trusted Sources." }) : new BoardError("protected", `${url} is behind Vercel Deployment Protection, and it rejected this deployment's Vercel OIDC token.`, error.status, { hint: "Add this project and environment to the board project's Deployment Protection Trusted Sources." });
|
|
505
509
|
});
|
|
506
510
|
}
|
|
507
511
|
const used = await (secret ??= readBypass(host));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as setOwn, t as own } from "./own-OwgPWdmh.mjs";
|
|
2
|
-
import { n as foldMessages, r as foldSync } from "./folds-
|
|
2
|
+
import { n as foldMessages, r as foldSync } from "./folds-Cu9Q9zyQ.mjs";
|
|
3
3
|
//#region src/store/project.ts
|
|
4
4
|
/** The message an event concerns, if any. */
|
|
5
5
|
function concernedMessageId(event) {
|