eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
1 lines • 4.89 kB
JavaScript
import{existsSync}from"node:fs";import{dirname,join}from"node:path";import{spawn}from"node:child_process";import{mkdir,readFile,rm,stat,writeFile}from"node:fs/promises";import{pathToFileURL}from"node:url";const EVE_DEV_ENV_FLAG=`EVE_DEV`;function isEveDevEnvironment(){return process.env[EVE_DEV_ENV_FLAG]===`1`}function detectProjectPackageManager(r){let i=r;for(;;){if(existsSync(join(i,`pnpm-lock.yaml`))||existsSync(join(i,`pnpm-workspace.yaml`)))return`pnpm`;if(existsSync(join(i,`yarn.lock`)))return`yarn`;if(existsSync(join(i,`bun.lock`))||existsSync(join(i,`bun.lockb`)))return`bun`;if(existsSync(join(i,`package-lock.json`)))return`npm`;let r=dirname(i);if(r===i)return`npm`;i=r}}const INSTALL_ARGUMENTS={bun:[`add`,`--dev`],npm:[`install`,`--save-dev`],pnpm:[`add`,`-D`],yarn:[`add`,`-D`]},OPTIONAL_PACKAGE_LOCK_TIMEOUT_MS=600*1e3,pendingOptionalPackageInstalls=new Map;async function installPackageIntoProject(e){let t=detectProjectPackageManager(e.appRoot),n=[...INSTALL_ARGUMENTS[t],e.packageName];console.info(`[eve:dev] installing optional dependency "${e.packageName}" via \`${t} ${n.join(` `)}\`...`);let i=spawn(t,n,{cwd:e.appRoot,shell:shouldSpawnPackageManagerWithShell(),stdio:[`ignore`,`pipe`,`pipe`]}),a=[];i.stdout?.on(`data`,e=>a.push(e)),i.stderr?.on(`data`,e=>a.push(e));let o=await new Promise((e,t)=>{i.on(`error`,t),i.on(`close`,t=>e(t??1))});if(o!==0){let n=Buffer.concat(a).toString(`utf8`).trim();throw Error(`Failed to install "${e.packageName}" with ${t} (exit ${o}).`+(n.length>0?`\n${n.slice(-2e3)}`:``))}console.info(`[eve:dev] installed "${e.packageName}".`)}async function loadOptionalEnginePackage(e){try{return await e.importModule()}catch(t){if(!e.autoInstall||!isEveDevEnvironment())throw Error(e.missingMessage,{cause:t});let n=e.importInstalledModule??(async()=>await importInstalledEnginePackage({appRoot:e.appRoot,packageName:e.packageName}));try{await withOptionalPackageInstallLock(e,async()=>{try{await n();return}catch{}await installPackageIntoProject({appRoot:e.appRoot,packageName:e.packageName})})}catch(t){throw Error(`${e.missingMessage} Automatic installation failed: ${toMessage(t)}`,{cause:t})}return await n()}}async function importInstalledEnginePackage(e){let t=join(e.appRoot,`node_modules`,...e.packageName.split(`/`)),r=join(t,`package.json`),i=resolvePackageEntryPoint(JSON.parse(await readFile(r,`utf8`)));if(i.startsWith(`/`))throw Error(`Invalid absolute entrypoint for optional package "${e.packageName}".`);return await import(pathToFileURL(join(t,i)).href)}function resolvePackageEntryPoint(e){let t=readDotExport(e.exports);return t===void 0?typeof e.module==`string`&&e.module.length>0?e.module:typeof e.main==`string`&&e.main.length>0?e.main:`index.js`:t}function readDotExport(e){if(typeof e==`string`&&e.length>0)return e;if(typeof e!=`object`||!e)return;let t=`.`in e?e[`.`]:e;if(typeof t==`string`&&t.length>0)return t;if(typeof t!=`object`||!t)return;let n=t;if(typeof n.import==`string`&&n.import.length>0)return n.import;if(typeof n.default==`string`&&n.default.length>0)return n.default}async function withOptionalPackageInstallLock(e,t){let n=`${e.appRoot}:${e.packageName}`,r=pendingOptionalPackageInstalls.get(n);if(r!==void 0){await r;return}let i=withOptionalPackageInstallFileLock(e,t).finally(()=>{pendingOptionalPackageInstalls.delete(n)});pendingOptionalPackageInstalls.set(n,i),await i}async function withOptionalPackageInstallFileLock(e,t){let r=join(e.appRoot,`.eve`,`optional-package-locks`,`${sanitizeLockName(e.packageName)}.lock`);await acquireLock(r);try{await t()}finally{await rm(r,{force:!0,recursive:!0}).catch(()=>{})}}async function acquireLock(e){let r=Date.now();for(;;){await mkdir(dirname(e),{recursive:!0});try{await mkdir(e),await writeFile(join(e,`owner.json`),`${JSON.stringify({createdAt:new Date().toISOString(),pid:process.pid})}\n`);return}catch(t){if(!isFileExistsError(t))throw t;await waitForExistingLock(e,r)}}}async function waitForExistingLock(e,t){let n=await stat(e).catch(e=>{if(isNotFoundError(e))return null;throw e});if(n!==null){if(Date.now()-n.mtimeMs>18e5){await rm(e,{force:!0,recursive:!0}).catch(()=>{});return}if(Date.now()-t>OPTIONAL_PACKAGE_LOCK_TIMEOUT_MS)throw Error(`Timed out waiting for optional package install lock "${e}" after ${OPTIONAL_PACKAGE_LOCK_TIMEOUT_MS}ms.`);await new Promise(e=>setTimeout(e,250))}}function sanitizeLockName(e){return e.replaceAll(/[^a-zA-Z0-9._-]+/g,`-`)}function shouldSpawnPackageManagerWithShell(e=process.platform){return e===`win32`}function isFileExistsError(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`EEXIST`}function isNotFoundError(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}function toMessage(e){return e instanceof Error?e.message:String(e)}export{EVE_DEV_ENV_FLAG,detectProjectPackageManager,installPackageIntoProject,isEveDevEnvironment,loadOptionalEnginePackage};