eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
3 lines • 7.26 kB
JavaScript
import{readProjectLink}from"#setup/project-resolution.js";import{captureVercel,runVercel,runVercelCaptureStdout}from"#setup/primitives/run-vercel.js";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";const CREATED_CONNECTOR=/\bConnector created:\s*(scl_[A-Za-z0-9_-]+)\b/u;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function parseJson(e){try{return JSON.parse(e)}catch{return}}function connectorCreationFailure(e,t){let n=t?.split(`
`).map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith(`Vercel CLI `)).at(-1);return n===void 0?`Could not create the ${e} connector.`:`Could not create the ${e} connector. Vercel returned: ${n}`}function parseConnectorRef(e){if(!isRecord(e)||typeof e.uid!=`string`||typeof e.id!=`string`)return;let t={uid:e.uid,id:e.id};return typeof e.name==`string`&&(t.name=e.name),t}function parseCreatedConnector(e,t=`user`){let n=parseJson(e),r=parseConnectorRef(n);if(!isRecord(n)||r===void 0)return;let i=n.supportedSubjectTypes;return Array.isArray(i)&&i.includes(t)?r:void 0}function parseConnectorList(e,t){let n=[];for(let r of e){if(!isRecord(r)||typeof r.service==`string`&&r.service!==t)continue;let e=parseConnectorRef(r);e!==void 0&&n.push(e)}return n}function parseConnectorListPage(e,t){if(!isRecord(e)||!Array.isArray(e.connectors))return;let n=typeof e.cursor==`string`?e.cursor:void 0;return n===void 0?{connectors:parseConnectorList(e.connectors,t)}:{connectors:parseConnectorList(e.connectors,t),cursor:n}}async function listConnectors(e,t,n){let r=[],i=new Set,a;do{let o=[`connect`,`list`,`-F`,`json`,`--all-projects`,`--service`,e.service,`--scope`,t.orgId];a!==void 0&&o.push(`--next`,a);let s=await captureVercel(o,{cwd:e.projectRoot,onOutput:n,signal:e.signal});if(!s.ok)throw Error(s.failure.message);let c=parseConnectorListPage(parseJson(s.stdout),e.service);if(c===void 0)throw Error(`Vercel returned an invalid connector list for ${e.service}.`);r.push(...c.connectors);let l=c.cursor;if(l!==void 0&&i.has(l))throw Error(`The connector list repeated cursor ${l}.`);l!==void 0&&i.add(l),a=l}while(a!==void 0);return r}async function supportsPrincipalType(e,t,n,r){let i=`/v1/connect/connectors/${encodeURIComponent(n.id)}`,a=await captureVercel([`api`,i,`--scope`,t.orgId,`--raw`],{cwd:e.projectRoot,onOutput:r,signal:e.signal});if(!a.ok)throw Error(`Could not verify connector ${n.uid}.`);let o=parseJson(a.stdout);if(!isRecord(o)||o.id!==n.id||o.uid!==n.uid||typeof o.service==`string`&&o.service!==e.service)throw Error(`Vercel returned invalid details for connector ${n.uid}.`);let s=o.supportedSubjectTypes;return Array.isArray(s)&&s.includes(e.principalType??`user`)}function connectorNames(e){let t=new Set;for(let n of e){n.name!==void 0&&t.add(n.name.toLowerCase());let e=n.uid.slice(n.uid.lastIndexOf(`/`)+1).trim();e.length>0&&t.add(e.toLowerCase())}return t}function connectorMatchesCanonicalName(e,t){let n=t.toLowerCase();return e.name?.toLowerCase()===n||e.uid.slice(e.uid.lastIndexOf(`/`)+1).trim().toLowerCase()===n}function nextConnectorName(e,t){if(!t.has(e.toLowerCase()))return e;let n=2;for(;t.has(`${e}-${n}`.toLowerCase());)n+=1;return`${e}-${n}`}async function cleanupCreatedConnectionConnector(e){let t=e.orgId??(await readProjectLink(e.projectRoot))?.orgId,n=[`connect`,`remove`,e.connectorId,`--disconnect-all`,`--yes`];if(t!==void 0&&n.push(`--scope`,t),!await runVercel(n,{cwd:e.projectRoot,onOutput:createPromptCommandOutput(e.log)}))throw Error(`Could not remove connector ${e.connectorId}; run \`vercel connect remove ${e.connectorId} --disconnect-all --yes\`.`)}async function attach(e,t,n,r){return runVercel([`connect`,`attach`,n,`--yes`,`--scope`,t.orgId],{cwd:e.projectRoot,onOutput:r,signal:e.signal})}async function resolveFallbackConnector(e,t,n,r,i){let a=e.principalType??`user`,o=i;for(;;){if(await e.prompter.select({message:`Which connector should ${e.slug} use?`,hintLayout:`inline`,notices:[{tone:`warning`,text:o}],options:[{value:`find`,label:`Find a new one`,hint:`Browse existing connectors`},{value:`create`,label:`Create a new one`,hint:`Register another connector`}]})===`find`){let i=[];for(let a of r)await supportsPrincipalType(e,t,a,n)&&i.push(a);if(i.length===0){o=`No existing ${e.service} connectors support ${a} credentials.`;continue}let s=new Map(i.map(e=>[e.uid,e])),c=await e.prompter.select({message:`Select a connector for ${e.slug}`,hintLayout:`inline`,search:!0,placeholder:`type to search connectors`,options:i.map(e=>({value:e.uid,label:e.uid,hint:e.name??e.id}))}),l=s.get(c);if(l===void 0)throw Error(`Connector ${c} is no longer available.`);return{kind:`existing`,connector:l}}let i=connectorNames(r),s=(await e.prompter.text({message:`New connector name`,defaultValue:nextConnectorName(e.slug,i),validate:e=>{let t=e.trim().toLowerCase();return t.length===0?`A name is required.`:i.has(t)?`A connector with this name already exists.`:void 0}})).trim(),c=[],createOutput=e=>{c.push(e.text),n(e)},l=await withPhase(e.log,`Waiting for you to complete setup in the browser…`,()=>runVercelCaptureStdout([`connect`,`create`,e.creationType??e.service,`--name`,s,...e.connectionMethod===void 0?[]:[`--connection-method`,e.connectionMethod],`-F`,`json`,`--scope`,t.orgId],{cwd:e.projectRoot,onOutput:createOutput,signal:e.signal}),{kind:`external-action`,emphasis:`browser`}),u=parseConnectorRef(parseJson(l.stdout))?.id??CREATED_CONNECTOR.exec(c.join(`
`))?.[1],d=l.ok?parseCreatedConnector(l.stdout,a):void 0;if(d!==void 0)return{kind:`created`,connector:d};let f=l.ok?`The ${e.service} connector does not support ${a} credentials.`:connectorCreationFailure(e.service,l.stderr);if(u!==void 0){try{await cleanupCreatedConnectionConnector({log:e.log,projectRoot:e.projectRoot,connectorId:u,orgId:t.orgId})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`${f} ${t}`)}e.signal?.throwIfAborted()}throw Error(f)}}async function setupConnectionConnector(e){let t=e.principalType??`user`,n=createPromptCommandOutput(e.log),r=e.project,i=await listConnectors(e,r,n),a=i.find(t=>connectorMatchesCanonicalName(t,e.canonicalConnectorName)),o=`Could not find a connector named ${e.canonicalConnectorName}.`;if(a!==void 0){if(await supportsPrincipalType(e,r,a,n)){if(await attach(e,r,a.uid,n))return e.log.success(`Attached ${a.uid} connector`),{kind:`existing`,connectorUid:a.uid};e.signal?.throwIfAborted(),o=`Could not attach ${a.uid}.`}else o=`${a.uid} does not support ${t} credentials.`}let s=await resolveFallbackConnector(e,r,n,i,o);if(!await attach(e,r,s.connector.uid,n)){if(s.kind===`created`){let t=`Could not attach ${s.connector.uid} to the linked project.`;try{await cleanupCreatedConnectionConnector({log:e.log,projectRoot:e.projectRoot,connectorId:s.connector.id,orgId:r.orgId})}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`${t} ${n}`)}throw e.signal?.throwIfAborted(),Error(t)}throw Error(`Could not attach ${s.connector.uid} to the linked project.`)}return e.log.success(`Attached ${s.connector.uid} connector`),s.kind===`created`?{kind:`created`,connectorUid:s.connector.uid,connectorId:s.connector.id}:{kind:`existing`,connectorUid:s.connector.uid}}export{cleanupCreatedConnectionConnector,parseCreatedConnector,setupConnectionConnector};