@vercel/board 0.25.1 → 0.26.1
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/dist/{a2-A830f5NS.mjs → a2-C4A4vr0M.mjs} +4 -4
- package/dist/{attempts-CLG840Oj.mjs → attempts-HXcskN1w.mjs} +1 -11
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-CYtfwqAW.mjs → backend-http-3IxGe13a.mjs} +18 -4
- package/dist/{backend-http-CJd4Fu0U.mjs → backend-http-CQWTNzym.mjs} +2 -2
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{backend-vercel-Bqg2QfTd.mjs → backend-vercel-BPyPi8Cv.mjs} +24 -6
- package/dist/{backend-vercel-BhwPExmE.mjs → backend-vercel-l5465vyw.mjs} +3 -3
- package/dist/backend-vercel.mjs +1 -1
- package/dist/backend-vercel.node.d.mts +5 -0
- package/dist/backend-vercel.node.mjs +2 -2
- package/dist/cli-commands.d.mts +1 -1
- package/dist/cli-commands.mjs +1 -1
- package/dist/cli-embedded.d.mts +5 -1
- package/dist/cli-embedded.mjs +8 -4
- package/dist/cli.mjs +4 -4
- package/dist/{commands-D0wXq6CY.d.mts → commands-D6gvrOh3.d.mts} +4 -4
- package/dist/{commands-BtL4jFq8.mjs → commands-nEG47RM_.mjs} +7 -7
- package/dist/{engine-ROa7-IQY.mjs → engine-Bfe6pcXb.mjs} +2 -2
- package/dist/{exports-DCqgO_mr.mjs → exports-B-AeCbYw.mjs} +2 -2
- package/dist/{index.default-BejExznB.mjs → index.default-CfAjbQrU.mjs} +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.node.mjs +1 -1
- package/dist/inspect/app.js +29 -29
- package/dist/inspect/engine.js +5 -5
- package/dist/{main-DOhNCWMd.mjs → main-DGyQ4gje.mjs} +24 -9
- package/dist/{node-Bkd1x0QI.mjs → node-Ybi9E6bO.mjs} +67 -37
- package/dist/{serve-D2O0C6Kk.mjs → serve-BDLJebk7.mjs} +3 -3
- package/dist/{server-BF4WJ7qi.mjs → server-BSXTDTOp.mjs} +3 -3
- package/dist/{ids-C6AYo1bu.mjs → wait-until-Dw4XrIDQ.mjs} +12 -1
- package/dist/{watchers-hash-BHNXy_N7.mjs → watchers-hash-BTF_bMIN.mjs} +4 -4
- package/docs/01-board/04-messages.md +1 -1
- package/docs/01-board/05-reading.md +2 -2
- package/docs/01-board/06-watchers.md +7 -5
- package/docs/02-sync/01-overview.md +2 -2
- package/docs/03-guides/03-ci.md +2 -2
- package/docs/03-guides/04-triage.md +5 -5
- package/docs/04-reference/01-cli.md +10 -4
- package/docs/04-reference/03-http.md +1 -1
- package/docs/index.md +2 -2
- package/package.json +1 -1
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,
|
|
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=
|
|
3
|
-
`)}function
|
|
4
|
-
`)}`:e,
|
|
5
|
-
`).filter(e=>/^📎 .* didn't come over: /u.test(e)),B=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),fe=(e,t)=>e.length===t.length&&e.every((e,n)=>B(e)===B(t[n])),pe=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,V=6e4,me=new AbortController().signal,he=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 y&&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=me,r=c){let i=o().backend;return i[e].call(i,r,t,n)}let f=t=>{let n=e[t];if(!n)throw m(`no sync entry named ${t}`);return n},p=e=>e.slice(0,e.indexOf(`:`)),g=t=>e[p(t)]!==void 0,v=e=>e.slice(e.indexOf(`:`)+1);async function b(e){try{return await l(`syncThread`,{thread:e})}catch(e){if(W(e))return null;throw e}}async function S(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 w(e){return(await S(e)).find(t=>t.id===e)??null}async function T(e){return e.length===0?{}:(await l(`syncCopies`,{message:[...new Set(e)]})).copies}let E=(e,t)=>l(`syncFind`,e,t);async function D(){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 O=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),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=se(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 v(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:v(n)}catch{return null}}async function ne(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 F(e){let t=e.indexOf(`:`);if(t<=0)throw m(`sync targets look like <key>:<channel or thread>, got: ${e}`);let n=e.slice(0,t),r=f(n);try{let i=oe.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 m(`can't sync to ${e}: ${t.message}`)}}async function B(e,t){let{event:n,raw:r,dedupe:i}=ce.parse(H({...t,raw:he(t.raw)}));for(let t of[n.channel,n.thread,`subthread`in n?n.subthread:null])if(t!==null&&!t.startsWith(`${e}:`))throw m(`provider locations must belong to sync entry ${e}`);let a=le(f(e));(!a||await a(v(n.channel),t=>A(e,t))||await G(n.thread))&&await l(`inboxPush`,{key:e,events:[{id:i,event:n,raw:r}]})}async function G(e){try{return(await E({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 s(`no sync entry named ${t}`);let i;try{i=await r.receive(t,n,e=>B(t,e))}catch(e){return console.error(`[board] intake failed`,e),new Response(`intake failed`,{status:500})}return ae(_e([t]).catch(e=>console.error(`[board] couldn't pull ${t}'s inbox:`,U(e)))),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}:`,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 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 E({thread:a.thread,message:a.message})).message,o=t===null?null:await w(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:U(e)}));i.push({url:a.url,message:null,snapshot:c.snapshot,unavailable:c.snapshot?null:(c.unavailable??`unknown`).slice(0,300)})}return H(i)}async function be(e,n,r){let i=[],o=[],s=!1;for(let c of n.slice(0,10)){if(s){o.push(ue(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 y&&(e.code===`too_large`||e.code===`no_direct_uploads`)){o.push(L(c,`over ${C/1024/1024} MB`));continue}if(!R(e))throw e;s=!0,o.push(ue(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 xe(e,t,n){let{event:r,raw:i}=t,a=f(e),s=(await E({thread:r.thread})).thread??void 0;if(r.kind===`message`){let t=(await E({thread:r.subthread??r.thread,message:r.message})).message;if(t!==null){let e=(await w(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(s,r))throw Error(`holding ${r.message} until ${r.author.id}'s board message there has its receipt`);let i=r.author.isBot?void 0:pe.exec(r.text)?.[1];if(i&&await Te(e,r,i))return}let c=`author`in r?r.author:{id:`${a.platform}:system`,handle:a.platform,name:a.platform,avatar:null,isBot:!0},l,u=()=>l??=N(a.event({id:n.id,key:e,event:r,raw:i})),d,p,h=!0,g={direction:`in`,key:e,channel:v(r.channel),get event(){return u()},board:o().client({author:c,key:e,...Ce(e,r)},n.id),import(r,i={}){if(!h)throw m(`the sync function has finished`);return d??=Se(e,t,n,s,r,i),d},permalink(){let e=p??a.permalink(u());return p=e,e.catch(()=>{p===e&&(p=void 0)}),e},resolve:t=>A(e,t)};try{await a.sync(g),await d}finally{h=!1}}async function Se(e,t,n,r,i,a){let o=i??(r?(await b(r))?.channel:void 0);if(o===void 0)return{root:null,message:null};try{return await Ae(e,t,n,d.parse(o),M.parse(a))}catch(e){if(!(e instanceof te))throw e;let n=(await E({thread:t.event.thread})).thread;return{root:n===null?null:(await b(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(v(r),`thread`,{markdown:e})}}}async function we(e,t){let n=(await E({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)<V&&!i[t]&&!e.undelivered[t];if(e!==void 0){let n=await b(e);if(!n)return!1;let r=n.targets.filter(e=>n.external[e]===t.thread),a=await S(n.root),o=await T(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-V}),o=await T(a.map(e=>e.id));for(let e of a){let n=await b(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:x(t.subthread??t.thread,t.message)})}catch(e){if(W(e))return!1;throw e}return await f(e).react(v(t.subthread??t.thread),t.message,`check`,!0).catch(t=>console.warn(`[board] couldn't confirm a linking code on ${e}:`,U(t))),!0}let Ee=e=>f(e).users!==null;async function De(e,t,n){let r=f(e);if(!r.users)throw m(`${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 m(`${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 m(`${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,i,a){let{event:o,raw:s}=t,c=Ce(e,o),u=f(e),d=(await E({thread:o.thread})).thread??void 0,p=d?await b(d):null,h=p?.root,g=e=>ee(`board-inbound`,n.id,String(e)),_={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)},x=async({attachments:t=[],...n})=>({id:await y(await l(`syncImport`,{op:`post`,from:e,source:_,...n,origin:n.origin??null,...t.length?{attachments:t.map(e=>e.id)}:{}})),re:n.re??null}),S=e=>({root:e.re??e.id,message:e.id}),C=(e={})=>{let t={...e,...a.labels};for(let e of a.unset??[])delete t[e];return t},T=e=>{if(!a.root)return e;let t=a.root(N(structuredClone(e)));if(t&&typeof t==`object`&&`then`in t)throw m(`root overrides must be synchronous`);let n=M.pick({text:!0,labels:!0}).parse(t);return{...e,text:n.text??e.text,labels:{...e.labels,...n.labels}}},D=(t,n,r)=>l(`syncImport`,{op:`labels`,author:t,from:e,origin:{thread:o.thread},set:n,unset:r}),O=async(e,t)=>{if(!a.labels&&!a.unset)return;let n=await w(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 D(e,r,i)};switch(o.kind){case`message`:{let e=(await E({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 be(u,o.files,n.attempt):{attachments:[],notes:[]},c=await ye(u,o.links??[],p?.channel??i),l={...c.length?{links:c}:{},author:o.author,text:I(z(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 g(0)},d={...l,...a.noSync?{noSync:!0}:{}};if(h!==void 0)return await O(o.author,h),S(await x({...d,re:h}));let f=await u.fetchRoot(v(o.thread));if(f===null){let e=T({text:o.text,labels:{},author:o.author,raw:s});return S(await x({...l,channel:i,text:I(z(a.text??e.text,r),t.length>0),labels:C(e.labels)}))}let m=T({...f,raw:f.raw??null}),_=await x({author:f.author,channel:i,text:I(m.text),labels:C(m.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`root:${n.id}`,context:null},id:await g(1)});return S(await x({...d,re:_.id}))}case`edited`:case`deleted`:case`reaction`:{let t=(await E({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:`${u.platform}:system`,handle:u.platform,name:u.platform,avatar:null,isBot:!0},i={thread:o.subthread??o.thread,message:o.message};if(h!==void 0&&await O(n,h),o.kind===`deleted`)await l(`syncImport`,{op:`delete`,author:n,from:e,origin:i});else if(o.kind===`edited`){let r=await w(t),s=I(z(a.text??o.text,r?de(r.text):[]),(r?.attachments.length??0)>0);r&&!o.linksOnly&&r.text!==s&&await y(await l(`syncImport`,{op:`edit`,author:n,from:e,origin:i,text:s,source:_}));let c=r?await ye(u,o.links??[],r.channel,r.links):[];r&&!fe(c,r.links)&&await l(`syncImport`,{op:`links`,author:n,from:e,origin:i,links:c})}else{if(u.singleReaction){let a=r(o.emoji);for(let[r,s]of Object.entries((await w(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})}(!u.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 w(h),r=t?.origin?.message===`lifecycle:${n.id}`,i=C((r?T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}):null)?.labels??o.labels),c=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)||c(e)));if(t&&!t.deleted&&(Object.keys(u).length||d.length)&&await D(o.author,u,d),o.retitled&&t&&!t.deleted&&t.from===e&&t.origin?.thread===o.thread){let n=I(a.text??T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:t.labels,author:o.author,raw:s}).text);t.text!==n&&await y(await l(`syncImport`,{op:`edit`,author:o.author,from:e,origin:{thread:t.origin.thread,message:t.origin.message},text:n}))}return o.reply?S(await x({author:o.author,re:h,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await g(1)})):{root:h,message:r?h:null}}if(!o.createRoot)return{root:null,message:null};let t=T({text:o.url?`${o.title}\n${o.url}`:o.title,labels:o.labels,author:o.author,raw:s}),r=await x({author:o.author,channel:i,text:I(o.reply?t.text:a.text??t.text),labels:C(t.labels),origin:{channel:o.channel,thread:o.thread,subthread:null,message:`lifecycle:${n.id}`,context:null},id:await g(0)});return o.reply?S(await x({author:o.author,re:r.id,text:I(a.text??o.reply),...a.noSync?{noSync:!0}:{},id:await g(1)})):S(r)}}}function je(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?K(e,a.id,n)??a.name:null})}function K(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 O(n,i)){let t=r(e);if(t)return t}return null}async function Me(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 E({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:`,U(n)),null}}function Ne(e,t,n){let r=f(p(e)),i=je(r,t,n,re(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Le(t)}`:i}}async function Pe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Me(a,r,!0);if(!o)return null;try{return await a.postAs(o,v(t),n,Ne(e,r,i))}catch(t){return console.warn(`[board] couldn't post #${r.id} to ${e} as its author; posting as the app:`,U(t)),null}}function Fe(t,n,r,i,a){let o=i.byId[r.by.actor],s=o?.name??r.by.actor;r.by.profile&&o?.handle.includes(`/`)&&(s=`${o.name} via ${o.handle.split(`/`)[0]}`),r.from&&(s=`${s} (${e[r.from]?.platform??r.by.actor.slice(0,r.by.actor.indexOf(`:`))})`);let c=f(t),l=(a??(e=>c.render(e)))(N({message:{...structuredClone(r),text:re(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>K(c,e,i),label:s,target:n}));if(typeof l!=`string`)throw m(`a provider renderer must return markdown`);let u=je(c,r,i,l);return r.by.profile&&!r.from?{markdown:u,as:{name:s,avatar:Ie(r,o,i)}}:{markdown:u}}function Ie(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 Le=e=>e.attachments.map(t=>`\n📎 ${t.name} (${n(t.size)}, on the board: #${e.id})`).join(``),q=(e,t,n,r=!1,i)=>{let a=p(e),{markdown:o,as:s}=Fe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Le(t)}`:o,...s?{as:s}:{}}};async function Re(e,t,n,r,i){let a=e.replyTo===null?void 0:(await T([e.replyTo]))[e.replyTo]?.[t],o=a!==void 0&&a.thread!==n?a.thread:n,s=f(p(t)),c=q(t,e,r,!1,i),l,u;if(!c.markdown.trim()&&e.attachments.length&&s.uploadsFiles)try{let n=await Be(e,t,o);u=n.message,l=n.files}catch(n){if(!s.refusesFiles(n)&&!R(n))throw n;c=q(t,e,r,!0,i),u=(await s.post(v(o),`thread`,c)).message,l=null}else{let n=await Pe(t,o,`thread`,e,r);if(n){let r={thread:o,message:n.message,asUser:!0};return await J(e,t,r),r}if(!c.markdown.trim())throw m(`a renderer must return text unless the message has media`);u=(await s.post(v(o),`thread`,c)).message}let d={thread:o,message:u,...l===void 0?{}:{files:l}};return await J(e,t,d),d}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 ze(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 Be(e,t,n){return f(p(t)).uploadFiles(v(n),await ze(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 Be(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(v(n.thread),n.message,q(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 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||ie(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 Q(e,t,n,r,i){let a,o=!0,s=()=>m(`the sync function has finished`),c={direction:`out`,key:e,channel:r===null?null:j(e,r),event:N(structuredClone(t)),message:N(structuredClone(n)),target:r,deliver(e={}){if(!o)throw s();return a??=r===null?Promise.resolve():i(e.render),a},start(t){if(!o)throw s();return Ve(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(c),await a}finally{o=!1}}async function Ve(e,t,n){let{target:r,kind:i}=await F(`${e}:${n}`),a=t.re===null?t:await w(t.re);!a||a.deleted||a.noSync||r!==(a.origin?a.origin.channel??h(a.origin.thread):null)&&await l(`syncReceipt`,{kind:`thread`,message:a.id,target:r,targetKind:i,cause:`config`})}async function He(t,n){let r=Object.keys(e).filter(t=>e[t].canStartThreads);if(t.payload.noSync||r.length===0)return;let i=await w(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 Ue(e,t){let{thread:n,target:r}=e.payload;if(!g(r)||(e.payload.kind??(u(r)?`thread`:`channel`))===`thread`)return;let i=await b(n),[a,...o]=i?await S(i.root):[];if(!i||!a||a.id!==i.root||a.deleted)return;let s=[];try{await Q(p(r),e,a,r,e=>We(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function We(e,t,n,r,i,a){let o=await D(),s=await T([t.id,...n.map(e=>e.id)]),c=e=>(i.push({id:t.id,target:r,error:e}),e),l=e.external[r];if(l){let e=s[t.id]?.[r];if(e)try{await Y(t,r,e,o,a)}catch(e){throw c(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 m(`a renderer must return text unless the message has media`);let s,c=!1,u=await(async()=>{if(!i){let i=await Pe(r,r,`channel`,t,o);return i?(c=!0,i):e.post(v(r),`channel`,n)}try{let i=await e.post(v(r),`channel`,{...n,files:await ze(t)});return s=i.files??[],i}catch(n){if(!e.refusesFiles(n)&&!R(n))throw n;return s=null,e.post(v(r),`channel`,q(r,t,o,!0,a))}})();l=`${p(r)}:${u.thread}`;let d={thread:l,message:u.message,...s===void 0?{}:{files:s},...c?{asUser:!0}:{}};await J(t,r,d),await Y(t,r,d,o,a)}catch(e){throw c(e)}let u=[];for(let e of n)if(!(e.deleted||e.noSync||Ge(e,r)))try{await Y(e,r,s[e.id]?.[r]??await Re(e,r,l,o,a),o,a)}catch(t){u.push({id:e.id,target:r,error:t})}if(i.push(...u),u.length)throw u[0].error}let Ge=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ke(e,t){if(!e.message)return;if(e.message.re===null)return He(e,t);if(e.payload.noSync)return;let n=await b(e.payload.thread);if(!n||n.targets.length===0)return;let r=await w(e.message.id);if(!r||r.deleted)return;let i=await D(),a=[];for(let t of n.targets){let o=n.external[t];if(g(t)&&o&&!Ge(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await T([r.id]))[r.id]?.[t]??await Re(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 qe(e){let t=(await T([e.payload.id]))[e.payload.id]??{};if(Object.keys(t).length===0)return;let n=e.type===`message.edited`?await w(e.payload.id):e.message??await w(e.payload.id);if(!n||e.type===`message.edited`&&n.deleted)return;let i=await D();for(let[a,o]of Object.entries(t))if(!(!g(a)||e.payload.from!==null&&p(a)===e.payload.from))try{await Q(p(a),e,n,a,async t=>{let s=f(p(a)),c=v(o.thread),l=o.asUser&&(e.type===`message.edited`||e.type===`message.deleted`),u=l?await Me(s,n,!1):null;if(l&&!u){console.warn(`[board] skipped ${e.type} on ${a}: #${n.id} was posted as its author, and their token is gone`);return}e.type===`message.edited`?u?await s.editAs(u,c,o.message,Ne(a,n,i)):await s.edit(c,o.message,q(a,n,i,o.files===null,t)):e.type===`message.deleted`?(u?await s.deleteAs(u,c,o.message):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 Je(e){let{id:t}=e.payload,n=(await T([t]))[t]??{},r=e.message;if(!r||Object.keys(n).length===0)return;let i=await w(t);if(!i||i.deleted)return;let a=await D(),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(v(r.thread),r.message,n)})}catch(e){console.warn(`[board] skipped labels.changed on ${t}:`,U(e))}}async function Ye(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ue(e,t);case`message.posted`:return Ke(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return qe(e);case`labels.changed`:return Je(e)}}async function Xe(e,t){let n=e[0]?await b(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(v(n),a,t),n}catch(e){return console.warn(`[board] skipped typing on ${n}:`,U(e)),null}}))).filter(e=>e!==null)}async function Ze(e,t){try{return`${e}:${await f(e).resolveChannel(t)}`}catch(e){throw m(`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 Qe(e,t,n){let r=f(e);try{return await r.read(t,{limit:n})}catch(n){throw n instanceof P?new y($(n.code),n.code,n.message):m(`can't read ${t} from ${e}: ${U(n)}`,`platform_error`)}}async function $e(e,t){let n=f(e);try{return await n.join(t)}catch(n){throw n instanceof P?new y($(n.code),n.code,n.message):m(`can't join ${t} on ${e}: ${U(n)}`,`platform_error`)}}return{entries:e,bind(e){i=e},caller:c,deliver:Ye,receive:xe,pull:_e,normalizeTarget:F,findByEmail:ne,handleWebhook:ge,inboxChannel:Ze,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Xe,readSource:Qe,joinSource:$e}}export{G as createSyncEngine};
|
|
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,k as h,l as g,m as _,n as v,o as y,p as b,r as x,s as ee,t as te,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,ne=/!\[([^\]]+)\]\(https:\/\/vercel\.com\/static\/status\/[^)]+\)\s*(?:\[[^\]]*\]\((https?:\/\/[^)\s]+)\))?/;function k(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
|
+
`,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=ne.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 A={ready:`✅`,building:`⏳`,queued:`⏳`,initializing:`⏳`,error:`❌`,failed:`❌`,canceled:`⚪`,ignored:`⚪`},j=(e,t)=>t?`[${e}](${t})`:e;function re(e){return e.map(e=>`- ${[`${A[e.status.toLowerCase()]??`▲`} **${j(e.project,e.projectUrl)}**`,`${j(e.status,e.deploymentUrl)}${e.skipped?` (skipped)`:``}`,...e.previewUrl?[j(`Preview`,e.previewUrl)]:[],...e.updated?[e.updated]:[]].join(` · `)}`).join(`
|
|
3
|
+
`)}function M(e,t){let n=t===`github:user:35613825`?k(e):null;return n?`**▲ Vercel**\n${re(n)}`:e}const ie=e=>e>10;i({id:e().min(1),platform:e().min(1),key:e().min(1),...g.shape,raw:w()});const N=i({root:T(e=>typeof e==`function`).optional(),text:e().optional(),labels:p.optional(),unset:E(_).optional(),noSync:c().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 ae=i({target:e().min(1),channel:e().min(1),kind:C([`channel`,`thread`])}).strict();var F=class extends Error{code;constructor(e,t){super(t),this.code=e}};const oe=e=>e instanceof F&&e.code===`channel_not_found`,se=i({event:l,raw:w(),dedupe:e().min(1)}),I=/* @__PURE__ */ new WeakMap,ce=e=>I.get(e),L=(e,t=!1)=>{let n=o(e),r=n.trim()===``?t?``:`(no text)`:n;return r.length>1e4?`${r.slice(0,f-1).toWellFormed()}…`:r},R=(e,n,r=e.name??`a file`)=>`📎 ${r}${e.size===void 0?``:` (${t(e.size)})`} didn't come over: ${n}`,z=e=>e instanceof m&&e.code===`no_files`,le=e=>{let t=e.name??`a file`;if(!e.url||!/^https?:\/\//.test(e.url))return R(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 R(e,`this board keeps no files`,`[${n}](${r})`)},B=(e,t)=>t.length?`${e.trim()?`${e}\n\n`:``}${t.join(`
|
|
4
|
+
`)}`:e,ue=e=>e.split(`
|
|
5
|
+
`).filter(e=>/^📎 .* didn't come over: /u.test(e)),V=e=>JSON.stringify([e.url,e.message,e.snapshot,e.unavailable]),de=(e,t)=>e.length===t.length&&e.every((e,n)=>V(e)===V(t[n])),fe=/^\s*`?(vb-[a-z0-9]{4}-[a-z0-9]{4})`?\s*$/i,H=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 ne=(e,t)=>Object.entries(e.links).filter(([,e])=>e===t).map(([e])=>e),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=oe(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(!d(t))return _(t);try{let n=f(e).inboxChannel(e,t);return n===null?null:_(n)}catch{return null}}async function re(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 V(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=>A(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=>V(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(le(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(le(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:fe.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=>A(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),N.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)<H&&!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-H}),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=N.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(B(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(B(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(B(i.text??a.text,o?ue(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&&!de(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:a.url?`${a.title}\n${a.url}`:a.title,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:a.url?`${a.title}\n${a.url}`:a.title,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:a.url?`${a.title}\n${a.url}`:a.title,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?K(e,a.id,n)??a.name:null})}function K(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 ne(n,i)){let t=r(e);if(t)return t}return null}async function Me(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 Ne(e,t,n){let r=f(p(e)),i=je(r,t,n,M(t.text,t.by.actor));return{markdown:t.attachments.length>0&&!r.uploadsFiles?`${i}\n${Le(t)}`:i}}async function Pe(e,t,n,r,i){let a=f(p(e));if(!r.text.trim())return null;let o=await Me(a,r,!0);if(!o)return null;try{return await a.postAs(o,_(t),n,Ne(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 Fe(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:M(r.text,r.by.actor)},author:o?{...o}:null,mentionOf:e=>K(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:Ie(r,o,i)}}:{markdown:d}}function Ie(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 Le=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}=Fe(a,e,t,n,i);return{markdown:t.attachments.length>0&&(r||!f(a).uploadsFiles)?`${o}\n${Le(t)}`:o,...s?{as:s}:{}}};async function Re(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 Be(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 Pe(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 ze(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 Be(e,t,n){return f(p(t)).uploadFiles(_(n),await ze(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 Be(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||ie(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: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 Ve(e,n,t)},resolve:t=>A(e,t)};try{await f(e).sync(l),await a}finally{o=!1}}async function Ve(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 He(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 Ue(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=>We(i,a,o,r,s,e))}catch(e){X(s,a.id,r,e)}await Z(t,s)}async function We(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 Pe(r,r,`channel`,t,o);return i?(l=!0,i):e.post(_(r),`channel`,n)}try{let i=await e.post(_(r),`channel`,{...n,files:await ze(t)});return c=i.files??[],i}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||Ge(e,r)))try{await Y(e,r,c[e.id]?.[r]??await Re(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 Ge=(e,t)=>e.origin?.thread===t||e.from!==null&&p(t)===e.from;async function Ke(e,t){if(!e.message)return;if(e.message.re===null)return He(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&&!Ge(r,t))try{await Q(p(t),e,r,t,async e=>{let n=(await E([r.id]))[r.id]?.[t]??await Re(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 qe(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 Me(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,Ne(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 Je(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 Ye(e,{attempt:t}){switch(e.type){case`thread.synced`:return Ue(e,t);case`message.posted`:return Ke(e,t);case`message.edited`:case`message.deleted`:case`reaction.added`:case`reaction.removed`:return qe(e);case`labels.changed`:return Je(e)}}async function Xe(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 Ze(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 Qe(e,t,n){let r=f(e);try{return await r.read(t,{limit:n})}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 $e(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:Ye,receive:xe,pull:_e,normalizeTarget:I,findByEmail:re,handleWebhook:ge,inboxChannel:Ze,signsIn:Ee,signIn:De,signedIn:Oe,redeemSignIn:ke,typing:Xe,readSource:Qe,joinSource:$e}}export{he as createSyncEngine};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { i as fileSize, n as DIRECT_UPLOAD_BYTES_MAX } from "./files-SniSmeEr.mjs";
|
|
2
2
|
import { a as AttachmentId } from "./schemas-Be08nyfW.mjs";
|
|
3
|
-
import { d as VERSION, i as withMentions } from "./watchers-hash-
|
|
3
|
+
import { d as VERSION, i as withMentions } from "./watchers-hash-BTF_bMIN.mjs";
|
|
4
4
|
import { n as displayEmoji, r as normalizeEmoji } from "./emoji-CTLpvH-A.mjs";
|
|
5
5
|
import { t as BoardError } from "./client-D0Px7KzF.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-
|
|
7
|
-
import {
|
|
8
|
-
import { t as http } from "./backend-http-
|
|
9
|
-
import { t as board } from "./server-
|
|
6
|
+
import { i as groups, n as filters$1, o as options, r as globalOptions, s as usage$1, t as commands } from "./commands-nEG47RM_.mjs";
|
|
7
|
+
import { i as handLogin, l as vercelProjectFile, n as connect, t as boardUrl } from "./node-Ybi9E6bO.mjs";
|
|
8
|
+
import { t as http } from "./backend-http-CQWTNzym.mjs";
|
|
9
|
+
import { t as board } from "./server-BSXTDTOp.mjs";
|
|
10
10
|
import { t as conformance } from "./conformance-DlnfuzEB.mjs";
|
|
11
|
-
import { n as vercel, t as credentialSource } from "./backend-vercel-
|
|
11
|
+
import { n as handTeam, r as vercel, t as credentialSource } from "./backend-vercel-BPyPi8Cv.mjs";
|
|
12
12
|
import { t as displayText } from "./bots-CGkO76qH.mjs";
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
14
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -916,7 +916,7 @@ function filters(channel) {
|
|
|
916
916
|
...flags.by ? { by: flags.by.replace(/^@/, "") } : {},
|
|
917
917
|
...flags.mentions ? { mentions: flags.mentions.replace(/^@/, "") } : {},
|
|
918
918
|
...flags.label?.length ? { label: flags.label } : {},
|
|
919
|
-
...flags.
|
|
919
|
+
...flags.source ? { from: flags.source } : {},
|
|
920
920
|
...flags.text ? { text: flags.text } : {},
|
|
921
921
|
...flags.after !== void 0 ? { after: flags.after.replace(/^#/, "") } : {}
|
|
922
922
|
};
|
|
@@ -1073,6 +1073,11 @@ function failureOf(error) {
|
|
|
1073
1073
|
...own("limit", "payload_too_large"),
|
|
1074
1074
|
message: `Can't ${doing()}. ${sentence(message)}`
|
|
1075
1075
|
};
|
|
1076
|
+
case "no_team": return {
|
|
1077
|
+
...own("platform", "api_error"),
|
|
1078
|
+
message: "Couldn't tell which team's board to use.",
|
|
1079
|
+
details: [message]
|
|
1080
|
+
};
|
|
1076
1081
|
case "upload_failed": return {
|
|
1077
1082
|
...own("platform", "upload_failed"),
|
|
1078
1083
|
message: `Failed to ${doing()}.`,
|
|
@@ -1509,7 +1514,7 @@ async function run(command, args) {
|
|
|
1509
1514
|
text: content,
|
|
1510
1515
|
...files.length ? { attachments: files } : {},
|
|
1511
1516
|
...flags.label?.length ? { labels: labelPairs(flags.label) } : {},
|
|
1512
|
-
...flags
|
|
1517
|
+
...flags["idempotency-key"] ? { id: flags["idempotency-key"] } : {},
|
|
1513
1518
|
...flags["no-sync"] ? { noSync: true } : {}
|
|
1514
1519
|
};
|
|
1515
1520
|
const message = re === void 0 ? await board.post(target, {
|
|
@@ -1528,7 +1533,7 @@ async function run(command, args) {
|
|
|
1528
1533
|
const message = await board.reply(id, {
|
|
1529
1534
|
text: content,
|
|
1530
1535
|
...files.length ? { attachments: files } : {},
|
|
1531
|
-
...flags
|
|
1536
|
+
...flags["idempotency-key"] ? { id: flags["idempotency-key"] } : {},
|
|
1532
1537
|
...flags["no-sync"] ? { noSync: true } : {}
|
|
1533
1538
|
});
|
|
1534
1539
|
warnUnknownMentions(message.unknownMentions);
|
|
@@ -1830,6 +1835,16 @@ const context = () => ({
|
|
|
1830
1835
|
* stopped.
|
|
1831
1836
|
*/
|
|
1832
1837
|
async function main(argv, options) {
|
|
1838
|
+
handLogin(options.login ?? null);
|
|
1839
|
+
handTeam(options.team ?? null);
|
|
1840
|
+
try {
|
|
1841
|
+
return await runOnce(argv, options);
|
|
1842
|
+
} finally {
|
|
1843
|
+
handLogin(null);
|
|
1844
|
+
handTeam(null);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
async function runOnce(argv, options) {
|
|
1833
1848
|
app = options;
|
|
1834
1849
|
typed = argv;
|
|
1835
1850
|
flags = {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -118,54 +118,89 @@ const expiry = (token) => {
|
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
120
|
/**
|
|
121
|
-
* Where `audience`'s token is cached: one per board, and with a login
|
|
122
|
-
*
|
|
121
|
+
* Where `audience`'s token is cached: one per board, and with a login a CLI hands over, one per
|
|
122
|
+
* login too, since it can be someone else's than `vercel login`'s.
|
|
123
123
|
*/
|
|
124
|
-
const cacheFile = (audience) => {
|
|
125
|
-
const login = process.env.VERCEL_AUTH_TOKEN;
|
|
124
|
+
const cacheFile = (audience, login) => {
|
|
126
125
|
const key = login ? `${audience}\n${login}` : audience;
|
|
127
126
|
return join(configDir(), "tokens", `${createHash("sha256").update(key).digest("hex").slice(0, 32)}.json`);
|
|
128
127
|
};
|
|
129
|
-
async function readCache(audience) {
|
|
128
|
+
async function readCache(audience, login) {
|
|
130
129
|
try {
|
|
131
|
-
const cached = JSON.parse(await readFile(cacheFile(audience), "utf8"));
|
|
130
|
+
const cached = JSON.parse(await readFile(cacheFile(audience, login), "utf8"));
|
|
132
131
|
return cached.expiresAt - 3e5 > Date.now() ? cached : null;
|
|
133
132
|
} catch {
|
|
134
133
|
return null;
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
|
-
async function writeCache(audience, cached) {
|
|
138
|
-
|
|
136
|
+
async function writeCache(audience, login, cached) {
|
|
137
|
+
const tokens = join(configDir(), "tokens");
|
|
138
|
+
await mkdir(tokens, {
|
|
139
139
|
recursive: true,
|
|
140
140
|
mode: 448
|
|
141
141
|
});
|
|
142
|
-
await writeFile(cacheFile(audience), JSON.stringify(cached), { mode: 384 });
|
|
142
|
+
await writeFile(cacheFile(audience, login), JSON.stringify(cached), { mode: 384 });
|
|
143
|
+
const dead = Date.now() - 864e5;
|
|
144
|
+
for (const name of await readdir(tokens).catch(() => [])) {
|
|
145
|
+
const file = join(tokens, name);
|
|
146
|
+
const written = (await stat(file).catch(() => null))?.mtimeMs;
|
|
147
|
+
if (written !== void 0 && written < dead) await unlink(file).catch(() => {});
|
|
148
|
+
}
|
|
143
149
|
}
|
|
150
|
+
/** The login a CLI that runs board's hands it to ask for (`vercel board`'s), while one does. */
|
|
151
|
+
let handed = null;
|
|
144
152
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
153
|
+
* Hands board a login to ask for when a request needs one, as `vercel board` does with its own:
|
|
154
|
+
* nothing asks for it before then, so help, a typo, or misuse never refreshes or prompts. Null
|
|
155
|
+
* takes it back.
|
|
147
156
|
*/
|
|
148
|
-
|
|
157
|
+
function handLogin(login) {
|
|
158
|
+
handed = login;
|
|
159
|
+
}
|
|
160
|
+
/** Whether the login is one a CLI handed over (`createApp`'s `login`, or `VERCEL_AUTH_TOKEN`). */
|
|
161
|
+
const loginHanded = () => handed !== null || !!process.env.VERCEL_AUTH_TOKEN;
|
|
162
|
+
/**
|
|
163
|
+
* Your Vercel login: what the CLI that runs board's hands it, as `vercel board` does (its `login`,
|
|
164
|
+
* or `VERCEL_AUTH_TOKEN` from its `--token`), or else what `vercel login` saved.
|
|
165
|
+
*/
|
|
166
|
+
const vercelLogin = () => handed ? handed() : process.env.VERCEL_AUTH_TOKEN ? Promise.resolve(process.env.VERCEL_AUTH_TOKEN) : getVercelToken();
|
|
167
|
+
/**
|
|
168
|
+
* A handed login's token, which its board tokens are cached by: null for `vercel login`'s own. One
|
|
169
|
+
* that fails fails the request: `vercel login`'s, cached or in the `vercel` on PATH, can be someone
|
|
170
|
+
* else's.
|
|
171
|
+
*/
|
|
172
|
+
async function handedToken() {
|
|
173
|
+
if (!loginHanded()) return null;
|
|
174
|
+
try {
|
|
175
|
+
return await vercelLogin();
|
|
176
|
+
} catch {
|
|
177
|
+
throw new BoardError("not_logged_in", "You're not logged in to Vercel.", 0, { hint: "Run vercel login." });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** The login to call Vercel's API with: a handed one, or `vercel login`'s, or none it can read (the keychain's). */
|
|
181
|
+
const apiLogin = async (handed) => handed ?? await getVercelToken().catch(() => null);
|
|
149
182
|
/**
|
|
150
183
|
* A person's token: their Vercel CLI login mints a development token for the
|
|
151
184
|
* project the board's host is a deployment of, exchanged so its audience is the
|
|
152
185
|
* board's URL. Cached on disk for its 12-hour life.
|
|
153
186
|
*/
|
|
154
187
|
async function personToken(audience, discovery) {
|
|
155
|
-
const
|
|
188
|
+
const login = await handedToken();
|
|
189
|
+
const cached = await readCache(audience, login);
|
|
156
190
|
if (cached && cached.name === void 0) {
|
|
157
|
-
const
|
|
191
|
+
const authToken = await apiLogin(login);
|
|
192
|
+
const user = authToken ? await vercelUser(authToken) : null;
|
|
158
193
|
const filled = {
|
|
159
194
|
...cached,
|
|
160
195
|
name: user?.name ?? null,
|
|
161
196
|
avatar: user?.avatar ?? null
|
|
162
197
|
};
|
|
163
|
-
await writeCache(audience, filled).catch(() => {});
|
|
198
|
+
await writeCache(audience, login, filled).catch(() => {});
|
|
164
199
|
return filled;
|
|
165
200
|
}
|
|
166
201
|
if (cached) return cached;
|
|
167
202
|
if (!discovery.project_id) throw new BoardError("no_project", `The board at ${audience} doesn't know its Vercel project, so there's no Vercel login token to mint for it.`, 0, { hint: "Pass a token it accepts in BOARD_TOKEN." });
|
|
168
|
-
const authToken = await
|
|
203
|
+
const authToken = await apiLogin(login);
|
|
169
204
|
const project = await boardProject(audience, discovery, authToken);
|
|
170
205
|
let development;
|
|
171
206
|
try {
|
|
@@ -178,7 +213,7 @@ async function personToken(audience, discovery) {
|
|
|
178
213
|
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
179
214
|
development = (await response.json()).token;
|
|
180
215
|
} catch (error) {
|
|
181
|
-
if (error instanceof BoardError) throw error;
|
|
216
|
+
if (error instanceof BoardError || login) throw error;
|
|
182
217
|
development = await tokenFromVercelCli(project);
|
|
183
218
|
}
|
|
184
219
|
const token = await exchangeVercelOidcToken({
|
|
@@ -193,7 +228,7 @@ async function personToken(audience, discovery) {
|
|
|
193
228
|
name: user?.name ?? null,
|
|
194
229
|
avatar: user?.avatar ?? null
|
|
195
230
|
};
|
|
196
|
-
await writeCache(audience, fresh).catch(() => {});
|
|
231
|
+
await writeCache(audience, login, fresh).catch(() => {});
|
|
197
232
|
return fresh;
|
|
198
233
|
}
|
|
199
234
|
/**
|
|
@@ -546,23 +581,18 @@ const automationBypass = (bypasses) => Object.entries(bypasses ?? {}).find(([, v
|
|
|
546
581
|
*/
|
|
547
582
|
async function projectBypass(url, host, doFetch) {
|
|
548
583
|
const blocked = (why, fix) => new BoardError("protected", `${url} is behind Vercel Deployment Protection, and your Vercel login ${why}.`, 0, { hint: `${fix}, or set VERCEL_AUTOMATION_BYPASS_SECRET (${WHERE}).` });
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
return response.ok ? await response.json() : null;
|
|
562
|
-
};
|
|
563
|
-
} catch {
|
|
564
|
-
api = vercelCliApi;
|
|
565
|
-
}
|
|
584
|
+
const token = await apiLogin(await handedToken());
|
|
585
|
+
const api = token ? async (method, path) => {
|
|
586
|
+
const response = await doFetch(`https://api.vercel.com${path}`, {
|
|
587
|
+
method,
|
|
588
|
+
headers: {
|
|
589
|
+
authorization: `Bearer ${token}`,
|
|
590
|
+
...method === "PATCH" ? { "content-type": "application/json" } : {}
|
|
591
|
+
},
|
|
592
|
+
...method === "PATCH" ? { body: "{}" } : {}
|
|
593
|
+
});
|
|
594
|
+
return response.ok ? await response.json() : null;
|
|
595
|
+
} : vercelCliApi;
|
|
566
596
|
const project = await findProject(api, host);
|
|
567
597
|
if (!project) throw blocked("can't see its project", "Log in as someone on its project's team (vercel login), ask for access");
|
|
568
598
|
const scope = project.ownerId.startsWith("team_") ? `?teamId=${project.ownerId}` : "";
|
|
@@ -628,4 +658,4 @@ const vercelCliApi = async (method, path) => {
|
|
|
628
658
|
}
|
|
629
659
|
};
|
|
630
660
|
//#endregion
|
|
631
|
-
export {
|
|
661
|
+
export { localTokenFile as a, vercelLogin as c, handLogin as i, vercelProjectFile as l, connect as n, loginHanded as o, environmentProfile as r, projectDir as s, boardUrl as t };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as BoardError } from "./client-D0Px7KzF.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { t as board } from "./server-
|
|
2
|
+
import { a as localTokenFile } from "./node-Ybi9E6bO.mjs";
|
|
3
|
+
import { t as board } from "./server-BSXTDTOp.mjs";
|
|
4
4
|
import { t as oidcTokens } from "./tokens-BuCU-61R.mjs";
|
|
5
|
-
import { n as fsBlob, t as a2 } from "./a2-
|
|
5
|
+
import { n as fsBlob, t as a2 } from "./a2-C4A4vr0M.mjs";
|
|
6
6
|
import { t as localScheduler } from "./local-DhkrAqPP.mjs";
|
|
7
7
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
8
8
|
import { userInfo } from "node:os";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createBoard } from "./exports-
|
|
2
|
-
import { t as http } from "./backend-http-
|
|
1
|
+
import { t as createBoard } from "./exports-B-AeCbYw.mjs";
|
|
2
|
+
import { t as http } from "./backend-http-CQWTNzym.mjs";
|
|
3
3
|
//#region src/server/index.ts
|
|
4
4
|
/**
|
|
5
5
|
* The handle on a board. With a `backend`, the board lives there: `a2()` keeps it in this
|
|
@@ -9,7 +9,7 @@ import { t as http } from "./backend-http-CJd4Fu0U.mjs";
|
|
|
9
9
|
function board(options = {}) {
|
|
10
10
|
return createBoard(options, {
|
|
11
11
|
http: () => http(),
|
|
12
|
-
sync: () => import("./engine-
|
|
12
|
+
sync: () => import("./engine-Bfe6pcXb.mjs"),
|
|
13
13
|
serving: () => import("./serving-CvOWEym2.mjs")
|
|
14
14
|
});
|
|
15
15
|
}
|
|
@@ -28,4 +28,15 @@ const keyId = async (principal, derived) => `key:${principal}:${(await sha256(`b
|
|
|
28
28
|
*/
|
|
29
29
|
const isKeyIdOf = (id, principal) => id.startsWith(`key:${principal}:`) && /^[0-9a-f]{32}$/.test(id.slice(`key:${principal}:`.length));
|
|
30
30
|
//#endregion
|
|
31
|
-
|
|
31
|
+
//#region src/server/wait-until.ts
|
|
32
|
+
/**
|
|
33
|
+
* Keeps `task` running after the response, where the platform offers it: Vercel's request context, as A2
|
|
34
|
+
* reads it too. Elsewhere it runs on in the process.
|
|
35
|
+
*/
|
|
36
|
+
function waitUntil(task) {
|
|
37
|
+
try {
|
|
38
|
+
Reflect.get(globalThis, Symbol.for("@vercel/request-context"))?.get?.()?.waitUntil?.(task);
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { keyId as a, isKeyIdOf as i, DERIVED_ID as n, sha256 as o, derivedId as r, waitUntil as t };
|
|
@@ -2,7 +2,7 @@ import { n as DIRECT_UPLOAD_BYTES_MAX, t as ATTACHMENT_BYTES_MAX } from "./files
|
|
|
2
2
|
import { S as storable, a as AttachmentId, c as ChannelPath, d as LabelKey, g as MessageText, l as Emoji, m as MESSAGE_TEXT_MAX, n as ActorId, p as Labels, x as platformAvatar } from "./schemas-Be08nyfW.mjs";
|
|
3
3
|
import { a as Link, i as ExternalTarget, n as ExternalEvent, o as Origin, t as ExternalAuthor } from "./external-BV_cUncs.mjs";
|
|
4
4
|
import { r as badRequest, t as HttpError } from "./errors-CusN4x2l.mjs";
|
|
5
|
-
import { o as options } from "./commands-
|
|
5
|
+
import { o as options } from "./commands-nEG47RM_.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/api/routes.ts
|
|
8
8
|
/**
|
|
@@ -662,7 +662,7 @@ const routes = {
|
|
|
662
662
|
const SYNC = "@sync";
|
|
663
663
|
//#endregion
|
|
664
664
|
//#region src/version.ts
|
|
665
|
-
const VERSION = "0.
|
|
665
|
+
const VERSION = "0.26.1";
|
|
666
666
|
//#endregion
|
|
667
667
|
//#region src/server/routing.ts
|
|
668
668
|
/** board()'s own routes: the files it serves, and sync's, which run where board() does. */
|
|
@@ -865,7 +865,7 @@ const FILTER_FLAGS = {
|
|
|
865
865
|
by: options.by,
|
|
866
866
|
mentions: options.mentions,
|
|
867
867
|
label: options.label,
|
|
868
|
-
|
|
868
|
+
source: options.source,
|
|
869
869
|
text: options.text
|
|
870
870
|
};
|
|
871
871
|
/** `--mentions` alone means mentions of me; `--mentions @ada`, mentions of ada. */
|
|
@@ -961,7 +961,7 @@ function parseWatchTail(expression) {
|
|
|
961
961
|
...values.by ? { by: values.by.replace(/^@/, "") } : {},
|
|
962
962
|
...values.mentions ? { mentions: values.mentions.replace(/^@/, "") } : {},
|
|
963
963
|
...values.label?.length ? { label: values.label } : {},
|
|
964
|
-
...values.
|
|
964
|
+
...values.source ? { from: values.source } : {},
|
|
965
965
|
...values.text ? { text: values.text } : {}
|
|
966
966
|
});
|
|
967
967
|
if (!parsed.success) {
|
|
@@ -164,7 +164,7 @@ await team.reply('812', { text: 'fixed, see after', attachments: [shot] })
|
|
|
164
164
|
A retry shouldn't post twice. Give a post or reply an ID of your own:
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
-
vb post deploys "a1b2c3 is live" --
|
|
167
|
+
vb post deploys "a1b2c3 is live" --idempotency-key deploy-a1b2c3
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
The same ID with the same write returns the message it already posted. The same
|
|
@@ -75,14 +75,14 @@ read includes it.
|
|
|
75
75
|
| `--re <id>` | the thread rooted at `<id>`: the root and its replies |
|
|
76
76
|
| `--by <handle>` | messages by that actor: a handle without the `@`, or an exact actor ID |
|
|
77
77
|
| `--mentions` | messages that mention you; `--mentions @ada` for someone else |
|
|
78
|
-
| `--
|
|
78
|
+
| `--source <source>` | where it was written: a sync key (`slack`, `github`), or `board` |
|
|
79
79
|
| `--label <key>` | messages with that label; `key=value`, or `key=a\|b` for either |
|
|
80
80
|
| `--text <words>` | text containing the words, case-insensitive (a substring, not search) |
|
|
81
81
|
| `--after <id>` | messages after that message ID or cursor; for `read`, threads with activity after it |
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
vb read prs --label state=open # what's pending
|
|
85
|
-
vb read issues --label tag.bug --
|
|
85
|
+
vb read issues --label tag.bug --source github
|
|
86
86
|
vb read --mentions # everywhere I'm mentioned
|
|
87
87
|
vb read errors-www --by ada --text ENOTFOUND
|
|
88
88
|
```
|
|
@@ -21,7 +21,7 @@ const team = board({
|
|
|
21
21
|
// sync as in Sync
|
|
22
22
|
watch: {
|
|
23
23
|
triage: {
|
|
24
|
-
tail: 'firehose --
|
|
24
|
+
tail: 'firehose --source slack',
|
|
25
25
|
run: async ({ event, board }) => {
|
|
26
26
|
if (event.type === 'message.posted' && event.message?.re === null)
|
|
27
27
|
await board.reply(event.message.id, 'Thanks! Tracking it.') // synced to the Slack thread
|
|
@@ -37,13 +37,13 @@ export const { scheduler } = backend
|
|
|
37
37
|
would print, exactly as `vb tail --json` prints it. Try it in a terminal first:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
vb tail firehose --
|
|
40
|
+
vb tail firehose --source slack --json
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## The expression
|
|
44
44
|
|
|
45
45
|
A channel (or `prefix/` for a subtree) and the shared
|
|
46
|
-
[filters](./05-reading.md#filters): `--re`, `--by`, `--mentions`, `--
|
|
46
|
+
[filters](./05-reading.md#filters): `--re`, `--by`, `--mentions`, `--source`,
|
|
47
47
|
`--label`, `--text`, and `--all`.
|
|
48
48
|
|
|
49
49
|
- **Positions aren't filters.** A watcher starts at the head when it's deployed
|
|
@@ -209,8 +209,10 @@ export const GET = team.fetch, POST = team.fetch
|
|
|
209
209
|
from.
|
|
210
210
|
- **A match wakes it with nothing in the wake-up:** a `POST <url>/wake` with
|
|
211
211
|
no event. It then pulls what matched with its own credentials, runs it, and
|
|
212
|
-
acks it, until nothing's left, so a burst of matches wakes it once.
|
|
213
|
-
|
|
212
|
+
acks it, until nothing's left, so a burst of matches wakes it once. It
|
|
213
|
+
answers when that's done, or after 2 seconds while it keeps going, so
|
|
214
|
+
whoever woke it never waits on slow watchers. A forged wake-up costs it one
|
|
215
|
+
pull that finds nothing.
|
|
214
216
|
- **A wake-up that doesn't get there is tried again** a minute or so on. While
|
|
215
217
|
they keep failing, the wait doubles, up to an hour, and the other board logs
|
|
216
218
|
the first failure and the start of the back-off. It starts over when this
|
|
@@ -95,7 +95,7 @@ see [Sync functions](./02-functions.md).
|
|
|
95
95
|
|
|
96
96
|
**Entry keys** (`slack`, `github`, or `acme` for a second Slack workspace) are
|
|
97
97
|
names you choose; the adapter decides the platform. The key appears in external
|
|
98
|
-
IDs (`acme:C0999XYZ:1727.9`), in sync targets, in `--
|
|
98
|
+
IDs (`acme:C0999XYZ:1727.9`), in sync targets, in `--source` filters, and in the
|
|
99
99
|
webhook path (`/webhooks/acme`).
|
|
100
100
|
|
|
101
101
|
**Map keys** are the platform's names or IDs: `'#prs'` or `'C0123ABC'` for
|
|
@@ -303,7 +303,7 @@ written to the log. It needs `post` and `read` in the thread's channel.
|
|
|
303
303
|
vb sources # the board's sync entries and their platforms
|
|
304
304
|
vb inbox slack # raw platform events, newest first: did Slack send it?
|
|
305
305
|
vb inbox slack '#errors-www' --limit 5
|
|
306
|
-
vb tail --
|
|
306
|
+
vb tail --source slack --all # board events from Slack, delivery receipts included
|
|
307
307
|
```
|
|
308
308
|
|
|
309
309
|
`vb inbox` needs `sync` in every channel for the board's own inbox, and every
|
package/docs/03-guides/03-ci.md
CHANGED
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
- uses: actions/checkout@v4
|
|
25
25
|
# your deploy steps…
|
|
26
26
|
- if: failure()
|
|
27
|
-
run: npx -y @vercel/board post errors-www "deploy failed on $SHA" --label source=ci --
|
|
27
|
+
run: npx -y @vercel/board post errors-www "deploy failed on $SHA" --label source=ci --idempotency-key "deploy-$SHA"
|
|
28
28
|
env:
|
|
29
29
|
BOARD_URL: https://team.example/api/board
|
|
30
30
|
SHA: ${{ github.sha }}
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
whose audience is the board's URL, so it's good for that board and nothing
|
|
35
35
|
else. Without `id-token: write`, GitHub gives the job no token, so the CLI
|
|
36
36
|
falls through to a `vercel login` the runner doesn't have, and fails.
|
|
37
|
-
- **`--
|
|
37
|
+
- **`--idempotency-key` makes a retried step post once.** The same ID with the same write
|
|
38
38
|
returns the message already posted.
|
|
39
39
|
- **The post is an ordinary thread.** If `errors-www` mirrors a Slack channel,
|
|
40
40
|
it lands there, and a reply in Slack reaches the board.
|
|
@@ -18,10 +18,10 @@ messages.
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# in a terminal:
|
|
21
|
-
vb tail firehose --
|
|
21
|
+
vb tail firehose --source slack --json
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`tail` prints one line of JSON per event, live, until Ctrl-C. `--
|
|
24
|
+
`tail` prints one line of JSON per event, live, until Ctrl-C. `--source slack`
|
|
25
25
|
keeps messages that came from Slack. Each line carries the event and the
|
|
26
26
|
message it concerns, as it is after the event:
|
|
27
27
|
|
|
@@ -38,7 +38,7 @@ report is a `message.posted` whose message has `re: null`: a thread root.
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
# triage.sh: runs until you stop it
|
|
41
|
-
vb tail firehose --
|
|
41
|
+
vb tail firehose --source slack --json | while read -r event; do
|
|
42
42
|
[ "$(jq -r '.type' <<<"$event")" = "message.posted" ] || continue
|
|
43
43
|
[ "$(jq -r '.message.re' <<<"$event")" = "null" ] || continue
|
|
44
44
|
id=$(jq -r '.message.id' <<<"$event")
|
|
@@ -63,7 +63,7 @@ prints it, and exits, so each turn handles one event. Every event carries a
|
|
|
63
63
|
```bash
|
|
64
64
|
# triage-turn.sh: one event per run
|
|
65
65
|
last=$(cat .triage-cursor 2>/dev/null)
|
|
66
|
-
next=$(vb wait firehose --
|
|
66
|
+
next=$(vb wait firehose --source slack ${last:+--after "$last"} --json --timeout 1h) || exit 0
|
|
67
67
|
jq -r '.cursor' <<<"$next" > .triage-cursor
|
|
68
68
|
|
|
69
69
|
[ "$(jq -r '.type' <<<"$next")" = "message.posted" ] || exit 0
|
|
@@ -117,7 +117,7 @@ const team = board({
|
|
|
117
117
|
// sync: { slack: slack('slack/acme', channels({ '#firehose': 'firehose' })) },
|
|
118
118
|
watch: {
|
|
119
119
|
triage: {
|
|
120
|
-
tail: 'firehose --
|
|
120
|
+
tail: 'firehose --source slack',
|
|
121
121
|
run: async ({ event, board }) => {
|
|
122
122
|
const message = event.message
|
|
123
123
|
if (event.type !== 'message.posted' || !message || message.re !== null) return
|