@agimon-ai/foundation-process-registry 0.17.4 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ProcessRegistryService.cjs +1 -1
- package/dist/ProcessRegistryService.cjs.map +1 -1
- package/dist/ProcessRegistryService.mjs +1 -1
- package/dist/ProcessRegistryService.mjs.map +1 -1
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -1
- package/dist/index.d.mts +38 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:crypto`),l=require(`node:fs`);l=s(l,1);let u=require(`node:fs/promises`);u=s(u,1);let d=require(`node:path`);d=s(d,1);let f=require(`node:os`);f=s(f,1);let p=require(`@agimon-ai/foundation-port-registry`),m=require(`zod`);const h=d.default.join(f.default.homedir(),`.process-registry`,`processes.json`),g=`${h}.lock`,_=`PROCESS_REGISTRY_TAG`,v=e=>d.default.resolve(e),y=e=>`${e}.${(0,c.randomBytes)(6).toString(`hex`)}.tmp`;function b(e){let t=S(e),n=S(process.env[_]),r=[...t??[],...n??[]];if(r.length!==0)return Array.from(new Set(r))}function x(e,t){if(!e)return;let n=d.default.resolve(e);return d.default.extname(n)===`.json`?d.default.join(d.default.dirname(n),t):d.default.join(n,t)}function S(e){if(e===void 0)return;let t=(Array.isArray(e)?e:e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)).map(e=>e.trim()).filter(e=>e.length>0);return t.length>0?t:void 0}const C=m.z.enum([`service`,`tool`]),w=m.z.record(m.z.string(),m.z.unknown()),T=m.z.object({repositoryPath:m.z.string().trim().min(1,`repositoryPath is required`),serviceName:m.z.string().trim().min(1,`serviceName is required`),serviceType:C,environment:m.z.string().trim().min(1).optional(),pid:m.z.number().int().min(1),port:m.z.number().int().min(1).max(65535).optional(),host:m.z.string().trim().min(1).optional(),command:m.z.string().trim().min(1).optional(),args:m.z.array(m.z.string()).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),metadata:w.optional(),createdAt:m.z.string().trim().min(1),updatedAt:m.z.string().trim().min(1)}),E=m.z.object({version:m.z.literal(1),updatedAt:m.z.string().trim().min(1),entries:m.z.array(T)}),D=m.z.object({repositoryPath:m.z.string().trim().min(1),serviceName:m.z.string().trim().min(1),serviceType:C.default(`service`),environment:m.z.string().trim().min(1).optional(),pid:m.z.number().int().min(1),port:m.z.number().int().min(1).max(65535).optional(),host:m.z.string().trim().min(1).optional(),command:m.z.string().trim().min(1).optional(),args:m.z.array(m.z.string()).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),metadata:w.optional(),force:m.z.boolean().optional()}),O=m.z.object({repositoryPath:m.z.string().trim().min(1),serviceName:m.z.string().trim().min(1),serviceType:C.optional(),pid:m.z.number().int().min(1).optional(),environment:m.z.string().trim().min(1).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),force:m.z.boolean().optional(),kill:m.z.boolean().optional().default(!0),releasePort:m.z.boolean().optional().default(!0)}),k=m.z.object({repositoryPath:m.z.string().trim().min(1).optional(),serviceType:C.optional(),serviceName:m.z.string().trim().min(1).optional(),environment:m.z.string().trim().min(1).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional()}),A=m.z.object({success:m.z.boolean(),pid:m.z.number().int().min(1).optional(),record:T.optional(),error:m.z.string().optional()});var j=class extends Error{code;constructor(e,t,n){super(e,n),this.name=`ProcessRegistryError`,this.code=t}},M=class e{registryPath;lockPath;activeLockToken;lockCleanupHandlers=new Map;constructor(t=h,n,r=new p.PortRegistryService(process.env.PORT_REGISTRY_PATH)){this.portRegistry=r,this.registryPath=e.resolveRegistryPath(t),this.lockPath=n??`${this.registryPath}.lock`}static resolveRegistryPath(e){if(!e)return h;let t=d.default.isAbsolute(e)?e:d.default.join(process.cwd(),e);return d.default.extname(t)===`.json`?t:d.default.join(t,`processes.json`)}async registerProcess(e){let t=D.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=v(t.repositoryPath),r=t.serviceType??`service`,i=t.environment??process.env.NODE_ENV??`development`,a=b(t.tags),o=await this.pruneState(e),s=this.findEntry(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i});if(s){if(s.pid===t.pid)return s.updatedAt=new Date().toISOString(),s.port=t.port??s.port,s.host=t.host??s.host,s.command=t.command??s.command,s.args=t.args??s.args,s.tags=a??s.tags,s.metadata=t.metadata??s.metadata,await this.saveState(o),this.createSuccessResponse(t.pid,s);let e=this.isProcessRunning(s.pid);if(e&&!(t.force??!0))return this.createFailureResponse(`Process already registered for ${t.serviceName} in requested scope`);e&&await this.terminateProcess(s.pid),await this.releaseAssociatedPort(s),this.removeMatchingEntries(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i})}let c=new Date().toISOString(),l={repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i,pid:t.pid,port:t.port,host:t.host,command:t.command,args:t.args,tags:a,metadata:t.metadata,createdAt:c,updatedAt:c};return o.entries.push(l),await this.saveState(o),this.createSuccessResponse(t.pid,l)})}async releaseProcess(e){let t=O.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=await this.pruneMissingRepositories(e),r=v(t.repositoryPath),i=t.serviceType??`service`,a=t.environment??process.env.NODE_ENV??`development`,o=N(t.tags),s=n.entries.filter(e=>!(e.repositoryPath!==r||e.serviceName!==t.serviceName||e.serviceType!==i||t.environment&&e.environment!==a||typeof t.pid==`number`&&e.pid!==t.pid||o&&o.length>0&&!this.matchesTags(e.tags,o)));if(s.length===0)return this.createFailureResponse(`No matching process entry for ${t.serviceName}`);let c=[],l=new Set;for(let e of s){let n=this.entryKey(e);try{(t.kill??!0)&&this.isProcessRunning(e.pid)&&await this.terminateProcess(e.pid),(t.releasePort??!0)&&await this.releaseAssociatedPort(e),l.add(n)}catch(t){c.push(`${e.serviceName} (pid ${e.pid}): ${t instanceof Error?t.message:String(t)}`)}}return l.size>0&&(n.entries=n.entries.filter(e=>!l.has(this.entryKey(e))),await this.saveState(n)),c.length>0?this.createFailureResponse(c.join(`; `)):this.createSuccessResponse()})}async listProcesses(e={}){let t=k.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=await this.pruneState(e),r=N(t.tags);return[...n.entries].filter(e=>!(t.repositoryPath&&e.repositoryPath!==v(t.repositoryPath)||t.serviceType&&e.serviceType!==t.serviceType||t.serviceName&&e.serviceName!==t.serviceName||t.environment&&e.environment!==t.environment||r&&r.length>0&&!this.matchesTags(e.tags,r)))})}async withLock(e){let t=`${process.pid}-${(0,c.randomBytes)(6).toString(`hex`)}`;await this.acquireLock(t);try{return await e()}finally{await this.releaseLock(t)}}async loadState(){await u.default.mkdir(d.default.dirname(this.registryPath),{recursive:!0});try{let e=await u.default.readFile(this.registryPath,`utf-8`),t=JSON.parse(e);return E.parse(t)}catch(e){let t=e;if(t.code===`ENOENT`)return{version:1,updatedAt:new Date().toISOString(),entries:[]};if(t instanceof SyntaxError){let e=`${this.registryPath}.corrupt.${Date.now()}`;await u.default.rename(this.registryPath,e).catch(()=>void 0)}throw new j(`Failed to read registry file: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_READ_FAILED`,{cause:e})}}async saveState(e){await u.default.mkdir(d.default.dirname(this.registryPath),{recursive:!0});let t={...e,updatedAt:new Date().toISOString(),entries:[...e.entries]},n=y(this.registryPath);await u.default.writeFile(n,JSON.stringify(t,null,2),`utf-8`),await u.default.rename(n,this.registryPath)}async pruneState(e){let t=await this.pruneMissingRepositories(e);return this.pruneDeadProcesses(t)}async pruneMissingRepositories(e){let t=(await Promise.all(e.entries.map(async e=>({entry:e,exists:await this.pathExists(e.repositoryPath)})))).filter(e=>e.exists).map(e=>e.entry);return t.length!==e.entries.length&&(e.entries=t,await this.saveState(e)),e}async pruneDeadProcesses(e){let t=[],n=!1;for(let r of e.entries){if(this.isProcessRunning(r.pid)){t.push(r);continue}n=!0,await this.releaseAssociatedPort(r)}return n&&(e.entries=t,await this.saveState(e)),e}async acquireLock(e){await u.default.mkdir(d.default.dirname(this.lockPath),{recursive:!0});for(let t=0;t<80;t+=1)try{let t={pid:process.pid,token:e,createdAt:new Date().toISOString()};await u.default.writeFile(this.lockPath,JSON.stringify(t),{flag:`wx`}),this.registerLockCleanup(e);return}catch(e){if(e.code!==`EEXIST`)throw new j(`Failed to acquire registry lock: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_LOCK_FAILED`,{cause:e});if(await this.isStaleLock()){await u.default.unlink(this.lockPath).catch(()=>void 0),--t;continue}await this.delay(75)}throw new j(`Unable to acquire registry lock (timeout)`,`REGISTRY_LOCK_FAILED`)}async releaseLock(e){try{let t=await u.default.readFile(this.lockPath,`utf-8`);JSON.parse(t).token===e&&await u.default.unlink(this.lockPath)}catch{}finally{this.unregisterLockCleanup(e)}}async isStaleLock(){try{let e=await u.default.readFile(this.lockPath,`utf-8`),t=JSON.parse(e);if(t.pid&&this.isProcessRunning(t.pid)){let e=Date.now()-new Date(t.createdAt).getTime();return!(Number.isFinite(e)&&e<5e3)}return!0}catch{return!0}}isProcessRunning(e){try{return process.kill(e,0),!0}catch{return!1}}registerLockCleanup(e){this.unregisterLockCleanup(this.activeLockToken),this.activeLockToken=e;let t=()=>{this.releaseLockSync(e)};for(let e of[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`])process.once(e,t),this.lockCleanupHandlers.set(e,t)}unregisterLockCleanup(e){if(!(!e||this.activeLockToken!==e)){for(let[e,t]of this.lockCleanupHandlers)process.off(e,t);this.lockCleanupHandlers.clear(),this.activeLockToken=void 0}}releaseLockSync(e){try{let t=l.default.readFileSync(this.lockPath,`utf-8`);JSON.parse(t).token===e&&l.default.unlinkSync(this.lockPath)}catch{}}async terminateProcess(e){try{process.kill(e,0)}catch{return}try{process.kill(e,`SIGTERM`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGTERM to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(500),this.isProcessRunning(e)){try{process.kill(e,`SIGKILL`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGKILL to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(250),this.isProcessRunning(e))throw Error(`Process ${e} did not exit after SIGKILL`)}}async releaseAssociatedPort(e){if(!(!this.portRegistry||!e.port))try{let t=await this.portRegistry.releasePort({repositoryPath:e.repositoryPath,serviceName:e.serviceName,serviceType:e.serviceType,environment:e.environment,pid:e.pid,force:!0});if(!t.success&&t.error&&!t.error.includes(`No matching registry entry`))throw Error(t.error)}catch{}}entryKey(e){return[e.repositoryPath,e.serviceName,e.serviceType,e.environment??``,String(e.pid)].join(`|`)}findEntry(e,t){return e.entries.find(e=>e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(t.environment?e.environment===t.environment:!0))}removeMatchingEntries(e,t){e.entries=e.entries.filter(e=>!(e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(!t.environment||e.environment===t.environment)))}createSuccessResponse(e,t){return A.parse({success:!0,...typeof e==`number`?{pid:e}:{},...t?{record:t}:{}})}createFailureResponse(e){return A.parse({success:!1,error:e})}matchesTags(e,t){return t.length===0?!0:!e||e.length===0?!1:t.some(t=>e.includes(t))}async pathExists(e){try{return await u.default.access(e),!0}catch{return!1}}delay(e){return new Promise(t=>setTimeout(t,e))}};function N(e){if(e!==void 0)return Array.from(new Set(e.map(e=>e.trim()).filter(e=>e.length>0)))}Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return 1}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return 5e3}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return 75}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return 80}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return v}});
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:crypto`),l=require(`node:fs`);l=s(l,1);let u=require(`node:fs/promises`);u=s(u,1);let d=require(`node:path`);d=s(d,1);let f=require(`node:os`);f=s(f,1);let p=require(`@agimon-ai/foundation-port-registry`),m=require(`zod`);const h=d.default.join(f.default.homedir(),`.process-registry`,`processes.json`),g=`${h}.lock`,_=`PROCESS_REGISTRY_TAG`,v=e=>d.default.resolve(e),y=e=>`${e}.${(0,c.randomBytes)(6).toString(`hex`)}.tmp`;function b(e){let t=S(e),n=S(process.env[_]),r=[...t??[],...n??[]];if(r.length!==0)return Array.from(new Set(r))}function x(e,t){if(!e)return;let n=d.default.resolve(e);return d.default.extname(n)===`.json`?d.default.join(d.default.dirname(n),t):d.default.join(n,t)}function S(e){if(e===void 0)return;let t=(Array.isArray(e)?e:e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)).map(e=>e.trim()).filter(e=>e.length>0);return t.length>0?t:void 0}const C=m.z.enum([`service`,`tool`]),w=m.z.record(m.z.string(),m.z.unknown()),T=m.z.object({repositoryPath:m.z.string().trim().min(1,`repositoryPath is required`),serviceName:m.z.string().trim().min(1,`serviceName is required`),serviceType:C,environment:m.z.string().trim().min(1).optional(),pid:m.z.number().int().min(1),port:m.z.number().int().min(1).max(65535).optional(),host:m.z.string().trim().min(1).optional(),command:m.z.string().trim().min(1).optional(),args:m.z.array(m.z.string()).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),metadata:w.optional(),createdAt:m.z.string().trim().min(1),updatedAt:m.z.string().trim().min(1)}),E=m.z.object({version:m.z.literal(1),updatedAt:m.z.string().trim().min(1),entries:m.z.array(T)}),D=m.z.object({repositoryPath:m.z.string().trim().min(1),serviceName:m.z.string().trim().min(1),serviceType:C.default(`service`),environment:m.z.string().trim().min(1).optional(),pid:m.z.number().int().min(1),port:m.z.number().int().min(1).max(65535).optional(),host:m.z.string().trim().min(1).optional(),command:m.z.string().trim().min(1).optional(),args:m.z.array(m.z.string()).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),metadata:w.optional(),force:m.z.boolean().optional()}),O=m.z.object({repositoryPath:m.z.string().trim().min(1),serviceName:m.z.string().trim().min(1),serviceType:C.optional(),pid:m.z.number().int().min(1).optional(),environment:m.z.string().trim().min(1).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional(),force:m.z.boolean().optional(),kill:m.z.boolean().optional().default(!0),releasePort:m.z.boolean().optional().default(!0)}),k=m.z.object({repositoryPath:m.z.string().trim().min(1).optional(),serviceType:C.optional(),serviceName:m.z.string().trim().min(1).optional(),environment:m.z.string().trim().min(1).optional(),tags:m.z.array(m.z.string().trim().min(1)).optional()}),A=k.extend({pid:m.z.number().int().min(1).optional(),force:m.z.boolean().optional(),kill:m.z.boolean().optional().default(!0),releasePort:m.z.boolean().optional().default(!0)}),j=m.z.object({success:m.z.boolean(),pid:m.z.number().int().min(1).optional(),record:T.optional(),error:m.z.string().optional()}),M=m.z.object({success:m.z.boolean(),scanned:m.z.number().int().min(0),removed:m.z.number().int().min(0),error:m.z.string().optional()}),N=m.z.object({success:m.z.boolean(),released:m.z.number().int().min(0),error:m.z.string().optional()});var P=class extends Error{code;constructor(e,t,n){super(e,n),this.name=`ProcessRegistryError`,this.code=t}},F=class e{registryPath;lockPath;activeLockToken;lockCleanupHandlers=new Map;constructor(t=h,n,r=new p.PortRegistryService(process.env.PORT_REGISTRY_PATH)){this.portRegistry=r,this.registryPath=e.resolveRegistryPath(t),this.lockPath=n??`${this.registryPath}.lock`}static resolveRegistryPath(e){if(!e)return h;let t=d.default.isAbsolute(e)?e:d.default.join(process.cwd(),e);return d.default.extname(t)===`.json`?t:d.default.join(t,`processes.json`)}async registerProcess(e){let t=D.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=v(t.repositoryPath),r=t.serviceType??`service`,i=t.environment??process.env.NODE_ENV??`development`,a=b(t.tags),o=await this.pruneState(e),s=this.findEntry(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i});if(s){if(s.pid===t.pid)return s.updatedAt=new Date().toISOString(),s.port=t.port??s.port,s.host=t.host??s.host,s.command=t.command??s.command,s.args=t.args??s.args,s.tags=a??s.tags,s.metadata=t.metadata??s.metadata,await this.saveState(o),this.createSuccessResponse(t.pid,s);let e=this.isProcessRunning(s.pid);if(e&&!(t.force??!0))return this.createFailureResponse(`Process already registered for ${t.serviceName} in requested scope`);e&&await this.terminateProcess(s.pid),await this.releaseAssociatedPort(s),this.removeMatchingEntries(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i})}let c=new Date().toISOString(),l={repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i,pid:t.pid,port:t.port,host:t.host,command:t.command,args:t.args,tags:a,metadata:t.metadata,createdAt:c,updatedAt:c};return o.entries.push(l),await this.saveState(o),this.createSuccessResponse(t.pid,l)})}async releaseProcess(e){let t=O.parse(e),n=await this.releaseProcesses(t);return n.success?this.createSuccessResponse():this.createFailureResponse(n.error??`No matching process entry for ${t.serviceName}`)}async releaseProcesses(e){let t=A.parse(e),n=this.normalizeReleaseFilters(t),r=await this.collectMatchingEntries(n);if(r.length===0)return N.parse({success:!1,released:0,error:`No matching process entries`});let i=[],a=new Set;for(let e of r)try{t.kill&&this.isProcessRunning(e.pid)&&await this.terminateProcess(e.pid),t.releasePort&&await this.releaseAssociatedPort(e),a.add(this.entryKey(e))}catch(t){i.push(`${e.serviceName} (pid ${e.pid}): ${t instanceof Error?t.message:String(t)}`)}let o=await this.removeEntriesByKey(a);return i.length>0?N.parse({success:!1,released:o,error:i.join(`; `)}):N.parse({success:!0,released:o})}async listProcesses(e={}){let t=k.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=this.normalizeListFilters(t);return[...e.entries].filter(e=>this.matchesListFilters(e,n))})}async cleanupStaleProcesses(e={}){let t=k.parse(e),n=this.normalizeListFilters(t),{staleEntries:r,scanned:i}=await this.collectStaleEntries(n);for(let e of r)await this.releaseAssociatedPort(e);let a=await this.removeEntriesByKey(new Set(r.map(e=>this.entryKey(e))));return M.parse({success:!0,scanned:i,removed:a})}async withLock(e){let t=`${process.pid}-${(0,c.randomBytes)(6).toString(`hex`)}`;await this.acquireLock(t);try{return await e()}finally{await this.releaseLock(t)}}async loadState(){await u.default.mkdir(d.default.dirname(this.registryPath),{recursive:!0});try{let e=await u.default.readFile(this.registryPath,`utf-8`),t=JSON.parse(e);return E.parse(t)}catch(e){let t=e;if(t.code===`ENOENT`)return{version:1,updatedAt:new Date().toISOString(),entries:[]};if(t instanceof SyntaxError){let e=`${this.registryPath}.corrupt.${Date.now()}`;await u.default.rename(this.registryPath,e).catch(()=>void 0)}throw new P(`Failed to read registry file: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_READ_FAILED`,{cause:e})}}async saveState(e){await u.default.mkdir(d.default.dirname(this.registryPath),{recursive:!0});let t={...e,updatedAt:new Date().toISOString(),entries:[...e.entries]},n=y(this.registryPath);await u.default.writeFile(n,JSON.stringify(t,null,2),`utf-8`),await u.default.rename(n,this.registryPath)}async pruneState(e){let t=await this.pruneMissingRepositories(e);return this.pruneDeadProcesses(t)}async pruneMissingRepositories(e){let t=(await Promise.all(e.entries.map(async e=>({entry:e,exists:await this.pathExists(e.repositoryPath)})))).filter(e=>e.exists).map(e=>e.entry);return t.length!==e.entries.length&&(e.entries=t,await this.saveState(e)),e}async pruneDeadProcesses(e){let t=[],n=!1;for(let r of e.entries){if(this.isProcessRunning(r.pid)){t.push(r);continue}n=!0,await this.releaseAssociatedPort(r)}return n&&(e.entries=t,await this.saveState(e)),e}async collectMatchingEntries(e){return this.withLock(async()=>[...(await this.loadState()).entries].filter(t=>this.matchesReleaseFilters(t,e)))}async collectStaleEntries(e){return this.withLock(async()=>{let t=await this.loadState(),n=await Promise.all(t.entries.map(async t=>{if(!this.matchesListFilters(t,e))return{entry:t,stale:!1,scanned:!1};let n=await this.pathExists(t.repositoryPath),r=!this.isProcessRunning(t.pid);return{entry:t,stale:!n||r,scanned:!0}}));return{staleEntries:n.filter(e=>e.stale).map(e=>e.entry),scanned:n.filter(e=>e.scanned).length}})}async removeEntriesByKey(e){return e.size===0?0:this.withLock(async()=>{let t=await this.loadState(),n=t.entries.length;t.entries=t.entries.filter(t=>!e.has(this.entryKey(t)));let r=n-t.entries.length;return r>0&&await this.saveState(t),r})}async acquireLock(e){await u.default.mkdir(d.default.dirname(this.lockPath),{recursive:!0});for(let t=0;t<80;t+=1){let n;try{let t={pid:process.pid,token:e,createdAt:new Date().toISOString()};n=`${this.lockPath}.${process.pid}.${(0,c.randomBytes)(4).toString(`hex`)}.acquire`,await u.default.writeFile(n,JSON.stringify(t),`utf-8`),await u.default.link(n,this.lockPath),this.registerLockCleanup(e);return}catch(e){if(e.code!==`EEXIST`)throw new P(`Failed to acquire registry lock: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_LOCK_FAILED`,{cause:e});if(await this.isStaleLock()){await u.default.unlink(this.lockPath).catch(()=>void 0),--t;continue}await this.delay(75)}finally{n&&await u.default.unlink(n).catch(()=>void 0)}}throw new P(`Unable to acquire registry lock (timeout)`,`REGISTRY_LOCK_FAILED`)}async releaseLock(e){try{let t=await u.default.readFile(this.lockPath,`utf-8`);JSON.parse(t).token===e&&await u.default.unlink(this.lockPath)}catch{}finally{this.unregisterLockCleanup(e)}}async isStaleLock(){try{let e=await u.default.readFile(this.lockPath,`utf-8`),t=JSON.parse(e);if(t.pid&&this.isProcessRunning(t.pid)){let e=Date.now()-new Date(t.createdAt).getTime();return!(Number.isFinite(e)&&e<5e3)}return!0}catch{return!0}}isProcessRunning(e){try{return process.kill(e,0),!0}catch{return!1}}registerLockCleanup(e){this.unregisterLockCleanup(this.activeLockToken),this.activeLockToken=e;let t=()=>{this.releaseLockSync(e)};for(let e of[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`])process.once(e,t),this.lockCleanupHandlers.set(e,t)}unregisterLockCleanup(e){if(!(!e||this.activeLockToken!==e)){for(let[e,t]of this.lockCleanupHandlers)process.off(e,t);this.lockCleanupHandlers.clear(),this.activeLockToken=void 0}}releaseLockSync(e){try{let t=l.default.readFileSync(this.lockPath,`utf-8`);JSON.parse(t).token===e&&l.default.unlinkSync(this.lockPath)}catch{}}async terminateProcess(e){try{process.kill(e,0)}catch{return}try{process.kill(e,`SIGTERM`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGTERM to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(500),this.isProcessRunning(e)){try{process.kill(e,`SIGKILL`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGKILL to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(250),this.isProcessRunning(e))throw Error(`Process ${e} did not exit after SIGKILL`)}}async releaseAssociatedPort(e){if(!(!this.portRegistry||!e.port))try{let t=await this.portRegistry.releasePort({repositoryPath:e.repositoryPath,serviceName:e.serviceName,serviceType:e.serviceType,environment:e.environment,pid:e.pid,force:!0});if(!t.success&&t.error&&!t.error.includes(`No matching registry entry`))throw Error(t.error)}catch{}}entryKey(e){return[e.repositoryPath,e.serviceName,e.serviceType,e.environment??``,String(e.pid)].join(`|`)}normalizeListFilters(e){return{...e.repositoryPath?{repositoryPath:v(e.repositoryPath)}:{},...e.serviceName?{serviceName:e.serviceName}:{},...e.serviceType?{serviceType:e.serviceType}:{},...e.environment?{environment:e.environment}:{},...e.tags?{tags:I(e.tags)}:{}}}normalizeReleaseFilters(e){return{...this.normalizeListFilters(e),...typeof e.pid==`number`?{pid:e.pid}:{},...typeof e.force==`boolean`?{force:e.force}:{}}}matchesListFilters(e,t){return!(t.repositoryPath&&e.repositoryPath!==t.repositoryPath||t.serviceType&&e.serviceType!==t.serviceType||t.serviceName&&e.serviceName!==t.serviceName||t.environment&&e.environment!==t.environment||t.tags&&t.tags.length>0&&!this.matchesTags(e.tags,t.tags))}matchesReleaseFilters(e,t){return this.matchesListFilters(e,t)?typeof t.pid!=`number`||t.force||e.pid===t.pid:!1}findEntry(e,t){return e.entries.find(e=>e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(t.environment?e.environment===t.environment:!0))}removeMatchingEntries(e,t){e.entries=e.entries.filter(e=>!(e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(!t.environment||e.environment===t.environment)))}createSuccessResponse(e,t){return j.parse({success:!0,...typeof e==`number`?{pid:e}:{},...t?{record:t}:{}})}createFailureResponse(e){return j.parse({success:!1,error:e})}matchesTags(e,t){return t.length===0?!0:!e||e.length===0?!1:t.some(t=>e.includes(t))}async pathExists(e){try{return await u.default.access(e),!0}catch{return!1}}delay(e){return new Promise(t=>setTimeout(t,e))}};function I(e){if(e!==void 0)return Array.from(new Set(e.map(e=>e.trim()).filter(e=>e.length>0)))}Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return v}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return 80}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return E}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return k}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return 1}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return 75}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return 5e3}});
|
|
2
2
|
//# sourceMappingURL=ProcessRegistryService.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProcessRegistryService.cjs","names":["path","os","normalizeTags","z","PortRegistryService","path","fs","fsSync","sysError"],"sources":["../src/utils/index.ts","../src/types/index.ts","../src/services/ProcessRegistryService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.process-registry', 'processes.json');\nexport const DEFAULT_REGISTRY_LOCK_PATH = `${DEFAULT_REGISTRY_PATH}.lock`;\nexport const LOCK_RETRY_DELAY_MS = 75;\nexport const LOCK_MAX_RETRIES = 80;\nexport const LOCK_STALE_AFTER_MS = 5_000;\nexport const PROCESS_REGISTRY_TAG_ENV_VAR = 'PROCESS_REGISTRY_TAG';\n\nexport const normalizeRepositoryPath = (value: string): string => path.resolve(value);\n\nexport const makeTempPath = (filePath: string): string => {\n const random = randomBytes(6).toString('hex');\n return `${filePath}.${random}.tmp`;\n};\n\nexport function resolveProcessTags(tags?: string[]): string[] | undefined {\n const resolved = normalizeTags(tags);\n const envTags = normalizeTags(process.env[PROCESS_REGISTRY_TAG_ENV_VAR]);\n\n const combined = [...(resolved ?? []), ...(envTags ?? [])];\n if (combined.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(combined));\n}\n\nexport function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined {\n if (!registryPath) {\n return undefined;\n }\n\n const resolved = path.resolve(registryPath);\n if (path.extname(resolved) === '.json') {\n return path.join(path.dirname(resolved), fileName);\n }\n\n return path.join(resolved, fileName);\n}\n\nfunction normalizeTags(tags: string[] | string | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n const values = Array.isArray(tags)\n ? tags\n : tags\n .split(',')\n .map((tag) => tag.trim())\n .filter((tag) => tag.length > 0);\n\n const normalized = values.map((tag) => tag.trim()).filter((tag) => tag.length > 0);\n return normalized.length > 0 ? normalized : undefined;\n}\n","import { z } from 'zod';\n\nexport const REGISTRY_VERSION = 1 as const;\n\nexport const ServiceCategorySchema = z.enum(['service', 'tool']);\n\nexport const ProcessMetadataSchema = z.record(z.string(), z.unknown());\n\nexport const ProcessRegistryRecordSchema = z.object({\n repositoryPath: z.string().trim().min(1, 'repositoryPath is required'),\n serviceName: z.string().trim().min(1, 'serviceName is required'),\n serviceType: ServiceCategorySchema,\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n createdAt: z.string().trim().min(1),\n updatedAt: z.string().trim().min(1),\n});\n\nexport const ProcessRegistryStateSchema = z.object({\n version: z.literal(REGISTRY_VERSION),\n updatedAt: z.string().trim().min(1),\n entries: z.array(ProcessRegistryRecordSchema),\n});\n\nexport const RegisterProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.default('service'),\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n force: z.boolean().optional(),\n});\n\nexport const ReleaseProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.optional(),\n pid: z.number().int().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ListProcessFiltersSchema = z.object({\n repositoryPath: z.string().trim().min(1).optional(),\n serviceType: ServiceCategorySchema.optional(),\n serviceName: z.string().trim().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n});\n\nexport const ProcessRegistryResponseSchema = z.object({\n success: z.boolean(),\n pid: z.number().int().min(1).optional(),\n record: ProcessRegistryRecordSchema.optional(),\n error: z.string().optional(),\n});\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;\nexport type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;\nexport type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;\nexport type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;\nexport type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;\nexport type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;\nexport type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;\n\nexport type ProcessRegistryErrorCode =\n | 'INVALID_REQUEST'\n | 'REGISTRY_READ_FAILED'\n | 'REGISTRY_WRITE_FAILED'\n | 'REGISTRY_LOCK_FAILED'\n | 'NO_PROCESS_FOUND';\n\nexport class ProcessRegistryError extends Error {\n readonly code: ProcessRegistryErrorCode;\n\n constructor(message: string, code: ProcessRegistryErrorCode, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProcessRegistryError';\n this.code = code;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n type ListProcessFilters,\n ListProcessFiltersSchema,\n ProcessRegistryError,\n type ProcessRegistryRecord,\n type ProcessRegistryResponse,\n ProcessRegistryResponseSchema,\n type ProcessRegistryState,\n ProcessRegistryStateSchema,\n REGISTRY_VERSION,\n type RegisterProcessRequest,\n RegisterProcessRequestSchema,\n type ReleaseProcessRequest,\n ReleaseProcessRequestSchema,\n} from '../types';\nimport {\n DEFAULT_REGISTRY_PATH,\n LOCK_MAX_RETRIES,\n LOCK_RETRY_DELAY_MS,\n LOCK_STALE_AFTER_MS,\n resolveProcessTags,\n makeTempPath,\n normalizeRepositoryPath,\n} from '../utils';\n\ninterface LockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\ninterface NormalizedFilters {\n repositoryPath: string;\n serviceName: string;\n serviceType: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ntype LockCleanupEvent = 'exit' | 'SIGINT' | 'SIGTERM' | 'uncaughtException' | 'unhandledRejection';\n\ntype PortRegistryCleanup = Pick<PortRegistryService, 'releasePort'>;\n\nexport class ProcessRegistryService {\n private readonly registryPath: string;\n private readonly lockPath: string;\n private activeLockToken?: string;\n private readonly lockCleanupHandlers = new Map<LockCleanupEvent, (...args: unknown[]) => void>();\n\n constructor(\n registryPath: string = DEFAULT_REGISTRY_PATH,\n lockPath?: string,\n private readonly portRegistry: PortRegistryCleanup = new PortRegistryService(process.env.PORT_REGISTRY_PATH),\n ) {\n this.registryPath = ProcessRegistryService.resolveRegistryPath(registryPath);\n this.lockPath = lockPath ?? `${this.registryPath}.lock`;\n }\n\n static resolveRegistryPath(inputPath?: string): string {\n if (!inputPath) {\n return DEFAULT_REGISTRY_PATH;\n }\n\n const resolvedPath = path.isAbsolute(inputPath) ? inputPath : path.join(process.cwd(), inputPath);\n if (path.extname(resolvedPath) === '.json') {\n return resolvedPath;\n }\n\n return path.join(resolvedPath, 'processes.json');\n }\n\n async registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse> {\n const request = RegisterProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const tags = resolveProcessTags(request.tags);\n const registry = await this.pruneState(state);\n const existing = this.findEntry(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n\n if (existing) {\n if (existing.pid === request.pid) {\n existing.updatedAt = new Date().toISOString();\n existing.port = request.port ?? existing.port;\n existing.host = request.host ?? existing.host;\n existing.command = request.command ?? existing.command;\n existing.args = request.args ?? existing.args;\n existing.tags = tags ?? existing.tags;\n existing.metadata = request.metadata ?? existing.metadata;\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, existing);\n }\n\n const isAlive = this.isProcessRunning(existing.pid);\n if (isAlive && !(request.force ?? true)) {\n return this.createFailureResponse(`Process already registered for ${request.serviceName} in requested scope`);\n }\n\n if (isAlive) {\n await this.terminateProcess(existing.pid);\n }\n\n await this.releaseAssociatedPort(existing);\n this.removeMatchingEntries(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n }\n\n const now = new Date().toISOString();\n const record: ProcessRegistryRecord = {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n pid: request.pid,\n port: request.port,\n host: request.host,\n command: request.command,\n args: request.args,\n tags,\n metadata: request.metadata,\n createdAt: now,\n updatedAt: now,\n };\n\n registry.entries.push(record);\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, record);\n });\n }\n\n async releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse> {\n const request = ReleaseProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const registry = await this.pruneMissingRepositories(state);\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const requestedTags = normalizeTags(request.tags);\n const matches = registry.entries.filter((entry) => {\n if (entry.repositoryPath !== normalizedRepo) return false;\n if (entry.serviceName !== request.serviceName) return false;\n if (entry.serviceType !== serviceType) return false;\n if (request.environment && entry.environment !== environment) return false;\n if (typeof request.pid === 'number' && entry.pid !== request.pid) return false;\n if (requestedTags && requestedTags.length > 0 && !this.matchesTags(entry.tags, requestedTags)) return false;\n return true;\n });\n\n if (matches.length === 0) {\n return this.createFailureResponse(`No matching process entry for ${request.serviceName}`);\n }\n\n const errors: string[] = [];\n const removable = new Set<string>();\n\n for (const entry of matches) {\n const entryKey = this.entryKey(entry);\n\n try {\n if (request.kill ?? true) {\n if (this.isProcessRunning(entry.pid)) {\n await this.terminateProcess(entry.pid);\n }\n }\n\n if (request.releasePort ?? true) {\n await this.releaseAssociatedPort(entry);\n }\n\n removable.add(entryKey);\n } catch (error) {\n errors.push(\n `${entry.serviceName} (pid ${entry.pid}): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (removable.size > 0) {\n registry.entries = registry.entries.filter((entry) => !removable.has(this.entryKey(entry)));\n await this.saveState(registry);\n }\n\n if (errors.length > 0) {\n return this.createFailureResponse(errors.join('; '));\n }\n\n return this.createSuccessResponse();\n });\n }\n\n async listProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryRecord[]> {\n const request = ListProcessFiltersSchema.parse(filters);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const registry = await this.pruneState(state);\n const requestedTags = normalizeTags(request.tags);\n\n return [...registry.entries].filter((entry) => {\n if (request.repositoryPath && entry.repositoryPath !== normalizeRepositoryPath(request.repositoryPath)) {\n return false;\n }\n if (request.serviceType && entry.serviceType !== request.serviceType) return false;\n if (request.serviceName && entry.serviceName !== request.serviceName) return false;\n if (request.environment && entry.environment !== request.environment) return false;\n if (requestedTags && requestedTags.length > 0 && !this.matchesTags(entry.tags, requestedTags)) return false;\n return true;\n });\n });\n }\n\n private async withLock<T>(callback: () => Promise<T>): Promise<T> {\n const lockToken = `${process.pid}-${randomBytes(6).toString('hex')}`;\n await this.acquireLock(lockToken);\n\n try {\n return await callback();\n } finally {\n await this.releaseLock(lockToken);\n }\n }\n\n private async loadState(): Promise<ProcessRegistryState> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n try {\n const content = await fs.readFile(this.registryPath, 'utf-8');\n const parsed = JSON.parse(content);\n return ProcessRegistryStateSchema.parse(parsed);\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ENOENT') {\n return {\n version: REGISTRY_VERSION,\n updatedAt: new Date().toISOString(),\n entries: [],\n };\n }\n\n if (sysError instanceof SyntaxError) {\n const backupPath = `${this.registryPath}.corrupt.${Date.now()}`;\n await fs.rename(this.registryPath, backupPath).catch(() => undefined);\n }\n\n throw new ProcessRegistryError(\n `Failed to read registry file: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_READ_FAILED',\n { cause: error },\n );\n }\n }\n\n private async saveState(state: ProcessRegistryState): Promise<void> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n const payload: ProcessRegistryState = {\n ...state,\n updatedAt: new Date().toISOString(),\n entries: [...state.entries],\n };\n\n const tempPath = makeTempPath(this.registryPath);\n await fs.writeFile(tempPath, JSON.stringify(payload, null, 2), 'utf-8');\n await fs.rename(tempPath, this.registryPath);\n }\n\n private async pruneState(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const repositoryPruned = await this.pruneMissingRepositories(state);\n return this.pruneDeadProcesses(repositoryPruned);\n }\n\n private async pruneMissingRepositories(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const entries = await Promise.all(\n state.entries.map(async (entry) => ({\n entry,\n exists: await this.pathExists(entry.repositoryPath),\n })),\n );\n\n const pruned = entries.filter((value) => value.exists).map((value) => value.entry);\n if (pruned.length !== state.entries.length) {\n state.entries = pruned;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async pruneDeadProcesses(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const aliveEntries: ProcessRegistryRecord[] = [];\n let changed = false;\n\n for (const entry of state.entries) {\n if (this.isProcessRunning(entry.pid)) {\n aliveEntries.push(entry);\n continue;\n }\n\n changed = true;\n await this.releaseAssociatedPort(entry);\n }\n\n if (changed) {\n state.entries = aliveEntries;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async acquireLock(token: string): Promise<void> {\n await fs.mkdir(path.dirname(this.lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt += 1) {\n try {\n const lockState: LockState = {\n pid: process.pid,\n token,\n createdAt: new Date().toISOString(),\n };\n await fs.writeFile(this.lockPath, JSON.stringify(lockState), { flag: 'wx' });\n this.registerLockCleanup(token);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ProcessRegistryError(\n `Failed to acquire registry lock: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_LOCK_FAILED',\n { cause: error },\n );\n }\n\n const stale = await this.isStaleLock();\n if (stale) {\n await fs.unlink(this.lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await this.delay(LOCK_RETRY_DELAY_MS);\n }\n }\n\n throw new ProcessRegistryError('Unable to acquire registry lock (timeout)', 'REGISTRY_LOCK_FAILED');\n }\n\n private async releaseLock(token: string): Promise<void> {\n try {\n const existing = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token: string };\n if (parsed.token === token) {\n await fs.unlink(this.lockPath);\n }\n } catch {\n // ignore\n } finally {\n this.unregisterLockCleanup(token);\n }\n }\n\n private async isStaleLock(): Promise<boolean> {\n try {\n const content = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(content) as LockState;\n\n if (parsed.pid) {\n const pidAlive = this.isProcessRunning(parsed.pid);\n if (pidAlive) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < LOCK_STALE_AFTER_MS);\n }\n }\n\n return true;\n } catch {\n return true;\n }\n }\n\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private registerLockCleanup(token: string): void {\n this.unregisterLockCleanup(this.activeLockToken);\n this.activeLockToken = token;\n\n const cleanup = (): void => {\n this.releaseLockSync(token);\n };\n\n for (const event of [\n 'exit',\n 'SIGINT',\n 'SIGTERM',\n 'uncaughtException',\n 'unhandledRejection',\n ] satisfies LockCleanupEvent[]) {\n process.once(event, cleanup);\n this.lockCleanupHandlers.set(event, cleanup);\n }\n }\n\n private unregisterLockCleanup(token: string | undefined): void {\n if (!token || this.activeLockToken !== token) {\n return;\n }\n\n for (const [event, handler] of this.lockCleanupHandlers) {\n process.off(event, handler);\n }\n\n this.lockCleanupHandlers.clear();\n this.activeLockToken = undefined;\n }\n\n private releaseLockSync(token: string): void {\n try {\n const existing = fsSync.readFileSync(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(this.lockPath);\n }\n } catch {\n // ignore best-effort cleanup\n }\n }\n\n private async terminateProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n } catch {\n return;\n }\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGTERM to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(500);\n\n if (!this.isProcessRunning(pid)) {\n return;\n }\n\n try {\n process.kill(pid, 'SIGKILL');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGKILL to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(250);\n\n if (this.isProcessRunning(pid)) {\n throw new Error(`Process ${pid} did not exit after SIGKILL`);\n }\n }\n\n private async releaseAssociatedPort(entry: ProcessRegistryRecord): Promise<void> {\n if (!this.portRegistry || !entry.port) {\n return;\n }\n\n try {\n const result = await this.portRegistry.releasePort({\n repositoryPath: entry.repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n pid: entry.pid,\n force: true,\n });\n\n if (!result.success && result.error && !result.error.includes('No matching registry entry')) {\n throw new Error(result.error);\n }\n } catch {\n // Best-effort cleanup: port release failures should not block process cleanup.\n }\n }\n\n private entryKey(entry: ProcessRegistryRecord): string {\n return [\n entry.repositoryPath,\n entry.serviceName,\n entry.serviceType,\n entry.environment ?? '',\n String(entry.pid),\n ].join('|');\n }\n\n private findEntry(state: ProcessRegistryState, filters: NormalizedFilters): ProcessRegistryRecord | undefined {\n return state.entries.find(\n (entry) =>\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (filters.environment ? entry.environment === filters.environment : true),\n );\n }\n\n private removeMatchingEntries(state: ProcessRegistryState, filters: NormalizedFilters): void {\n state.entries = state.entries.filter(\n (entry) =>\n !(\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (!filters.environment || entry.environment === filters.environment)\n ),\n );\n }\n\n private createSuccessResponse(pid?: number, record?: ProcessRegistryRecord): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: true,\n ...(typeof pid === 'number' ? { pid } : {}),\n ...(record ? { record } : {}),\n });\n }\n\n private createFailureResponse(error: string): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: false,\n error,\n });\n }\n\n private matchesTags(entryTags: string[] | undefined, requestedTags: string[]): boolean {\n if (requestedTags.length === 0) {\n return true;\n }\n\n if (!entryTags || entryTags.length === 0) {\n return false;\n }\n\n return requestedTags.some((tag) => entryTags.includes(tag));\n }\n\n private async pathExists(candidate: string): Promise<boolean> {\n try {\n await fs.access(candidate);\n return true;\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nfunction normalizeTags(tags: string[] | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n return Array.from(new Set(tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProcessRegistryService.cjs","names":["path","os","normalizeTags","z","PortRegistryService","path","fs","fsSync","sysError"],"sources":["../src/utils/index.ts","../src/types/index.ts","../src/services/ProcessRegistryService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.process-registry', 'processes.json');\nexport const DEFAULT_REGISTRY_LOCK_PATH = `${DEFAULT_REGISTRY_PATH}.lock`;\nexport const LOCK_RETRY_DELAY_MS = 75;\nexport const LOCK_MAX_RETRIES = 80;\nexport const LOCK_STALE_AFTER_MS = 5_000;\nexport const PROCESS_REGISTRY_TAG_ENV_VAR = 'PROCESS_REGISTRY_TAG';\n\nexport const normalizeRepositoryPath = (value: string): string => path.resolve(value);\n\nexport const makeTempPath = (filePath: string): string => {\n const random = randomBytes(6).toString('hex');\n return `${filePath}.${random}.tmp`;\n};\n\nexport function resolveProcessTags(tags?: string[]): string[] | undefined {\n const resolved = normalizeTags(tags);\n const envTags = normalizeTags(process.env[PROCESS_REGISTRY_TAG_ENV_VAR]);\n\n const combined = [...(resolved ?? []), ...(envTags ?? [])];\n if (combined.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(combined));\n}\n\nexport function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined {\n if (!registryPath) {\n return undefined;\n }\n\n const resolved = path.resolve(registryPath);\n if (path.extname(resolved) === '.json') {\n return path.join(path.dirname(resolved), fileName);\n }\n\n return path.join(resolved, fileName);\n}\n\nfunction normalizeTags(tags: string[] | string | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n const values = Array.isArray(tags)\n ? tags\n : tags\n .split(',')\n .map((tag) => tag.trim())\n .filter((tag) => tag.length > 0);\n\n const normalized = values.map((tag) => tag.trim()).filter((tag) => tag.length > 0);\n return normalized.length > 0 ? normalized : undefined;\n}\n","import { z } from 'zod';\n\nexport const REGISTRY_VERSION = 1 as const;\n\nexport const ServiceCategorySchema = z.enum(['service', 'tool']);\n\nexport const ProcessMetadataSchema = z.record(z.string(), z.unknown());\n\nexport const ProcessRegistryRecordSchema = z.object({\n repositoryPath: z.string().trim().min(1, 'repositoryPath is required'),\n serviceName: z.string().trim().min(1, 'serviceName is required'),\n serviceType: ServiceCategorySchema,\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n createdAt: z.string().trim().min(1),\n updatedAt: z.string().trim().min(1),\n});\n\nexport const ProcessRegistryStateSchema = z.object({\n version: z.literal(REGISTRY_VERSION),\n updatedAt: z.string().trim().min(1),\n entries: z.array(ProcessRegistryRecordSchema),\n});\n\nexport const RegisterProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.default('service'),\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n force: z.boolean().optional(),\n});\n\nexport const ReleaseProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.optional(),\n pid: z.number().int().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ListProcessFiltersSchema = z.object({\n repositoryPath: z.string().trim().min(1).optional(),\n serviceType: ServiceCategorySchema.optional(),\n serviceName: z.string().trim().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n});\n\nexport const ReleaseProcessesRequestSchema = ListProcessFiltersSchema.extend({\n pid: z.number().int().min(1).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ProcessRegistryResponseSchema = z.object({\n success: z.boolean(),\n pid: z.number().int().min(1).optional(),\n record: ProcessRegistryRecordSchema.optional(),\n error: z.string().optional(),\n});\n\nexport const ProcessRegistryCleanupResponseSchema = z.object({\n success: z.boolean(),\n scanned: z.number().int().min(0),\n removed: z.number().int().min(0),\n error: z.string().optional(),\n});\n\nexport const ProcessRegistryBulkReleaseResponseSchema = z.object({\n success: z.boolean(),\n released: z.number().int().min(0),\n error: z.string().optional(),\n});\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;\nexport type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;\nexport type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;\nexport type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;\nexport type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;\nexport type ReleaseProcessesRequest = z.infer<typeof ReleaseProcessesRequestSchema>;\nexport type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;\nexport type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;\nexport type ProcessRegistryCleanupResponse = z.infer<typeof ProcessRegistryCleanupResponseSchema>;\nexport type ProcessRegistryBulkReleaseResponse = z.infer<typeof ProcessRegistryBulkReleaseResponseSchema>;\n\nexport type ProcessRegistryErrorCode =\n | 'INVALID_REQUEST'\n | 'REGISTRY_READ_FAILED'\n | 'REGISTRY_WRITE_FAILED'\n | 'REGISTRY_LOCK_FAILED'\n | 'NO_PROCESS_FOUND';\n\nexport class ProcessRegistryError extends Error {\n readonly code: ProcessRegistryErrorCode;\n\n constructor(message: string, code: ProcessRegistryErrorCode, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProcessRegistryError';\n this.code = code;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n type ListProcessFilters,\n ListProcessFiltersSchema,\n type ProcessRegistryBulkReleaseResponse,\n ProcessRegistryBulkReleaseResponseSchema,\n type ProcessRegistryCleanupResponse,\n ProcessRegistryCleanupResponseSchema,\n ProcessRegistryError,\n type ProcessRegistryRecord,\n type ProcessRegistryResponse,\n ProcessRegistryResponseSchema,\n type ProcessRegistryState,\n ProcessRegistryStateSchema,\n REGISTRY_VERSION,\n type RegisterProcessRequest,\n RegisterProcessRequestSchema,\n type ReleaseProcessesRequest,\n ReleaseProcessesRequestSchema,\n type ReleaseProcessRequest,\n ReleaseProcessRequestSchema,\n} from '../types';\nimport {\n DEFAULT_REGISTRY_PATH,\n LOCK_MAX_RETRIES,\n LOCK_RETRY_DELAY_MS,\n LOCK_STALE_AFTER_MS,\n makeTempPath,\n normalizeRepositoryPath,\n resolveProcessTags,\n} from '../utils';\n\ninterface LockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\ninterface NormalizedFilters {\n repositoryPath: string;\n serviceName: string;\n serviceType: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ninterface NormalizedListFilters {\n repositoryPath?: string;\n serviceName?: string;\n serviceType?: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ninterface NormalizedReleaseFilters extends NormalizedListFilters {\n pid?: number;\n force?: boolean;\n}\n\ntype LockCleanupEvent = 'exit' | 'SIGINT' | 'SIGTERM' | 'uncaughtException' | 'unhandledRejection';\n\ntype PortRegistryCleanup = Pick<PortRegistryService, 'releasePort'>;\n\nexport class ProcessRegistryService {\n private readonly registryPath: string;\n private readonly lockPath: string;\n private activeLockToken?: string;\n private readonly lockCleanupHandlers = new Map<LockCleanupEvent, (...args: unknown[]) => void>();\n\n constructor(\n registryPath: string = DEFAULT_REGISTRY_PATH,\n lockPath?: string,\n private readonly portRegistry: PortRegistryCleanup = new PortRegistryService(process.env.PORT_REGISTRY_PATH),\n ) {\n this.registryPath = ProcessRegistryService.resolveRegistryPath(registryPath);\n this.lockPath = lockPath ?? `${this.registryPath}.lock`;\n }\n\n static resolveRegistryPath(inputPath?: string): string {\n if (!inputPath) {\n return DEFAULT_REGISTRY_PATH;\n }\n\n const resolvedPath = path.isAbsolute(inputPath) ? inputPath : path.join(process.cwd(), inputPath);\n if (path.extname(resolvedPath) === '.json') {\n return resolvedPath;\n }\n\n return path.join(resolvedPath, 'processes.json');\n }\n\n async registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse> {\n const request = RegisterProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const tags = resolveProcessTags(request.tags);\n const registry = await this.pruneState(state);\n const existing = this.findEntry(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n\n if (existing) {\n if (existing.pid === request.pid) {\n existing.updatedAt = new Date().toISOString();\n existing.port = request.port ?? existing.port;\n existing.host = request.host ?? existing.host;\n existing.command = request.command ?? existing.command;\n existing.args = request.args ?? existing.args;\n existing.tags = tags ?? existing.tags;\n existing.metadata = request.metadata ?? existing.metadata;\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, existing);\n }\n\n const isAlive = this.isProcessRunning(existing.pid);\n if (isAlive && !(request.force ?? true)) {\n return this.createFailureResponse(`Process already registered for ${request.serviceName} in requested scope`);\n }\n\n if (isAlive) {\n await this.terminateProcess(existing.pid);\n }\n\n await this.releaseAssociatedPort(existing);\n this.removeMatchingEntries(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n }\n\n const now = new Date().toISOString();\n const record: ProcessRegistryRecord = {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n pid: request.pid,\n port: request.port,\n host: request.host,\n command: request.command,\n args: request.args,\n tags,\n metadata: request.metadata,\n createdAt: now,\n updatedAt: now,\n };\n\n registry.entries.push(record);\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, record);\n });\n }\n\n async releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse> {\n const request = ReleaseProcessRequestSchema.parse(rawRequest);\n const result = await this.releaseProcesses(request);\n\n if (!result.success) {\n return this.createFailureResponse(result.error ?? `No matching process entry for ${request.serviceName}`);\n }\n\n return this.createSuccessResponse();\n }\n\n async releaseProcesses(rawRequest: ReleaseProcessesRequest): Promise<ProcessRegistryBulkReleaseResponse> {\n const request = ReleaseProcessesRequestSchema.parse(rawRequest);\n const filters = this.normalizeReleaseFilters(request);\n const matches = await this.collectMatchingEntries(filters);\n\n if (matches.length === 0) {\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: false,\n released: 0,\n error: 'No matching process entries',\n });\n }\n\n const errors: string[] = [];\n const removable = new Set<string>();\n\n for (const entry of matches) {\n try {\n if (request.kill && this.isProcessRunning(entry.pid)) {\n await this.terminateProcess(entry.pid);\n }\n\n if (request.releasePort) {\n await this.releaseAssociatedPort(entry);\n }\n\n removable.add(this.entryKey(entry));\n } catch (error) {\n errors.push(\n `${entry.serviceName} (pid ${entry.pid}): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const released = await this.removeEntriesByKey(removable);\n\n if (errors.length > 0) {\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: false,\n released,\n error: errors.join('; '),\n });\n }\n\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: true,\n released,\n });\n }\n\n async listProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryRecord[]> {\n const request = ListProcessFiltersSchema.parse(filters);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedFilters = this.normalizeListFilters(request);\n\n return [...state.entries].filter((entry) => this.matchesListFilters(entry, normalizedFilters));\n });\n }\n\n async cleanupStaleProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryCleanupResponse> {\n const request = ListProcessFiltersSchema.parse(filters);\n const normalizedFilters = this.normalizeListFilters(request);\n const { staleEntries, scanned } = await this.collectStaleEntries(normalizedFilters);\n\n for (const entry of staleEntries) {\n await this.releaseAssociatedPort(entry);\n }\n\n const removed = await this.removeEntriesByKey(new Set(staleEntries.map((entry) => this.entryKey(entry))));\n\n return ProcessRegistryCleanupResponseSchema.parse({\n success: true,\n scanned,\n removed,\n });\n }\n\n private async withLock<T>(callback: () => Promise<T>): Promise<T> {\n const lockToken = `${process.pid}-${randomBytes(6).toString('hex')}`;\n await this.acquireLock(lockToken);\n\n try {\n return await callback();\n } finally {\n await this.releaseLock(lockToken);\n }\n }\n\n private async loadState(): Promise<ProcessRegistryState> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n try {\n const content = await fs.readFile(this.registryPath, 'utf-8');\n const parsed = JSON.parse(content);\n return ProcessRegistryStateSchema.parse(parsed);\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ENOENT') {\n return {\n version: REGISTRY_VERSION,\n updatedAt: new Date().toISOString(),\n entries: [],\n };\n }\n\n if (sysError instanceof SyntaxError) {\n const backupPath = `${this.registryPath}.corrupt.${Date.now()}`;\n await fs.rename(this.registryPath, backupPath).catch(() => undefined);\n }\n\n throw new ProcessRegistryError(\n `Failed to read registry file: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_READ_FAILED',\n { cause: error },\n );\n }\n }\n\n private async saveState(state: ProcessRegistryState): Promise<void> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n const payload: ProcessRegistryState = {\n ...state,\n updatedAt: new Date().toISOString(),\n entries: [...state.entries],\n };\n\n const tempPath = makeTempPath(this.registryPath);\n await fs.writeFile(tempPath, JSON.stringify(payload, null, 2), 'utf-8');\n await fs.rename(tempPath, this.registryPath);\n }\n\n private async pruneState(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const repositoryPruned = await this.pruneMissingRepositories(state);\n return this.pruneDeadProcesses(repositoryPruned);\n }\n\n private async pruneMissingRepositories(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const entries = await Promise.all(\n state.entries.map(async (entry) => ({\n entry,\n exists: await this.pathExists(entry.repositoryPath),\n })),\n );\n\n const pruned = entries.filter((value) => value.exists).map((value) => value.entry);\n if (pruned.length !== state.entries.length) {\n state.entries = pruned;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async pruneDeadProcesses(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const aliveEntries: ProcessRegistryRecord[] = [];\n let changed = false;\n\n for (const entry of state.entries) {\n if (this.isProcessRunning(entry.pid)) {\n aliveEntries.push(entry);\n continue;\n }\n\n changed = true;\n await this.releaseAssociatedPort(entry);\n }\n\n if (changed) {\n state.entries = aliveEntries;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async collectMatchingEntries(filters: NormalizedReleaseFilters): Promise<ProcessRegistryRecord[]> {\n return this.withLock(async () => {\n const state = await this.loadState();\n return [...state.entries].filter((entry) => this.matchesReleaseFilters(entry, filters));\n });\n }\n\n private async collectStaleEntries(\n filters: NormalizedListFilters,\n ): Promise<{ staleEntries: ProcessRegistryRecord[]; scanned: number }> {\n return this.withLock(async () => {\n const state = await this.loadState();\n const checks = await Promise.all(\n state.entries.map(async (entry) => {\n if (!this.matchesListFilters(entry, filters)) {\n return { entry, stale: false, scanned: false };\n }\n\n const repositoryExists = await this.pathExists(entry.repositoryPath);\n const deadPid = !this.isProcessRunning(entry.pid);\n return {\n entry,\n stale: !repositoryExists || deadPid,\n scanned: true,\n };\n }),\n );\n\n return {\n staleEntries: checks.filter((check) => check.stale).map((check) => check.entry),\n scanned: checks.filter((check) => check.scanned).length,\n };\n });\n }\n\n private async removeEntriesByKey(entryKeys: Set<string>): Promise<number> {\n if (entryKeys.size === 0) {\n return 0;\n }\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const before = state.entries.length;\n state.entries = state.entries.filter((entry) => !entryKeys.has(this.entryKey(entry)));\n const removed = before - state.entries.length;\n\n if (removed > 0) {\n await this.saveState(state);\n }\n\n return removed;\n });\n }\n\n private async acquireLock(token: string): Promise<void> {\n await fs.mkdir(path.dirname(this.lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt += 1) {\n let tempPath: string | undefined;\n try {\n const lockState: LockState = {\n pid: process.pid,\n token,\n createdAt: new Date().toISOString(),\n };\n tempPath = `${this.lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.acquire`;\n await fs.writeFile(tempPath, JSON.stringify(lockState), 'utf-8');\n await fs.link(tempPath, this.lockPath);\n this.registerLockCleanup(token);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ProcessRegistryError(\n `Failed to acquire registry lock: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_LOCK_FAILED',\n { cause: error },\n );\n }\n\n const stale = await this.isStaleLock();\n if (stale) {\n await fs.unlink(this.lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await this.delay(LOCK_RETRY_DELAY_MS);\n } finally {\n if (tempPath) {\n await fs.unlink(tempPath).catch(() => undefined);\n }\n }\n }\n\n throw new ProcessRegistryError('Unable to acquire registry lock (timeout)', 'REGISTRY_LOCK_FAILED');\n }\n\n private async releaseLock(token: string): Promise<void> {\n try {\n const existing = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token: string };\n if (parsed.token === token) {\n await fs.unlink(this.lockPath);\n }\n } catch {\n // ignore\n } finally {\n this.unregisterLockCleanup(token);\n }\n }\n\n private async isStaleLock(): Promise<boolean> {\n try {\n const content = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(content) as LockState;\n\n if (parsed.pid) {\n const pidAlive = this.isProcessRunning(parsed.pid);\n if (pidAlive) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < LOCK_STALE_AFTER_MS);\n }\n }\n\n return true;\n } catch {\n return true;\n }\n }\n\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private registerLockCleanup(token: string): void {\n this.unregisterLockCleanup(this.activeLockToken);\n this.activeLockToken = token;\n\n const cleanup = (): void => {\n this.releaseLockSync(token);\n };\n\n for (const event of [\n 'exit',\n 'SIGINT',\n 'SIGTERM',\n 'uncaughtException',\n 'unhandledRejection',\n ] satisfies LockCleanupEvent[]) {\n process.once(event, cleanup);\n this.lockCleanupHandlers.set(event, cleanup);\n }\n }\n\n private unregisterLockCleanup(token: string | undefined): void {\n if (!token || this.activeLockToken !== token) {\n return;\n }\n\n for (const [event, handler] of this.lockCleanupHandlers) {\n process.off(event, handler);\n }\n\n this.lockCleanupHandlers.clear();\n this.activeLockToken = undefined;\n }\n\n private releaseLockSync(token: string): void {\n try {\n const existing = fsSync.readFileSync(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(this.lockPath);\n }\n } catch {\n // ignore best-effort cleanup\n }\n }\n\n private async terminateProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n } catch {\n return;\n }\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGTERM to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(500);\n\n if (!this.isProcessRunning(pid)) {\n return;\n }\n\n try {\n process.kill(pid, 'SIGKILL');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGKILL to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(250);\n\n if (this.isProcessRunning(pid)) {\n throw new Error(`Process ${pid} did not exit after SIGKILL`);\n }\n }\n\n private async releaseAssociatedPort(entry: ProcessRegistryRecord): Promise<void> {\n if (!this.portRegistry || !entry.port) {\n return;\n }\n\n try {\n const result = await this.portRegistry.releasePort({\n repositoryPath: entry.repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n pid: entry.pid,\n force: true,\n });\n\n if (!result.success && result.error && !result.error.includes('No matching registry entry')) {\n throw new Error(result.error);\n }\n } catch {\n // Best-effort cleanup: port release failures should not block process cleanup.\n }\n }\n\n private entryKey(entry: ProcessRegistryRecord): string {\n return [\n entry.repositoryPath,\n entry.serviceName,\n entry.serviceType,\n entry.environment ?? '',\n String(entry.pid),\n ].join('|');\n }\n\n private normalizeListFilters(filters: ListProcessFilters): NormalizedListFilters {\n return {\n ...(filters.repositoryPath ? { repositoryPath: normalizeRepositoryPath(filters.repositoryPath) } : {}),\n ...(filters.serviceName ? { serviceName: filters.serviceName } : {}),\n ...(filters.serviceType ? { serviceType: filters.serviceType } : {}),\n ...(filters.environment ? { environment: filters.environment } : {}),\n ...(filters.tags ? { tags: normalizeTags(filters.tags) } : {}),\n };\n }\n\n private normalizeReleaseFilters(filters: ReleaseProcessesRequest): NormalizedReleaseFilters {\n return {\n ...this.normalizeListFilters(filters),\n ...(typeof filters.pid === 'number' ? { pid: filters.pid } : {}),\n ...(typeof filters.force === 'boolean' ? { force: filters.force } : {}),\n };\n }\n\n private matchesListFilters(entry: ProcessRegistryRecord, filters: NormalizedListFilters): boolean {\n if (filters.repositoryPath && entry.repositoryPath !== filters.repositoryPath) return false;\n if (filters.serviceType && entry.serviceType !== filters.serviceType) return false;\n if (filters.serviceName && entry.serviceName !== filters.serviceName) return false;\n if (filters.environment && entry.environment !== filters.environment) return false;\n if (filters.tags && filters.tags.length > 0 && !this.matchesTags(entry.tags, filters.tags)) return false;\n return true;\n }\n\n private matchesReleaseFilters(entry: ProcessRegistryRecord, filters: NormalizedReleaseFilters): boolean {\n if (!this.matchesListFilters(entry, filters)) {\n return false;\n }\n\n return typeof filters.pid !== 'number' || filters.force || entry.pid === filters.pid;\n }\n\n private findEntry(state: ProcessRegistryState, filters: NormalizedFilters): ProcessRegistryRecord | undefined {\n return state.entries.find(\n (entry) =>\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (filters.environment ? entry.environment === filters.environment : true),\n );\n }\n\n private removeMatchingEntries(state: ProcessRegistryState, filters: NormalizedFilters): void {\n state.entries = state.entries.filter(\n (entry) =>\n !(\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (!filters.environment || entry.environment === filters.environment)\n ),\n );\n }\n\n private createSuccessResponse(pid?: number, record?: ProcessRegistryRecord): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: true,\n ...(typeof pid === 'number' ? { pid } : {}),\n ...(record ? { record } : {}),\n });\n }\n\n private createFailureResponse(error: string): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: false,\n error,\n });\n }\n\n private matchesTags(entryTags: string[] | undefined, requestedTags: string[]): boolean {\n if (requestedTags.length === 0) {\n return true;\n }\n\n if (!entryTags || entryTags.length === 0) {\n return false;\n }\n\n return requestedTags.some((tag) => entryTags.includes(tag));\n }\n\n private async pathExists(candidate: string): Promise<boolean> {\n try {\n await fs.access(candidate);\n return true;\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nfunction normalizeTags(tags: string[] | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n return Array.from(new Set(tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomBytes as e}from"node:crypto";import t from"node:fs";import n from"node:fs/promises";import r from"node:path";import i from"node:os";import{PortRegistryService as a}from"@agimon-ai/foundation-port-registry";import{z as o}from"zod";const s=r.join(i.homedir(),`.process-registry`,`processes.json`),c=`${s}.lock`,l=75,u=80,d=5e3,f=`PROCESS_REGISTRY_TAG`,p=e=>r.resolve(e),m=t=>`${t}.${e(6).toString(`hex`)}.tmp`;function h(e){let t=_(e),n=_(process.env[f]),r=[...t??[],...n??[]];if(r.length!==0)return Array.from(new Set(r))}function g(e,t){if(!e)return;let n=r.resolve(e);return r.extname(n)===`.json`?r.join(r.dirname(n),t):r.join(n,t)}function _(e){if(e===void 0)return;let t=(Array.isArray(e)?e:e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)).map(e=>e.trim()).filter(e=>e.length>0);return t.length>0?t:void 0}const v=1,y=o.enum([`service`,`tool`]),b=o.record(o.string(),o.unknown()),x=o.object({repositoryPath:o.string().trim().min(1,`repositoryPath is required`),serviceName:o.string().trim().min(1,`serviceName is required`),serviceType:y,environment:o.string().trim().min(1).optional(),pid:o.number().int().min(1),port:o.number().int().min(1).max(65535).optional(),host:o.string().trim().min(1).optional(),command:o.string().trim().min(1).optional(),args:o.array(o.string()).optional(),tags:o.array(o.string().trim().min(1)).optional(),metadata:b.optional(),createdAt:o.string().trim().min(1),updatedAt:o.string().trim().min(1)}),S=o.object({version:o.literal(1),updatedAt:o.string().trim().min(1),entries:o.array(x)}),C=o.object({repositoryPath:o.string().trim().min(1),serviceName:o.string().trim().min(1),serviceType:y.default(`service`),environment:o.string().trim().min(1).optional(),pid:o.number().int().min(1),port:o.number().int().min(1).max(65535).optional(),host:o.string().trim().min(1).optional(),command:o.string().trim().min(1).optional(),args:o.array(o.string()).optional(),tags:o.array(o.string().trim().min(1)).optional(),metadata:b.optional(),force:o.boolean().optional()}),w=o.object({repositoryPath:o.string().trim().min(1),serviceName:o.string().trim().min(1),serviceType:y.optional(),pid:o.number().int().min(1).optional(),environment:o.string().trim().min(1).optional(),tags:o.array(o.string().trim().min(1)).optional(),force:o.boolean().optional(),kill:o.boolean().optional().default(!0),releasePort:o.boolean().optional().default(!0)}),T=o.object({repositoryPath:o.string().trim().min(1).optional(),serviceType:y.optional(),serviceName:o.string().trim().min(1).optional(),environment:o.string().trim().min(1).optional(),tags:o.array(o.string().trim().min(1)).optional()}),E=o.object({success:o.boolean(),pid:o.number().int().min(1).optional(),record:x.optional(),error:o.string().optional()});var D=class extends Error{code;constructor(e,t,n){super(e,n),this.name=`ProcessRegistryError`,this.code=t}},O=class i{registryPath;lockPath;activeLockToken;lockCleanupHandlers=new Map;constructor(e=s,t,n=new a(process.env.PORT_REGISTRY_PATH)){this.portRegistry=n,this.registryPath=i.resolveRegistryPath(e),this.lockPath=t??`${this.registryPath}.lock`}static resolveRegistryPath(e){if(!e)return s;let t=r.isAbsolute(e)?e:r.join(process.cwd(),e);return r.extname(t)===`.json`?t:r.join(t,`processes.json`)}async registerProcess(e){let t=C.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=p(t.repositoryPath),r=t.serviceType??`service`,i=t.environment??process.env.NODE_ENV??`development`,a=h(t.tags),o=await this.pruneState(e),s=this.findEntry(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i});if(s){if(s.pid===t.pid)return s.updatedAt=new Date().toISOString(),s.port=t.port??s.port,s.host=t.host??s.host,s.command=t.command??s.command,s.args=t.args??s.args,s.tags=a??s.tags,s.metadata=t.metadata??s.metadata,await this.saveState(o),this.createSuccessResponse(t.pid,s);let e=this.isProcessRunning(s.pid);if(e&&!(t.force??!0))return this.createFailureResponse(`Process already registered for ${t.serviceName} in requested scope`);e&&await this.terminateProcess(s.pid),await this.releaseAssociatedPort(s),this.removeMatchingEntries(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i})}let c=new Date().toISOString(),l={repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i,pid:t.pid,port:t.port,host:t.host,command:t.command,args:t.args,tags:a,metadata:t.metadata,createdAt:c,updatedAt:c};return o.entries.push(l),await this.saveState(o),this.createSuccessResponse(t.pid,l)})}async releaseProcess(e){let t=w.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=await this.pruneMissingRepositories(e),r=p(t.repositoryPath),i=t.serviceType??`service`,a=t.environment??process.env.NODE_ENV??`development`,o=k(t.tags),s=n.entries.filter(e=>!(e.repositoryPath!==r||e.serviceName!==t.serviceName||e.serviceType!==i||t.environment&&e.environment!==a||typeof t.pid==`number`&&e.pid!==t.pid||o&&o.length>0&&!this.matchesTags(e.tags,o)));if(s.length===0)return this.createFailureResponse(`No matching process entry for ${t.serviceName}`);let c=[],l=new Set;for(let e of s){let n=this.entryKey(e);try{(t.kill??!0)&&this.isProcessRunning(e.pid)&&await this.terminateProcess(e.pid),(t.releasePort??!0)&&await this.releaseAssociatedPort(e),l.add(n)}catch(t){c.push(`${e.serviceName} (pid ${e.pid}): ${t instanceof Error?t.message:String(t)}`)}}return l.size>0&&(n.entries=n.entries.filter(e=>!l.has(this.entryKey(e))),await this.saveState(n)),c.length>0?this.createFailureResponse(c.join(`; `)):this.createSuccessResponse()})}async listProcesses(e={}){let t=T.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=await this.pruneState(e),r=k(t.tags);return[...n.entries].filter(e=>!(t.repositoryPath&&e.repositoryPath!==p(t.repositoryPath)||t.serviceType&&e.serviceType!==t.serviceType||t.serviceName&&e.serviceName!==t.serviceName||t.environment&&e.environment!==t.environment||r&&r.length>0&&!this.matchesTags(e.tags,r)))})}async withLock(t){let n=`${process.pid}-${e(6).toString(`hex`)}`;await this.acquireLock(n);try{return await t()}finally{await this.releaseLock(n)}}async loadState(){await n.mkdir(r.dirname(this.registryPath),{recursive:!0});try{let e=await n.readFile(this.registryPath,`utf-8`),t=JSON.parse(e);return S.parse(t)}catch(e){let t=e;if(t.code===`ENOENT`)return{version:1,updatedAt:new Date().toISOString(),entries:[]};if(t instanceof SyntaxError){let e=`${this.registryPath}.corrupt.${Date.now()}`;await n.rename(this.registryPath,e).catch(()=>void 0)}throw new D(`Failed to read registry file: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_READ_FAILED`,{cause:e})}}async saveState(e){await n.mkdir(r.dirname(this.registryPath),{recursive:!0});let t={...e,updatedAt:new Date().toISOString(),entries:[...e.entries]},i=m(this.registryPath);await n.writeFile(i,JSON.stringify(t,null,2),`utf-8`),await n.rename(i,this.registryPath)}async pruneState(e){let t=await this.pruneMissingRepositories(e);return this.pruneDeadProcesses(t)}async pruneMissingRepositories(e){let t=(await Promise.all(e.entries.map(async e=>({entry:e,exists:await this.pathExists(e.repositoryPath)})))).filter(e=>e.exists).map(e=>e.entry);return t.length!==e.entries.length&&(e.entries=t,await this.saveState(e)),e}async pruneDeadProcesses(e){let t=[],n=!1;for(let r of e.entries){if(this.isProcessRunning(r.pid)){t.push(r);continue}n=!0,await this.releaseAssociatedPort(r)}return n&&(e.entries=t,await this.saveState(e)),e}async acquireLock(e){await n.mkdir(r.dirname(this.lockPath),{recursive:!0});for(let t=0;t<80;t+=1)try{let t={pid:process.pid,token:e,createdAt:new Date().toISOString()};await n.writeFile(this.lockPath,JSON.stringify(t),{flag:`wx`}),this.registerLockCleanup(e);return}catch(e){if(e.code!==`EEXIST`)throw new D(`Failed to acquire registry lock: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_LOCK_FAILED`,{cause:e});if(await this.isStaleLock()){await n.unlink(this.lockPath).catch(()=>void 0),--t;continue}await this.delay(75)}throw new D(`Unable to acquire registry lock (timeout)`,`REGISTRY_LOCK_FAILED`)}async releaseLock(e){try{let t=await n.readFile(this.lockPath,`utf-8`);JSON.parse(t).token===e&&await n.unlink(this.lockPath)}catch{}finally{this.unregisterLockCleanup(e)}}async isStaleLock(){try{let e=await n.readFile(this.lockPath,`utf-8`),t=JSON.parse(e);if(t.pid&&this.isProcessRunning(t.pid)){let e=Date.now()-new Date(t.createdAt).getTime();return!(Number.isFinite(e)&&e<5e3)}return!0}catch{return!0}}isProcessRunning(e){try{return process.kill(e,0),!0}catch{return!1}}registerLockCleanup(e){this.unregisterLockCleanup(this.activeLockToken),this.activeLockToken=e;let t=()=>{this.releaseLockSync(e)};for(let e of[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`])process.once(e,t),this.lockCleanupHandlers.set(e,t)}unregisterLockCleanup(e){if(!(!e||this.activeLockToken!==e)){for(let[e,t]of this.lockCleanupHandlers)process.off(e,t);this.lockCleanupHandlers.clear(),this.activeLockToken=void 0}}releaseLockSync(e){try{let n=t.readFileSync(this.lockPath,`utf-8`);JSON.parse(n).token===e&&t.unlinkSync(this.lockPath)}catch{}}async terminateProcess(e){try{process.kill(e,0)}catch{return}try{process.kill(e,`SIGTERM`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGTERM to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(500),this.isProcessRunning(e)){try{process.kill(e,`SIGKILL`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGKILL to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(250),this.isProcessRunning(e))throw Error(`Process ${e} did not exit after SIGKILL`)}}async releaseAssociatedPort(e){if(!(!this.portRegistry||!e.port))try{let t=await this.portRegistry.releasePort({repositoryPath:e.repositoryPath,serviceName:e.serviceName,serviceType:e.serviceType,environment:e.environment,pid:e.pid,force:!0});if(!t.success&&t.error&&!t.error.includes(`No matching registry entry`))throw Error(t.error)}catch{}}entryKey(e){return[e.repositoryPath,e.serviceName,e.serviceType,e.environment??``,String(e.pid)].join(`|`)}findEntry(e,t){return e.entries.find(e=>e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(t.environment?e.environment===t.environment:!0))}removeMatchingEntries(e,t){e.entries=e.entries.filter(e=>!(e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(!t.environment||e.environment===t.environment)))}createSuccessResponse(e,t){return E.parse({success:!0,...typeof e==`number`?{pid:e}:{},...t?{record:t}:{}})}createFailureResponse(e){return E.parse({success:!1,error:e})}matchesTags(e,t){return t.length===0?!0:!e||e.length===0?!1:t.some(t=>e.includes(t))}async pathExists(e){try{return await n.access(e),!0}catch{return!1}}delay(e){return new Promise(t=>setTimeout(t,e))}};function k(e){if(e!==void 0)return Array.from(new Set(e.map(e=>e.trim()).filter(e=>e.length>0)))}export{f as _,x as a,h as b,v as c,y as d,c as f,d as g,l as h,D as i,C as l,u as m,T as n,E as o,s as p,b as r,S as s,O as t,w as u,m as v,g as x,p as y};
|
|
1
|
+
import{randomBytes as e}from"node:crypto";import t from"node:fs";import n from"node:fs/promises";import r from"node:path";import i from"node:os";import{PortRegistryService as a}from"@agimon-ai/foundation-port-registry";import{z as o}from"zod";const s=r.join(i.homedir(),`.process-registry`,`processes.json`),c=`${s}.lock`,l=75,u=80,d=5e3,f=`PROCESS_REGISTRY_TAG`,p=e=>r.resolve(e),m=t=>`${t}.${e(6).toString(`hex`)}.tmp`;function h(e){let t=_(e),n=_(process.env[f]),r=[...t??[],...n??[]];if(r.length!==0)return Array.from(new Set(r))}function g(e,t){if(!e)return;let n=r.resolve(e);return r.extname(n)===`.json`?r.join(r.dirname(n),t):r.join(n,t)}function _(e){if(e===void 0)return;let t=(Array.isArray(e)?e:e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)).map(e=>e.trim()).filter(e=>e.length>0);return t.length>0?t:void 0}const v=1,y=o.enum([`service`,`tool`]),b=o.record(o.string(),o.unknown()),x=o.object({repositoryPath:o.string().trim().min(1,`repositoryPath is required`),serviceName:o.string().trim().min(1,`serviceName is required`),serviceType:y,environment:o.string().trim().min(1).optional(),pid:o.number().int().min(1),port:o.number().int().min(1).max(65535).optional(),host:o.string().trim().min(1).optional(),command:o.string().trim().min(1).optional(),args:o.array(o.string()).optional(),tags:o.array(o.string().trim().min(1)).optional(),metadata:b.optional(),createdAt:o.string().trim().min(1),updatedAt:o.string().trim().min(1)}),S=o.object({version:o.literal(1),updatedAt:o.string().trim().min(1),entries:o.array(x)}),C=o.object({repositoryPath:o.string().trim().min(1),serviceName:o.string().trim().min(1),serviceType:y.default(`service`),environment:o.string().trim().min(1).optional(),pid:o.number().int().min(1),port:o.number().int().min(1).max(65535).optional(),host:o.string().trim().min(1).optional(),command:o.string().trim().min(1).optional(),args:o.array(o.string()).optional(),tags:o.array(o.string().trim().min(1)).optional(),metadata:b.optional(),force:o.boolean().optional()}),w=o.object({repositoryPath:o.string().trim().min(1),serviceName:o.string().trim().min(1),serviceType:y.optional(),pid:o.number().int().min(1).optional(),environment:o.string().trim().min(1).optional(),tags:o.array(o.string().trim().min(1)).optional(),force:o.boolean().optional(),kill:o.boolean().optional().default(!0),releasePort:o.boolean().optional().default(!0)}),T=o.object({repositoryPath:o.string().trim().min(1).optional(),serviceType:y.optional(),serviceName:o.string().trim().min(1).optional(),environment:o.string().trim().min(1).optional(),tags:o.array(o.string().trim().min(1)).optional()}),E=T.extend({pid:o.number().int().min(1).optional(),force:o.boolean().optional(),kill:o.boolean().optional().default(!0),releasePort:o.boolean().optional().default(!0)}),D=o.object({success:o.boolean(),pid:o.number().int().min(1).optional(),record:x.optional(),error:o.string().optional()}),O=o.object({success:o.boolean(),scanned:o.number().int().min(0),removed:o.number().int().min(0),error:o.string().optional()}),k=o.object({success:o.boolean(),released:o.number().int().min(0),error:o.string().optional()});var A=class extends Error{code;constructor(e,t,n){super(e,n),this.name=`ProcessRegistryError`,this.code=t}},j=class i{registryPath;lockPath;activeLockToken;lockCleanupHandlers=new Map;constructor(e=s,t,n=new a(process.env.PORT_REGISTRY_PATH)){this.portRegistry=n,this.registryPath=i.resolveRegistryPath(e),this.lockPath=t??`${this.registryPath}.lock`}static resolveRegistryPath(e){if(!e)return s;let t=r.isAbsolute(e)?e:r.join(process.cwd(),e);return r.extname(t)===`.json`?t:r.join(t,`processes.json`)}async registerProcess(e){let t=C.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=p(t.repositoryPath),r=t.serviceType??`service`,i=t.environment??process.env.NODE_ENV??`development`,a=h(t.tags),o=await this.pruneState(e),s=this.findEntry(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i});if(s){if(s.pid===t.pid)return s.updatedAt=new Date().toISOString(),s.port=t.port??s.port,s.host=t.host??s.host,s.command=t.command??s.command,s.args=t.args??s.args,s.tags=a??s.tags,s.metadata=t.metadata??s.metadata,await this.saveState(o),this.createSuccessResponse(t.pid,s);let e=this.isProcessRunning(s.pid);if(e&&!(t.force??!0))return this.createFailureResponse(`Process already registered for ${t.serviceName} in requested scope`);e&&await this.terminateProcess(s.pid),await this.releaseAssociatedPort(s),this.removeMatchingEntries(o,{repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i})}let c=new Date().toISOString(),l={repositoryPath:n,serviceName:t.serviceName,serviceType:r,environment:i,pid:t.pid,port:t.port,host:t.host,command:t.command,args:t.args,tags:a,metadata:t.metadata,createdAt:c,updatedAt:c};return o.entries.push(l),await this.saveState(o),this.createSuccessResponse(t.pid,l)})}async releaseProcess(e){let t=w.parse(e),n=await this.releaseProcesses(t);return n.success?this.createSuccessResponse():this.createFailureResponse(n.error??`No matching process entry for ${t.serviceName}`)}async releaseProcesses(e){let t=E.parse(e),n=this.normalizeReleaseFilters(t),r=await this.collectMatchingEntries(n);if(r.length===0)return k.parse({success:!1,released:0,error:`No matching process entries`});let i=[],a=new Set;for(let e of r)try{t.kill&&this.isProcessRunning(e.pid)&&await this.terminateProcess(e.pid),t.releasePort&&await this.releaseAssociatedPort(e),a.add(this.entryKey(e))}catch(t){i.push(`${e.serviceName} (pid ${e.pid}): ${t instanceof Error?t.message:String(t)}`)}let o=await this.removeEntriesByKey(a);return i.length>0?k.parse({success:!1,released:o,error:i.join(`; `)}):k.parse({success:!0,released:o})}async listProcesses(e={}){let t=T.parse(e);return this.withLock(async()=>{let e=await this.loadState(),n=this.normalizeListFilters(t);return[...e.entries].filter(e=>this.matchesListFilters(e,n))})}async cleanupStaleProcesses(e={}){let t=T.parse(e),n=this.normalizeListFilters(t),{staleEntries:r,scanned:i}=await this.collectStaleEntries(n);for(let e of r)await this.releaseAssociatedPort(e);let a=await this.removeEntriesByKey(new Set(r.map(e=>this.entryKey(e))));return O.parse({success:!0,scanned:i,removed:a})}async withLock(t){let n=`${process.pid}-${e(6).toString(`hex`)}`;await this.acquireLock(n);try{return await t()}finally{await this.releaseLock(n)}}async loadState(){await n.mkdir(r.dirname(this.registryPath),{recursive:!0});try{let e=await n.readFile(this.registryPath,`utf-8`),t=JSON.parse(e);return S.parse(t)}catch(e){let t=e;if(t.code===`ENOENT`)return{version:1,updatedAt:new Date().toISOString(),entries:[]};if(t instanceof SyntaxError){let e=`${this.registryPath}.corrupt.${Date.now()}`;await n.rename(this.registryPath,e).catch(()=>void 0)}throw new A(`Failed to read registry file: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_READ_FAILED`,{cause:e})}}async saveState(e){await n.mkdir(r.dirname(this.registryPath),{recursive:!0});let t={...e,updatedAt:new Date().toISOString(),entries:[...e.entries]},i=m(this.registryPath);await n.writeFile(i,JSON.stringify(t,null,2),`utf-8`),await n.rename(i,this.registryPath)}async pruneState(e){let t=await this.pruneMissingRepositories(e);return this.pruneDeadProcesses(t)}async pruneMissingRepositories(e){let t=(await Promise.all(e.entries.map(async e=>({entry:e,exists:await this.pathExists(e.repositoryPath)})))).filter(e=>e.exists).map(e=>e.entry);return t.length!==e.entries.length&&(e.entries=t,await this.saveState(e)),e}async pruneDeadProcesses(e){let t=[],n=!1;for(let r of e.entries){if(this.isProcessRunning(r.pid)){t.push(r);continue}n=!0,await this.releaseAssociatedPort(r)}return n&&(e.entries=t,await this.saveState(e)),e}async collectMatchingEntries(e){return this.withLock(async()=>[...(await this.loadState()).entries].filter(t=>this.matchesReleaseFilters(t,e)))}async collectStaleEntries(e){return this.withLock(async()=>{let t=await this.loadState(),n=await Promise.all(t.entries.map(async t=>{if(!this.matchesListFilters(t,e))return{entry:t,stale:!1,scanned:!1};let n=await this.pathExists(t.repositoryPath),r=!this.isProcessRunning(t.pid);return{entry:t,stale:!n||r,scanned:!0}}));return{staleEntries:n.filter(e=>e.stale).map(e=>e.entry),scanned:n.filter(e=>e.scanned).length}})}async removeEntriesByKey(e){return e.size===0?0:this.withLock(async()=>{let t=await this.loadState(),n=t.entries.length;t.entries=t.entries.filter(t=>!e.has(this.entryKey(t)));let r=n-t.entries.length;return r>0&&await this.saveState(t),r})}async acquireLock(t){await n.mkdir(r.dirname(this.lockPath),{recursive:!0});for(let r=0;r<80;r+=1){let i;try{let r={pid:process.pid,token:t,createdAt:new Date().toISOString()};i=`${this.lockPath}.${process.pid}.${e(4).toString(`hex`)}.acquire`,await n.writeFile(i,JSON.stringify(r),`utf-8`),await n.link(i,this.lockPath),this.registerLockCleanup(t);return}catch(e){if(e.code!==`EEXIST`)throw new A(`Failed to acquire registry lock: ${e instanceof Error?e.message:String(e)}`,`REGISTRY_LOCK_FAILED`,{cause:e});if(await this.isStaleLock()){await n.unlink(this.lockPath).catch(()=>void 0),--r;continue}await this.delay(75)}finally{i&&await n.unlink(i).catch(()=>void 0)}}throw new A(`Unable to acquire registry lock (timeout)`,`REGISTRY_LOCK_FAILED`)}async releaseLock(e){try{let t=await n.readFile(this.lockPath,`utf-8`);JSON.parse(t).token===e&&await n.unlink(this.lockPath)}catch{}finally{this.unregisterLockCleanup(e)}}async isStaleLock(){try{let e=await n.readFile(this.lockPath,`utf-8`),t=JSON.parse(e);if(t.pid&&this.isProcessRunning(t.pid)){let e=Date.now()-new Date(t.createdAt).getTime();return!(Number.isFinite(e)&&e<5e3)}return!0}catch{return!0}}isProcessRunning(e){try{return process.kill(e,0),!0}catch{return!1}}registerLockCleanup(e){this.unregisterLockCleanup(this.activeLockToken),this.activeLockToken=e;let t=()=>{this.releaseLockSync(e)};for(let e of[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`])process.once(e,t),this.lockCleanupHandlers.set(e,t)}unregisterLockCleanup(e){if(!(!e||this.activeLockToken!==e)){for(let[e,t]of this.lockCleanupHandlers)process.off(e,t);this.lockCleanupHandlers.clear(),this.activeLockToken=void 0}}releaseLockSync(e){try{let n=t.readFileSync(this.lockPath,`utf-8`);JSON.parse(n).token===e&&t.unlinkSync(this.lockPath)}catch{}}async terminateProcess(e){try{process.kill(e,0)}catch{return}try{process.kill(e,`SIGTERM`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGTERM to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(500),this.isProcessRunning(e)){try{process.kill(e,`SIGKILL`)}catch(t){if(t.code===`ESRCH`)return;throw Error(`Failed to send SIGKILL to process ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}if(await this.delay(250),this.isProcessRunning(e))throw Error(`Process ${e} did not exit after SIGKILL`)}}async releaseAssociatedPort(e){if(!(!this.portRegistry||!e.port))try{let t=await this.portRegistry.releasePort({repositoryPath:e.repositoryPath,serviceName:e.serviceName,serviceType:e.serviceType,environment:e.environment,pid:e.pid,force:!0});if(!t.success&&t.error&&!t.error.includes(`No matching registry entry`))throw Error(t.error)}catch{}}entryKey(e){return[e.repositoryPath,e.serviceName,e.serviceType,e.environment??``,String(e.pid)].join(`|`)}normalizeListFilters(e){return{...e.repositoryPath?{repositoryPath:p(e.repositoryPath)}:{},...e.serviceName?{serviceName:e.serviceName}:{},...e.serviceType?{serviceType:e.serviceType}:{},...e.environment?{environment:e.environment}:{},...e.tags?{tags:M(e.tags)}:{}}}normalizeReleaseFilters(e){return{...this.normalizeListFilters(e),...typeof e.pid==`number`?{pid:e.pid}:{},...typeof e.force==`boolean`?{force:e.force}:{}}}matchesListFilters(e,t){return!(t.repositoryPath&&e.repositoryPath!==t.repositoryPath||t.serviceType&&e.serviceType!==t.serviceType||t.serviceName&&e.serviceName!==t.serviceName||t.environment&&e.environment!==t.environment||t.tags&&t.tags.length>0&&!this.matchesTags(e.tags,t.tags))}matchesReleaseFilters(e,t){return this.matchesListFilters(e,t)?typeof t.pid!=`number`||t.force||e.pid===t.pid:!1}findEntry(e,t){return e.entries.find(e=>e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(t.environment?e.environment===t.environment:!0))}removeMatchingEntries(e,t){e.entries=e.entries.filter(e=>!(e.repositoryPath===t.repositoryPath&&e.serviceName===t.serviceName&&e.serviceType===t.serviceType&&(!t.environment||e.environment===t.environment)))}createSuccessResponse(e,t){return D.parse({success:!0,...typeof e==`number`?{pid:e}:{},...t?{record:t}:{}})}createFailureResponse(e){return D.parse({success:!1,error:e})}matchesTags(e,t){return t.length===0?!0:!e||e.length===0?!1:t.some(t=>e.includes(t))}async pathExists(e){try{return await n.access(e),!0}catch{return!1}}delay(e){return new Promise(t=>setTimeout(t,e))}};function M(e){if(e!==void 0)return Array.from(new Set(e.map(e=>e.trim()).filter(e=>e.length>0)))}export{h as C,p as S,u as _,O as a,f as b,D as c,C as d,w as f,s as g,c as h,k as i,S as l,y as m,T as n,A as o,E as p,b as r,x as s,j as t,v as u,l as v,g as w,m as x,d as y};
|
|
2
2
|
//# sourceMappingURL=ProcessRegistryService.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProcessRegistryService.mjs","names":["normalizeTags","sysError"],"sources":["../src/utils/index.ts","../src/types/index.ts","../src/services/ProcessRegistryService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.process-registry', 'processes.json');\nexport const DEFAULT_REGISTRY_LOCK_PATH = `${DEFAULT_REGISTRY_PATH}.lock`;\nexport const LOCK_RETRY_DELAY_MS = 75;\nexport const LOCK_MAX_RETRIES = 80;\nexport const LOCK_STALE_AFTER_MS = 5_000;\nexport const PROCESS_REGISTRY_TAG_ENV_VAR = 'PROCESS_REGISTRY_TAG';\n\nexport const normalizeRepositoryPath = (value: string): string => path.resolve(value);\n\nexport const makeTempPath = (filePath: string): string => {\n const random = randomBytes(6).toString('hex');\n return `${filePath}.${random}.tmp`;\n};\n\nexport function resolveProcessTags(tags?: string[]): string[] | undefined {\n const resolved = normalizeTags(tags);\n const envTags = normalizeTags(process.env[PROCESS_REGISTRY_TAG_ENV_VAR]);\n\n const combined = [...(resolved ?? []), ...(envTags ?? [])];\n if (combined.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(combined));\n}\n\nexport function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined {\n if (!registryPath) {\n return undefined;\n }\n\n const resolved = path.resolve(registryPath);\n if (path.extname(resolved) === '.json') {\n return path.join(path.dirname(resolved), fileName);\n }\n\n return path.join(resolved, fileName);\n}\n\nfunction normalizeTags(tags: string[] | string | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n const values = Array.isArray(tags)\n ? tags\n : tags\n .split(',')\n .map((tag) => tag.trim())\n .filter((tag) => tag.length > 0);\n\n const normalized = values.map((tag) => tag.trim()).filter((tag) => tag.length > 0);\n return normalized.length > 0 ? normalized : undefined;\n}\n","import { z } from 'zod';\n\nexport const REGISTRY_VERSION = 1 as const;\n\nexport const ServiceCategorySchema = z.enum(['service', 'tool']);\n\nexport const ProcessMetadataSchema = z.record(z.string(), z.unknown());\n\nexport const ProcessRegistryRecordSchema = z.object({\n repositoryPath: z.string().trim().min(1, 'repositoryPath is required'),\n serviceName: z.string().trim().min(1, 'serviceName is required'),\n serviceType: ServiceCategorySchema,\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n createdAt: z.string().trim().min(1),\n updatedAt: z.string().trim().min(1),\n});\n\nexport const ProcessRegistryStateSchema = z.object({\n version: z.literal(REGISTRY_VERSION),\n updatedAt: z.string().trim().min(1),\n entries: z.array(ProcessRegistryRecordSchema),\n});\n\nexport const RegisterProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.default('service'),\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n force: z.boolean().optional(),\n});\n\nexport const ReleaseProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.optional(),\n pid: z.number().int().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ListProcessFiltersSchema = z.object({\n repositoryPath: z.string().trim().min(1).optional(),\n serviceType: ServiceCategorySchema.optional(),\n serviceName: z.string().trim().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n});\n\nexport const ProcessRegistryResponseSchema = z.object({\n success: z.boolean(),\n pid: z.number().int().min(1).optional(),\n record: ProcessRegistryRecordSchema.optional(),\n error: z.string().optional(),\n});\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;\nexport type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;\nexport type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;\nexport type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;\nexport type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;\nexport type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;\nexport type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;\n\nexport type ProcessRegistryErrorCode =\n | 'INVALID_REQUEST'\n | 'REGISTRY_READ_FAILED'\n | 'REGISTRY_WRITE_FAILED'\n | 'REGISTRY_LOCK_FAILED'\n | 'NO_PROCESS_FOUND';\n\nexport class ProcessRegistryError extends Error {\n readonly code: ProcessRegistryErrorCode;\n\n constructor(message: string, code: ProcessRegistryErrorCode, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProcessRegistryError';\n this.code = code;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n type ListProcessFilters,\n ListProcessFiltersSchema,\n ProcessRegistryError,\n type ProcessRegistryRecord,\n type ProcessRegistryResponse,\n ProcessRegistryResponseSchema,\n type ProcessRegistryState,\n ProcessRegistryStateSchema,\n REGISTRY_VERSION,\n type RegisterProcessRequest,\n RegisterProcessRequestSchema,\n type ReleaseProcessRequest,\n ReleaseProcessRequestSchema,\n} from '../types';\nimport {\n DEFAULT_REGISTRY_PATH,\n LOCK_MAX_RETRIES,\n LOCK_RETRY_DELAY_MS,\n LOCK_STALE_AFTER_MS,\n resolveProcessTags,\n makeTempPath,\n normalizeRepositoryPath,\n} from '../utils';\n\ninterface LockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\ninterface NormalizedFilters {\n repositoryPath: string;\n serviceName: string;\n serviceType: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ntype LockCleanupEvent = 'exit' | 'SIGINT' | 'SIGTERM' | 'uncaughtException' | 'unhandledRejection';\n\ntype PortRegistryCleanup = Pick<PortRegistryService, 'releasePort'>;\n\nexport class ProcessRegistryService {\n private readonly registryPath: string;\n private readonly lockPath: string;\n private activeLockToken?: string;\n private readonly lockCleanupHandlers = new Map<LockCleanupEvent, (...args: unknown[]) => void>();\n\n constructor(\n registryPath: string = DEFAULT_REGISTRY_PATH,\n lockPath?: string,\n private readonly portRegistry: PortRegistryCleanup = new PortRegistryService(process.env.PORT_REGISTRY_PATH),\n ) {\n this.registryPath = ProcessRegistryService.resolveRegistryPath(registryPath);\n this.lockPath = lockPath ?? `${this.registryPath}.lock`;\n }\n\n static resolveRegistryPath(inputPath?: string): string {\n if (!inputPath) {\n return DEFAULT_REGISTRY_PATH;\n }\n\n const resolvedPath = path.isAbsolute(inputPath) ? inputPath : path.join(process.cwd(), inputPath);\n if (path.extname(resolvedPath) === '.json') {\n return resolvedPath;\n }\n\n return path.join(resolvedPath, 'processes.json');\n }\n\n async registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse> {\n const request = RegisterProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const tags = resolveProcessTags(request.tags);\n const registry = await this.pruneState(state);\n const existing = this.findEntry(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n\n if (existing) {\n if (existing.pid === request.pid) {\n existing.updatedAt = new Date().toISOString();\n existing.port = request.port ?? existing.port;\n existing.host = request.host ?? existing.host;\n existing.command = request.command ?? existing.command;\n existing.args = request.args ?? existing.args;\n existing.tags = tags ?? existing.tags;\n existing.metadata = request.metadata ?? existing.metadata;\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, existing);\n }\n\n const isAlive = this.isProcessRunning(existing.pid);\n if (isAlive && !(request.force ?? true)) {\n return this.createFailureResponse(`Process already registered for ${request.serviceName} in requested scope`);\n }\n\n if (isAlive) {\n await this.terminateProcess(existing.pid);\n }\n\n await this.releaseAssociatedPort(existing);\n this.removeMatchingEntries(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n }\n\n const now = new Date().toISOString();\n const record: ProcessRegistryRecord = {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n pid: request.pid,\n port: request.port,\n host: request.host,\n command: request.command,\n args: request.args,\n tags,\n metadata: request.metadata,\n createdAt: now,\n updatedAt: now,\n };\n\n registry.entries.push(record);\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, record);\n });\n }\n\n async releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse> {\n const request = ReleaseProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const registry = await this.pruneMissingRepositories(state);\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const requestedTags = normalizeTags(request.tags);\n const matches = registry.entries.filter((entry) => {\n if (entry.repositoryPath !== normalizedRepo) return false;\n if (entry.serviceName !== request.serviceName) return false;\n if (entry.serviceType !== serviceType) return false;\n if (request.environment && entry.environment !== environment) return false;\n if (typeof request.pid === 'number' && entry.pid !== request.pid) return false;\n if (requestedTags && requestedTags.length > 0 && !this.matchesTags(entry.tags, requestedTags)) return false;\n return true;\n });\n\n if (matches.length === 0) {\n return this.createFailureResponse(`No matching process entry for ${request.serviceName}`);\n }\n\n const errors: string[] = [];\n const removable = new Set<string>();\n\n for (const entry of matches) {\n const entryKey = this.entryKey(entry);\n\n try {\n if (request.kill ?? true) {\n if (this.isProcessRunning(entry.pid)) {\n await this.terminateProcess(entry.pid);\n }\n }\n\n if (request.releasePort ?? true) {\n await this.releaseAssociatedPort(entry);\n }\n\n removable.add(entryKey);\n } catch (error) {\n errors.push(\n `${entry.serviceName} (pid ${entry.pid}): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n if (removable.size > 0) {\n registry.entries = registry.entries.filter((entry) => !removable.has(this.entryKey(entry)));\n await this.saveState(registry);\n }\n\n if (errors.length > 0) {\n return this.createFailureResponse(errors.join('; '));\n }\n\n return this.createSuccessResponse();\n });\n }\n\n async listProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryRecord[]> {\n const request = ListProcessFiltersSchema.parse(filters);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const registry = await this.pruneState(state);\n const requestedTags = normalizeTags(request.tags);\n\n return [...registry.entries].filter((entry) => {\n if (request.repositoryPath && entry.repositoryPath !== normalizeRepositoryPath(request.repositoryPath)) {\n return false;\n }\n if (request.serviceType && entry.serviceType !== request.serviceType) return false;\n if (request.serviceName && entry.serviceName !== request.serviceName) return false;\n if (request.environment && entry.environment !== request.environment) return false;\n if (requestedTags && requestedTags.length > 0 && !this.matchesTags(entry.tags, requestedTags)) return false;\n return true;\n });\n });\n }\n\n private async withLock<T>(callback: () => Promise<T>): Promise<T> {\n const lockToken = `${process.pid}-${randomBytes(6).toString('hex')}`;\n await this.acquireLock(lockToken);\n\n try {\n return await callback();\n } finally {\n await this.releaseLock(lockToken);\n }\n }\n\n private async loadState(): Promise<ProcessRegistryState> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n try {\n const content = await fs.readFile(this.registryPath, 'utf-8');\n const parsed = JSON.parse(content);\n return ProcessRegistryStateSchema.parse(parsed);\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ENOENT') {\n return {\n version: REGISTRY_VERSION,\n updatedAt: new Date().toISOString(),\n entries: [],\n };\n }\n\n if (sysError instanceof SyntaxError) {\n const backupPath = `${this.registryPath}.corrupt.${Date.now()}`;\n await fs.rename(this.registryPath, backupPath).catch(() => undefined);\n }\n\n throw new ProcessRegistryError(\n `Failed to read registry file: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_READ_FAILED',\n { cause: error },\n );\n }\n }\n\n private async saveState(state: ProcessRegistryState): Promise<void> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n const payload: ProcessRegistryState = {\n ...state,\n updatedAt: new Date().toISOString(),\n entries: [...state.entries],\n };\n\n const tempPath = makeTempPath(this.registryPath);\n await fs.writeFile(tempPath, JSON.stringify(payload, null, 2), 'utf-8');\n await fs.rename(tempPath, this.registryPath);\n }\n\n private async pruneState(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const repositoryPruned = await this.pruneMissingRepositories(state);\n return this.pruneDeadProcesses(repositoryPruned);\n }\n\n private async pruneMissingRepositories(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const entries = await Promise.all(\n state.entries.map(async (entry) => ({\n entry,\n exists: await this.pathExists(entry.repositoryPath),\n })),\n );\n\n const pruned = entries.filter((value) => value.exists).map((value) => value.entry);\n if (pruned.length !== state.entries.length) {\n state.entries = pruned;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async pruneDeadProcesses(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const aliveEntries: ProcessRegistryRecord[] = [];\n let changed = false;\n\n for (const entry of state.entries) {\n if (this.isProcessRunning(entry.pid)) {\n aliveEntries.push(entry);\n continue;\n }\n\n changed = true;\n await this.releaseAssociatedPort(entry);\n }\n\n if (changed) {\n state.entries = aliveEntries;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async acquireLock(token: string): Promise<void> {\n await fs.mkdir(path.dirname(this.lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt += 1) {\n try {\n const lockState: LockState = {\n pid: process.pid,\n token,\n createdAt: new Date().toISOString(),\n };\n await fs.writeFile(this.lockPath, JSON.stringify(lockState), { flag: 'wx' });\n this.registerLockCleanup(token);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ProcessRegistryError(\n `Failed to acquire registry lock: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_LOCK_FAILED',\n { cause: error },\n );\n }\n\n const stale = await this.isStaleLock();\n if (stale) {\n await fs.unlink(this.lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await this.delay(LOCK_RETRY_DELAY_MS);\n }\n }\n\n throw new ProcessRegistryError('Unable to acquire registry lock (timeout)', 'REGISTRY_LOCK_FAILED');\n }\n\n private async releaseLock(token: string): Promise<void> {\n try {\n const existing = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token: string };\n if (parsed.token === token) {\n await fs.unlink(this.lockPath);\n }\n } catch {\n // ignore\n } finally {\n this.unregisterLockCleanup(token);\n }\n }\n\n private async isStaleLock(): Promise<boolean> {\n try {\n const content = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(content) as LockState;\n\n if (parsed.pid) {\n const pidAlive = this.isProcessRunning(parsed.pid);\n if (pidAlive) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < LOCK_STALE_AFTER_MS);\n }\n }\n\n return true;\n } catch {\n return true;\n }\n }\n\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private registerLockCleanup(token: string): void {\n this.unregisterLockCleanup(this.activeLockToken);\n this.activeLockToken = token;\n\n const cleanup = (): void => {\n this.releaseLockSync(token);\n };\n\n for (const event of [\n 'exit',\n 'SIGINT',\n 'SIGTERM',\n 'uncaughtException',\n 'unhandledRejection',\n ] satisfies LockCleanupEvent[]) {\n process.once(event, cleanup);\n this.lockCleanupHandlers.set(event, cleanup);\n }\n }\n\n private unregisterLockCleanup(token: string | undefined): void {\n if (!token || this.activeLockToken !== token) {\n return;\n }\n\n for (const [event, handler] of this.lockCleanupHandlers) {\n process.off(event, handler);\n }\n\n this.lockCleanupHandlers.clear();\n this.activeLockToken = undefined;\n }\n\n private releaseLockSync(token: string): void {\n try {\n const existing = fsSync.readFileSync(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(this.lockPath);\n }\n } catch {\n // ignore best-effort cleanup\n }\n }\n\n private async terminateProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n } catch {\n return;\n }\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGTERM to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(500);\n\n if (!this.isProcessRunning(pid)) {\n return;\n }\n\n try {\n process.kill(pid, 'SIGKILL');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGKILL to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(250);\n\n if (this.isProcessRunning(pid)) {\n throw new Error(`Process ${pid} did not exit after SIGKILL`);\n }\n }\n\n private async releaseAssociatedPort(entry: ProcessRegistryRecord): Promise<void> {\n if (!this.portRegistry || !entry.port) {\n return;\n }\n\n try {\n const result = await this.portRegistry.releasePort({\n repositoryPath: entry.repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n pid: entry.pid,\n force: true,\n });\n\n if (!result.success && result.error && !result.error.includes('No matching registry entry')) {\n throw new Error(result.error);\n }\n } catch {\n // Best-effort cleanup: port release failures should not block process cleanup.\n }\n }\n\n private entryKey(entry: ProcessRegistryRecord): string {\n return [\n entry.repositoryPath,\n entry.serviceName,\n entry.serviceType,\n entry.environment ?? '',\n String(entry.pid),\n ].join('|');\n }\n\n private findEntry(state: ProcessRegistryState, filters: NormalizedFilters): ProcessRegistryRecord | undefined {\n return state.entries.find(\n (entry) =>\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (filters.environment ? entry.environment === filters.environment : true),\n );\n }\n\n private removeMatchingEntries(state: ProcessRegistryState, filters: NormalizedFilters): void {\n state.entries = state.entries.filter(\n (entry) =>\n !(\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (!filters.environment || entry.environment === filters.environment)\n ),\n );\n }\n\n private createSuccessResponse(pid?: number, record?: ProcessRegistryRecord): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: true,\n ...(typeof pid === 'number' ? { pid } : {}),\n ...(record ? { record } : {}),\n });\n }\n\n private createFailureResponse(error: string): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: false,\n error,\n });\n }\n\n private matchesTags(entryTags: string[] | undefined, requestedTags: string[]): boolean {\n if (requestedTags.length === 0) {\n return true;\n }\n\n if (!entryTags || entryTags.length === 0) {\n return false;\n }\n\n return requestedTags.some((tag) => entryTags.includes(tag));\n }\n\n private async pathExists(candidate: string): Promise<boolean> {\n try {\n await fs.access(candidate);\n return true;\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nfunction normalizeTags(tags: string[] | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n return Array.from(new Set(tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProcessRegistryService.mjs","names":["normalizeTags","sysError"],"sources":["../src/utils/index.ts","../src/types/index.ts","../src/services/ProcessRegistryService.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_REGISTRY_PATH = path.join(os.homedir(), '.process-registry', 'processes.json');\nexport const DEFAULT_REGISTRY_LOCK_PATH = `${DEFAULT_REGISTRY_PATH}.lock`;\nexport const LOCK_RETRY_DELAY_MS = 75;\nexport const LOCK_MAX_RETRIES = 80;\nexport const LOCK_STALE_AFTER_MS = 5_000;\nexport const PROCESS_REGISTRY_TAG_ENV_VAR = 'PROCESS_REGISTRY_TAG';\n\nexport const normalizeRepositoryPath = (value: string): string => path.resolve(value);\n\nexport const makeTempPath = (filePath: string): string => {\n const random = randomBytes(6).toString('hex');\n return `${filePath}.${random}.tmp`;\n};\n\nexport function resolveProcessTags(tags?: string[]): string[] | undefined {\n const resolved = normalizeTags(tags);\n const envTags = normalizeTags(process.env[PROCESS_REGISTRY_TAG_ENV_VAR]);\n\n const combined = [...(resolved ?? []), ...(envTags ?? [])];\n if (combined.length === 0) {\n return undefined;\n }\n\n return Array.from(new Set(combined));\n}\n\nexport function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined {\n if (!registryPath) {\n return undefined;\n }\n\n const resolved = path.resolve(registryPath);\n if (path.extname(resolved) === '.json') {\n return path.join(path.dirname(resolved), fileName);\n }\n\n return path.join(resolved, fileName);\n}\n\nfunction normalizeTags(tags: string[] | string | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n const values = Array.isArray(tags)\n ? tags\n : tags\n .split(',')\n .map((tag) => tag.trim())\n .filter((tag) => tag.length > 0);\n\n const normalized = values.map((tag) => tag.trim()).filter((tag) => tag.length > 0);\n return normalized.length > 0 ? normalized : undefined;\n}\n","import { z } from 'zod';\n\nexport const REGISTRY_VERSION = 1 as const;\n\nexport const ServiceCategorySchema = z.enum(['service', 'tool']);\n\nexport const ProcessMetadataSchema = z.record(z.string(), z.unknown());\n\nexport const ProcessRegistryRecordSchema = z.object({\n repositoryPath: z.string().trim().min(1, 'repositoryPath is required'),\n serviceName: z.string().trim().min(1, 'serviceName is required'),\n serviceType: ServiceCategorySchema,\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n createdAt: z.string().trim().min(1),\n updatedAt: z.string().trim().min(1),\n});\n\nexport const ProcessRegistryStateSchema = z.object({\n version: z.literal(REGISTRY_VERSION),\n updatedAt: z.string().trim().min(1),\n entries: z.array(ProcessRegistryRecordSchema),\n});\n\nexport const RegisterProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.default('service'),\n environment: z.string().trim().min(1).optional(),\n pid: z.number().int().min(1),\n port: z.number().int().min(1).max(65535).optional(),\n host: z.string().trim().min(1).optional(),\n command: z.string().trim().min(1).optional(),\n args: z.array(z.string()).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n metadata: ProcessMetadataSchema.optional(),\n force: z.boolean().optional(),\n});\n\nexport const ReleaseProcessRequestSchema = z.object({\n repositoryPath: z.string().trim().min(1),\n serviceName: z.string().trim().min(1),\n serviceType: ServiceCategorySchema.optional(),\n pid: z.number().int().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ListProcessFiltersSchema = z.object({\n repositoryPath: z.string().trim().min(1).optional(),\n serviceType: ServiceCategorySchema.optional(),\n serviceName: z.string().trim().min(1).optional(),\n environment: z.string().trim().min(1).optional(),\n tags: z.array(z.string().trim().min(1)).optional(),\n});\n\nexport const ReleaseProcessesRequestSchema = ListProcessFiltersSchema.extend({\n pid: z.number().int().min(1).optional(),\n force: z.boolean().optional(),\n kill: z.boolean().optional().default(true),\n releasePort: z.boolean().optional().default(true),\n});\n\nexport const ProcessRegistryResponseSchema = z.object({\n success: z.boolean(),\n pid: z.number().int().min(1).optional(),\n record: ProcessRegistryRecordSchema.optional(),\n error: z.string().optional(),\n});\n\nexport const ProcessRegistryCleanupResponseSchema = z.object({\n success: z.boolean(),\n scanned: z.number().int().min(0),\n removed: z.number().int().min(0),\n error: z.string().optional(),\n});\n\nexport const ProcessRegistryBulkReleaseResponseSchema = z.object({\n success: z.boolean(),\n released: z.number().int().min(0),\n error: z.string().optional(),\n});\n\nexport type ServiceCategory = z.infer<typeof ServiceCategorySchema>;\nexport type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;\nexport type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;\nexport type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;\nexport type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;\nexport type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;\nexport type ReleaseProcessesRequest = z.infer<typeof ReleaseProcessesRequestSchema>;\nexport type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;\nexport type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;\nexport type ProcessRegistryCleanupResponse = z.infer<typeof ProcessRegistryCleanupResponseSchema>;\nexport type ProcessRegistryBulkReleaseResponse = z.infer<typeof ProcessRegistryBulkReleaseResponseSchema>;\n\nexport type ProcessRegistryErrorCode =\n | 'INVALID_REQUEST'\n | 'REGISTRY_READ_FAILED'\n | 'REGISTRY_WRITE_FAILED'\n | 'REGISTRY_LOCK_FAILED'\n | 'NO_PROCESS_FOUND';\n\nexport class ProcessRegistryError extends Error {\n readonly code: ProcessRegistryErrorCode;\n\n constructor(message: string, code: ProcessRegistryErrorCode, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ProcessRegistryError';\n this.code = code;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { PortRegistryService } from '@agimon-ai/foundation-port-registry';\nimport {\n type ListProcessFilters,\n ListProcessFiltersSchema,\n type ProcessRegistryBulkReleaseResponse,\n ProcessRegistryBulkReleaseResponseSchema,\n type ProcessRegistryCleanupResponse,\n ProcessRegistryCleanupResponseSchema,\n ProcessRegistryError,\n type ProcessRegistryRecord,\n type ProcessRegistryResponse,\n ProcessRegistryResponseSchema,\n type ProcessRegistryState,\n ProcessRegistryStateSchema,\n REGISTRY_VERSION,\n type RegisterProcessRequest,\n RegisterProcessRequestSchema,\n type ReleaseProcessesRequest,\n ReleaseProcessesRequestSchema,\n type ReleaseProcessRequest,\n ReleaseProcessRequestSchema,\n} from '../types';\nimport {\n DEFAULT_REGISTRY_PATH,\n LOCK_MAX_RETRIES,\n LOCK_RETRY_DELAY_MS,\n LOCK_STALE_AFTER_MS,\n makeTempPath,\n normalizeRepositoryPath,\n resolveProcessTags,\n} from '../utils';\n\ninterface LockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\ninterface NormalizedFilters {\n repositoryPath: string;\n serviceName: string;\n serviceType: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ninterface NormalizedListFilters {\n repositoryPath?: string;\n serviceName?: string;\n serviceType?: 'service' | 'tool';\n environment?: string;\n tags?: string[];\n}\n\ninterface NormalizedReleaseFilters extends NormalizedListFilters {\n pid?: number;\n force?: boolean;\n}\n\ntype LockCleanupEvent = 'exit' | 'SIGINT' | 'SIGTERM' | 'uncaughtException' | 'unhandledRejection';\n\ntype PortRegistryCleanup = Pick<PortRegistryService, 'releasePort'>;\n\nexport class ProcessRegistryService {\n private readonly registryPath: string;\n private readonly lockPath: string;\n private activeLockToken?: string;\n private readonly lockCleanupHandlers = new Map<LockCleanupEvent, (...args: unknown[]) => void>();\n\n constructor(\n registryPath: string = DEFAULT_REGISTRY_PATH,\n lockPath?: string,\n private readonly portRegistry: PortRegistryCleanup = new PortRegistryService(process.env.PORT_REGISTRY_PATH),\n ) {\n this.registryPath = ProcessRegistryService.resolveRegistryPath(registryPath);\n this.lockPath = lockPath ?? `${this.registryPath}.lock`;\n }\n\n static resolveRegistryPath(inputPath?: string): string {\n if (!inputPath) {\n return DEFAULT_REGISTRY_PATH;\n }\n\n const resolvedPath = path.isAbsolute(inputPath) ? inputPath : path.join(process.cwd(), inputPath);\n if (path.extname(resolvedPath) === '.json') {\n return resolvedPath;\n }\n\n return path.join(resolvedPath, 'processes.json');\n }\n\n async registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse> {\n const request = RegisterProcessRequestSchema.parse(rawRequest);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedRepo = normalizeRepositoryPath(request.repositoryPath);\n const serviceType = request.serviceType ?? 'service';\n const environment = request.environment ?? process.env.NODE_ENV ?? 'development';\n const tags = resolveProcessTags(request.tags);\n const registry = await this.pruneState(state);\n const existing = this.findEntry(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n\n if (existing) {\n if (existing.pid === request.pid) {\n existing.updatedAt = new Date().toISOString();\n existing.port = request.port ?? existing.port;\n existing.host = request.host ?? existing.host;\n existing.command = request.command ?? existing.command;\n existing.args = request.args ?? existing.args;\n existing.tags = tags ?? existing.tags;\n existing.metadata = request.metadata ?? existing.metadata;\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, existing);\n }\n\n const isAlive = this.isProcessRunning(existing.pid);\n if (isAlive && !(request.force ?? true)) {\n return this.createFailureResponse(`Process already registered for ${request.serviceName} in requested scope`);\n }\n\n if (isAlive) {\n await this.terminateProcess(existing.pid);\n }\n\n await this.releaseAssociatedPort(existing);\n this.removeMatchingEntries(registry, {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n });\n }\n\n const now = new Date().toISOString();\n const record: ProcessRegistryRecord = {\n repositoryPath: normalizedRepo,\n serviceName: request.serviceName,\n serviceType,\n environment,\n pid: request.pid,\n port: request.port,\n host: request.host,\n command: request.command,\n args: request.args,\n tags,\n metadata: request.metadata,\n createdAt: now,\n updatedAt: now,\n };\n\n registry.entries.push(record);\n await this.saveState(registry);\n return this.createSuccessResponse(request.pid, record);\n });\n }\n\n async releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse> {\n const request = ReleaseProcessRequestSchema.parse(rawRequest);\n const result = await this.releaseProcesses(request);\n\n if (!result.success) {\n return this.createFailureResponse(result.error ?? `No matching process entry for ${request.serviceName}`);\n }\n\n return this.createSuccessResponse();\n }\n\n async releaseProcesses(rawRequest: ReleaseProcessesRequest): Promise<ProcessRegistryBulkReleaseResponse> {\n const request = ReleaseProcessesRequestSchema.parse(rawRequest);\n const filters = this.normalizeReleaseFilters(request);\n const matches = await this.collectMatchingEntries(filters);\n\n if (matches.length === 0) {\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: false,\n released: 0,\n error: 'No matching process entries',\n });\n }\n\n const errors: string[] = [];\n const removable = new Set<string>();\n\n for (const entry of matches) {\n try {\n if (request.kill && this.isProcessRunning(entry.pid)) {\n await this.terminateProcess(entry.pid);\n }\n\n if (request.releasePort) {\n await this.releaseAssociatedPort(entry);\n }\n\n removable.add(this.entryKey(entry));\n } catch (error) {\n errors.push(\n `${entry.serviceName} (pid ${entry.pid}): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const released = await this.removeEntriesByKey(removable);\n\n if (errors.length > 0) {\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: false,\n released,\n error: errors.join('; '),\n });\n }\n\n return ProcessRegistryBulkReleaseResponseSchema.parse({\n success: true,\n released,\n });\n }\n\n async listProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryRecord[]> {\n const request = ListProcessFiltersSchema.parse(filters);\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const normalizedFilters = this.normalizeListFilters(request);\n\n return [...state.entries].filter((entry) => this.matchesListFilters(entry, normalizedFilters));\n });\n }\n\n async cleanupStaleProcesses(filters: ListProcessFilters = {}): Promise<ProcessRegistryCleanupResponse> {\n const request = ListProcessFiltersSchema.parse(filters);\n const normalizedFilters = this.normalizeListFilters(request);\n const { staleEntries, scanned } = await this.collectStaleEntries(normalizedFilters);\n\n for (const entry of staleEntries) {\n await this.releaseAssociatedPort(entry);\n }\n\n const removed = await this.removeEntriesByKey(new Set(staleEntries.map((entry) => this.entryKey(entry))));\n\n return ProcessRegistryCleanupResponseSchema.parse({\n success: true,\n scanned,\n removed,\n });\n }\n\n private async withLock<T>(callback: () => Promise<T>): Promise<T> {\n const lockToken = `${process.pid}-${randomBytes(6).toString('hex')}`;\n await this.acquireLock(lockToken);\n\n try {\n return await callback();\n } finally {\n await this.releaseLock(lockToken);\n }\n }\n\n private async loadState(): Promise<ProcessRegistryState> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n try {\n const content = await fs.readFile(this.registryPath, 'utf-8');\n const parsed = JSON.parse(content);\n return ProcessRegistryStateSchema.parse(parsed);\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ENOENT') {\n return {\n version: REGISTRY_VERSION,\n updatedAt: new Date().toISOString(),\n entries: [],\n };\n }\n\n if (sysError instanceof SyntaxError) {\n const backupPath = `${this.registryPath}.corrupt.${Date.now()}`;\n await fs.rename(this.registryPath, backupPath).catch(() => undefined);\n }\n\n throw new ProcessRegistryError(\n `Failed to read registry file: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_READ_FAILED',\n { cause: error },\n );\n }\n }\n\n private async saveState(state: ProcessRegistryState): Promise<void> {\n await fs.mkdir(path.dirname(this.registryPath), { recursive: true });\n\n const payload: ProcessRegistryState = {\n ...state,\n updatedAt: new Date().toISOString(),\n entries: [...state.entries],\n };\n\n const tempPath = makeTempPath(this.registryPath);\n await fs.writeFile(tempPath, JSON.stringify(payload, null, 2), 'utf-8');\n await fs.rename(tempPath, this.registryPath);\n }\n\n private async pruneState(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const repositoryPruned = await this.pruneMissingRepositories(state);\n return this.pruneDeadProcesses(repositoryPruned);\n }\n\n private async pruneMissingRepositories(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const entries = await Promise.all(\n state.entries.map(async (entry) => ({\n entry,\n exists: await this.pathExists(entry.repositoryPath),\n })),\n );\n\n const pruned = entries.filter((value) => value.exists).map((value) => value.entry);\n if (pruned.length !== state.entries.length) {\n state.entries = pruned;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async pruneDeadProcesses(state: ProcessRegistryState): Promise<ProcessRegistryState> {\n const aliveEntries: ProcessRegistryRecord[] = [];\n let changed = false;\n\n for (const entry of state.entries) {\n if (this.isProcessRunning(entry.pid)) {\n aliveEntries.push(entry);\n continue;\n }\n\n changed = true;\n await this.releaseAssociatedPort(entry);\n }\n\n if (changed) {\n state.entries = aliveEntries;\n await this.saveState(state);\n }\n\n return state;\n }\n\n private async collectMatchingEntries(filters: NormalizedReleaseFilters): Promise<ProcessRegistryRecord[]> {\n return this.withLock(async () => {\n const state = await this.loadState();\n return [...state.entries].filter((entry) => this.matchesReleaseFilters(entry, filters));\n });\n }\n\n private async collectStaleEntries(\n filters: NormalizedListFilters,\n ): Promise<{ staleEntries: ProcessRegistryRecord[]; scanned: number }> {\n return this.withLock(async () => {\n const state = await this.loadState();\n const checks = await Promise.all(\n state.entries.map(async (entry) => {\n if (!this.matchesListFilters(entry, filters)) {\n return { entry, stale: false, scanned: false };\n }\n\n const repositoryExists = await this.pathExists(entry.repositoryPath);\n const deadPid = !this.isProcessRunning(entry.pid);\n return {\n entry,\n stale: !repositoryExists || deadPid,\n scanned: true,\n };\n }),\n );\n\n return {\n staleEntries: checks.filter((check) => check.stale).map((check) => check.entry),\n scanned: checks.filter((check) => check.scanned).length,\n };\n });\n }\n\n private async removeEntriesByKey(entryKeys: Set<string>): Promise<number> {\n if (entryKeys.size === 0) {\n return 0;\n }\n\n return this.withLock(async () => {\n const state = await this.loadState();\n const before = state.entries.length;\n state.entries = state.entries.filter((entry) => !entryKeys.has(this.entryKey(entry)));\n const removed = before - state.entries.length;\n\n if (removed > 0) {\n await this.saveState(state);\n }\n\n return removed;\n });\n }\n\n private async acquireLock(token: string): Promise<void> {\n await fs.mkdir(path.dirname(this.lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < LOCK_MAX_RETRIES; attempt += 1) {\n let tempPath: string | undefined;\n try {\n const lockState: LockState = {\n pid: process.pid,\n token,\n createdAt: new Date().toISOString(),\n };\n tempPath = `${this.lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.acquire`;\n await fs.writeFile(tempPath, JSON.stringify(lockState), 'utf-8');\n await fs.link(tempPath, this.lockPath);\n this.registerLockCleanup(token);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ProcessRegistryError(\n `Failed to acquire registry lock: ${error instanceof Error ? error.message : String(error)}`,\n 'REGISTRY_LOCK_FAILED',\n { cause: error },\n );\n }\n\n const stale = await this.isStaleLock();\n if (stale) {\n await fs.unlink(this.lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await this.delay(LOCK_RETRY_DELAY_MS);\n } finally {\n if (tempPath) {\n await fs.unlink(tempPath).catch(() => undefined);\n }\n }\n }\n\n throw new ProcessRegistryError('Unable to acquire registry lock (timeout)', 'REGISTRY_LOCK_FAILED');\n }\n\n private async releaseLock(token: string): Promise<void> {\n try {\n const existing = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token: string };\n if (parsed.token === token) {\n await fs.unlink(this.lockPath);\n }\n } catch {\n // ignore\n } finally {\n this.unregisterLockCleanup(token);\n }\n }\n\n private async isStaleLock(): Promise<boolean> {\n try {\n const content = await fs.readFile(this.lockPath, 'utf-8');\n const parsed = JSON.parse(content) as LockState;\n\n if (parsed.pid) {\n const pidAlive = this.isProcessRunning(parsed.pid);\n if (pidAlive) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < LOCK_STALE_AFTER_MS);\n }\n }\n\n return true;\n } catch {\n return true;\n }\n }\n\n private isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n private registerLockCleanup(token: string): void {\n this.unregisterLockCleanup(this.activeLockToken);\n this.activeLockToken = token;\n\n const cleanup = (): void => {\n this.releaseLockSync(token);\n };\n\n for (const event of [\n 'exit',\n 'SIGINT',\n 'SIGTERM',\n 'uncaughtException',\n 'unhandledRejection',\n ] satisfies LockCleanupEvent[]) {\n process.once(event, cleanup);\n this.lockCleanupHandlers.set(event, cleanup);\n }\n }\n\n private unregisterLockCleanup(token: string | undefined): void {\n if (!token || this.activeLockToken !== token) {\n return;\n }\n\n for (const [event, handler] of this.lockCleanupHandlers) {\n process.off(event, handler);\n }\n\n this.lockCleanupHandlers.clear();\n this.activeLockToken = undefined;\n }\n\n private releaseLockSync(token: string): void {\n try {\n const existing = fsSync.readFileSync(this.lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(this.lockPath);\n }\n } catch {\n // ignore best-effort cleanup\n }\n }\n\n private async terminateProcess(pid: number): Promise<void> {\n try {\n process.kill(pid, 0);\n } catch {\n return;\n }\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGTERM to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(500);\n\n if (!this.isProcessRunning(pid)) {\n return;\n }\n\n try {\n process.kill(pid, 'SIGKILL');\n } catch (error) {\n const sysError = error as NodeJS.ErrnoException;\n if (sysError.code === 'ESRCH') {\n return;\n }\n\n throw new Error(\n `Failed to send SIGKILL to process ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n );\n }\n\n await this.delay(250);\n\n if (this.isProcessRunning(pid)) {\n throw new Error(`Process ${pid} did not exit after SIGKILL`);\n }\n }\n\n private async releaseAssociatedPort(entry: ProcessRegistryRecord): Promise<void> {\n if (!this.portRegistry || !entry.port) {\n return;\n }\n\n try {\n const result = await this.portRegistry.releasePort({\n repositoryPath: entry.repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n pid: entry.pid,\n force: true,\n });\n\n if (!result.success && result.error && !result.error.includes('No matching registry entry')) {\n throw new Error(result.error);\n }\n } catch {\n // Best-effort cleanup: port release failures should not block process cleanup.\n }\n }\n\n private entryKey(entry: ProcessRegistryRecord): string {\n return [\n entry.repositoryPath,\n entry.serviceName,\n entry.serviceType,\n entry.environment ?? '',\n String(entry.pid),\n ].join('|');\n }\n\n private normalizeListFilters(filters: ListProcessFilters): NormalizedListFilters {\n return {\n ...(filters.repositoryPath ? { repositoryPath: normalizeRepositoryPath(filters.repositoryPath) } : {}),\n ...(filters.serviceName ? { serviceName: filters.serviceName } : {}),\n ...(filters.serviceType ? { serviceType: filters.serviceType } : {}),\n ...(filters.environment ? { environment: filters.environment } : {}),\n ...(filters.tags ? { tags: normalizeTags(filters.tags) } : {}),\n };\n }\n\n private normalizeReleaseFilters(filters: ReleaseProcessesRequest): NormalizedReleaseFilters {\n return {\n ...this.normalizeListFilters(filters),\n ...(typeof filters.pid === 'number' ? { pid: filters.pid } : {}),\n ...(typeof filters.force === 'boolean' ? { force: filters.force } : {}),\n };\n }\n\n private matchesListFilters(entry: ProcessRegistryRecord, filters: NormalizedListFilters): boolean {\n if (filters.repositoryPath && entry.repositoryPath !== filters.repositoryPath) return false;\n if (filters.serviceType && entry.serviceType !== filters.serviceType) return false;\n if (filters.serviceName && entry.serviceName !== filters.serviceName) return false;\n if (filters.environment && entry.environment !== filters.environment) return false;\n if (filters.tags && filters.tags.length > 0 && !this.matchesTags(entry.tags, filters.tags)) return false;\n return true;\n }\n\n private matchesReleaseFilters(entry: ProcessRegistryRecord, filters: NormalizedReleaseFilters): boolean {\n if (!this.matchesListFilters(entry, filters)) {\n return false;\n }\n\n return typeof filters.pid !== 'number' || filters.force || entry.pid === filters.pid;\n }\n\n private findEntry(state: ProcessRegistryState, filters: NormalizedFilters): ProcessRegistryRecord | undefined {\n return state.entries.find(\n (entry) =>\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (filters.environment ? entry.environment === filters.environment : true),\n );\n }\n\n private removeMatchingEntries(state: ProcessRegistryState, filters: NormalizedFilters): void {\n state.entries = state.entries.filter(\n (entry) =>\n !(\n entry.repositoryPath === filters.repositoryPath &&\n entry.serviceName === filters.serviceName &&\n entry.serviceType === filters.serviceType &&\n (!filters.environment || entry.environment === filters.environment)\n ),\n );\n }\n\n private createSuccessResponse(pid?: number, record?: ProcessRegistryRecord): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: true,\n ...(typeof pid === 'number' ? { pid } : {}),\n ...(record ? { record } : {}),\n });\n }\n\n private createFailureResponse(error: string): ProcessRegistryResponse {\n return ProcessRegistryResponseSchema.parse({\n success: false,\n error,\n });\n }\n\n private matchesTags(entryTags: string[] | undefined, requestedTags: string[]): boolean {\n if (requestedTags.length === 0) {\n return true;\n }\n\n if (!entryTags || entryTags.length === 0) {\n return false;\n }\n\n return requestedTags.some((tag) => entryTags.includes(tag));\n }\n\n private async pathExists(candidate: string): Promise<boolean> {\n try {\n await fs.access(candidate);\n return true;\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nfunction normalizeTags(tags: string[] | undefined): string[] | undefined {\n if (tags === undefined) {\n return undefined;\n }\n\n return Array.from(new Set(tags.map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n}\n"],"mappings":"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"}
|
package/dist/cli.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./ProcessRegistryService.cjs`);let t=require(`node:path`);function n(e,t,n){if(e.includes(`=`)){let t=e.indexOf(`=`),r=t>=0?e.slice(t+1):``;if(r.length===0)throw Error(`Missing value for ${n}`);return[r,0]}if(!t||t.startsWith(`--`))throw Error(`Missing value for ${n}`);return[t,1]}function
|
|
3
|
-
`)}
|
|
2
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./ProcessRegistryService.cjs`);let t=require(`node:path`);function n(e){process.stdout.write(`${e}\n`)}function r(e){process.stderr.write(`${e}\n`)}function i(e,t,n){if(e.includes(`=`)){let t=e.indexOf(`=`),r=t>=0?e.slice(t+1):``;if(r.length===0)throw Error(`Missing value for ${n}`);return[r,0]}if(!t||t.startsWith(`--`))throw Error(`Missing value for ${n}`);return[t,1]}function a(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`--repository-path`){let[a,o]=i(r,e[n+1],`--repository-path`);t.repositoryPath=a,n+=o;continue}if(r.startsWith(`--repository-path=`)){let[e]=i(r,void 0,`--repository-path`);t.repositoryPath=e;continue}if(r===`--service-name`){let[a,o]=i(r,e[n+1],`--service-name`);t.serviceName=a,n+=o;continue}if(r.startsWith(`--service-name=`)){let[e]=i(r,void 0,`--service-name`);t.serviceName=e;continue}if(r===`--tag`){let[a,o]=i(r,e[n+1],`--tag`);t.tags=[...t.tags??[],a],n+=o;continue}if(r.startsWith(`--tag=`)){let[e]=i(r,void 0,`--tag`);t.tags=[...t.tags??[],e];continue}throw r===`--help`||r===`-h`?Error(`help`):r.startsWith(`--`)?Error(`Unknown option: ${r}`):Error(`Unknown argument: ${r}`)}return t}function o(){let e=(0,t.basename)(process.argv[1]||`process-registry`);return[`Usage: ${e} release-process [options]`,``,`Options:`,` --repository-path <path> Worktree root to clean up (default: process.cwd())`,` --service-name <name> Service name to clean up (default: all services in worktree)`,` --tag <tag> Only clean up registrations carrying the given tag (repeatable)`,``,`Examples:`,` ${e} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,` ${e} release-process --repository-path /path/to/worktree --tag api`,` ${e} release-process --service-name mcp-proxy-http`,` ${e} release-process --repository-path /path/to/worktree`].join(`
|
|
3
|
+
`)}async function s(t){let i=e.S(t.repositoryPath??process.cwd()),a=t.serviceName?.trim(),o=Array.from(new Set((t.tags??[]).map(e=>e.trim()).filter(e=>e.length>0))),s=new e.t(process.env.PROCESS_REGISTRY_PATH),c={repositoryPath:i,...a?{serviceName:a}:{},...o.length>0?{tags:o}:{}};await s.cleanupStaleProcesses(c);let l=await s.releaseProcesses({...c,kill:!0,releasePort:!0});if(!l.success){let e=[a?`service ${a}`:``,o.length>0?`tag ${o.join(`, `)}`:``].filter(Boolean).join(` and `);l.released>0&&(r(`Failed to release ${l.released} process registration(s) for ${e?`${e} `:``}in worktree ${i}: ${l.error??`Unknown error`}`),process.exit(1)),r(`No matching process registrations found for ${e?`${e} `:``}in worktree ${i}`),process.exit(1)}let u=[a?`service ${a}`:``,o.length>0?`tag ${o.join(`, `)}`:``].filter(Boolean).join(` and `);if(u){n(`Released ${l.released} process registration(s) for ${u} in ${i}`);return}n(`Released ${l.released} process registration(s) for worktree ${i}`)}async function c(){let[e=``,...t]=process.argv.slice(2);if(e===`-h`||e===`--help`||e===`help`){n(o());return}e!==`release-process`&&(r(`Unknown command: ${e||`(none)`}`),r(o()),process.exit(1));try{await s(a(t))}catch(e){if(e instanceof Error&&e.message===`help`){n(o());return}r(e instanceof Error?e.message:`Failed to execute command`),process.exit(1)}}process.argv[1]&&/[\\/](cli\.(ts|mjs|cjs))$/.test(process.argv[1])&&c(),exports.parseReleaseProcessArgs=a,exports.runReleaseProcess=s;
|
|
4
4
|
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":["normalizeRepositoryPath","ProcessRegistryService"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { basename } from 'node:path';\nimport { ProcessRegistryService } from './services';\nimport { normalizeRepositoryPath } from './utils';\nimport { type ProcessRegistryRecord } from './types';\n\ntype ReleaseProcessCommandOptions = {\n repositoryPath?: string;\n serviceName?: string;\n tags?: string[];\n};\n\nfunction parseValueFromArg(arg: string, nextValue: string | undefined, optionName: string): [string, number] {\n if (arg.includes('=')) {\n const equalsIndex = arg.indexOf('=');\n const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : '';\n if (value.length === 0) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [value, 0];\n }\n\n if (!nextValue || nextValue.startsWith('--')) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [nextValue, 1];\n}\n\nfunction parseReleaseProcessArgs(argv: string[]): ReleaseProcessCommandOptions {\n const parsed: ReleaseProcessCommandOptions = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (arg === '--repository-path') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--repository-path');\n parsed.repositoryPath = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--repository-path=')) {\n const [value] = parseValueFromArg(arg, undefined, '--repository-path');\n parsed.repositoryPath = value;\n continue;\n }\n\n if (arg === '--service-name') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--service-name');\n parsed.serviceName = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--service-name=')) {\n const [value] = parseValueFromArg(arg, undefined, '--service-name');\n parsed.serviceName = value;\n continue;\n }\n\n if (arg === '--tag') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--tag=')) {\n const [value] = parseValueFromArg(arg, undefined, '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n continue;\n }\n\n if (arg === '--help' || arg === '-h') {\n throw new Error('help');\n }\n\n if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n }\n\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n return parsed;\n}\n\nfunction formatUsage() {\n const command = basename(process.argv[1] || 'process-registry');\n\n return [\n `Usage: ${command} release-process [options]`,\n '',\n 'Options:',\n ' --repository-path <path> Worktree root to clean up (default: process.cwd())',\n ' --service-name <name> Service name to clean up (default: all services in worktree)',\n ' --tag <tag> Only clean up registrations carrying the given tag (repeatable)',\n '',\n 'Examples:',\n ` ${command} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree --tag api`,\n ` ${command} release-process --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree`,\n ].join('\\n');\n}\n\nfunction dedupeRecords(records: ProcessRegistryRecord[]): ProcessRegistryRecord[] {\n const byKey = new Map<string, ProcessRegistryRecord>();\n\n for (const record of records) {\n const key = [\n record.repositoryPath,\n record.serviceName,\n record.serviceType,\n record.environment ?? '',\n String(record.pid ?? ''),\n ].join('|');\n if (!byKey.has(key)) {\n byKey.set(key, record);\n }\n }\n\n return [...byKey.values()];\n}\n\nasync function runReleaseProcess(options: ReleaseProcessCommandOptions): Promise<void> {\n const repositoryPath = normalizeRepositoryPath(options.repositoryPath ?? process.cwd());\n const serviceName = options.serviceName?.trim();\n const tags = Array.from(new Set((options.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n const service = new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);\n const releaseFilters = {\n repositoryPath,\n ...(serviceName ? { serviceName } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n };\n\n const matches = await service.listProcesses(releaseFilters);\n\n if (matches.length === 0) {\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n console.error(\n `No matching process registrations found for ${target ? `${target} ` : ''}in worktree ${repositoryPath}`,\n );\n process.exit(1);\n }\n\n const releases = dedupeRecords(matches);\n const releaseErrors: string[] = [];\n\n for (const entry of releases) {\n const response = await service.releaseProcess({\n repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n ...(entry.pid ? { pid: entry.pid } : {}),\n kill: true,\n releasePort: true,\n });\n\n if (!response.success) {\n releaseErrors.push(`Service ${entry.serviceName}: ${response.error ?? 'Unknown error'}`);\n }\n }\n\n if (releaseErrors.length > 0) {\n console.error(`Failed to release ${releaseErrors.length} entr${releaseErrors.length === 1 ? 'y' : 'ies'}:`);\n for (const failure of releaseErrors) {\n console.error(`- ${failure}`);\n }\n process.exit(1);\n }\n\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n\n if (target) {\n console.log(`Released ${releases.length} process registration(s) for ${target} in ${repositoryPath}`);\n return;\n }\n\n console.log(`Released ${releases.length} process registration(s) for worktree ${repositoryPath}`);\n}\n\nasync function main() {\n const [command = '', ...rawArgs] = process.argv.slice(2);\n\n if (command === '-h' || command === '--help' || command === 'help') {\n console.log(formatUsage());\n return;\n }\n\n if (command !== 'release-process') {\n console.error(`Unknown command: ${command || '(none)'}`);\n console.error(formatUsage());\n process.exit(1);\n }\n\n try {\n const options = parseReleaseProcessArgs(rawArgs);\n await runReleaseProcess(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'help') {\n console.log(formatUsage());\n return;\n }\n\n console.error(error instanceof Error ? error.message : 'Failed to execute command');\n process.exit(1);\n }\n}\n\nif (process.argv[1] && /[\\\\/](cli\\.(ts|mjs|cjs))$/.test(process.argv[1])) {\n void main();\n}\n\nexport { parseReleaseProcessArgs, runReleaseProcess };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["normalizeRepositoryPath","ProcessRegistryService"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { basename } from 'node:path';\nimport { ProcessRegistryService } from './services';\nimport { normalizeRepositoryPath } from './utils';\n\ntype ReleaseProcessCommandOptions = {\n repositoryPath?: string;\n serviceName?: string;\n tags?: string[];\n};\n\nfunction writeStdout(message: string): void {\n process.stdout.write(`${message}\\n`);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n\nfunction parseValueFromArg(arg: string, nextValue: string | undefined, optionName: string): [string, number] {\n if (arg.includes('=')) {\n const equalsIndex = arg.indexOf('=');\n const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : '';\n if (value.length === 0) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [value, 0];\n }\n\n if (!nextValue || nextValue.startsWith('--')) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [nextValue, 1];\n}\n\nfunction parseReleaseProcessArgs(argv: string[]): ReleaseProcessCommandOptions {\n const parsed: ReleaseProcessCommandOptions = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (arg === '--repository-path') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--repository-path');\n parsed.repositoryPath = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--repository-path=')) {\n const [value] = parseValueFromArg(arg, undefined, '--repository-path');\n parsed.repositoryPath = value;\n continue;\n }\n\n if (arg === '--service-name') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--service-name');\n parsed.serviceName = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--service-name=')) {\n const [value] = parseValueFromArg(arg, undefined, '--service-name');\n parsed.serviceName = value;\n continue;\n }\n\n if (arg === '--tag') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--tag=')) {\n const [value] = parseValueFromArg(arg, undefined, '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n continue;\n }\n\n if (arg === '--help' || arg === '-h') {\n throw new Error('help');\n }\n\n if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n }\n\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n return parsed;\n}\n\nfunction formatUsage() {\n const command = basename(process.argv[1] || 'process-registry');\n\n return [\n `Usage: ${command} release-process [options]`,\n '',\n 'Options:',\n ' --repository-path <path> Worktree root to clean up (default: process.cwd())',\n ' --service-name <name> Service name to clean up (default: all services in worktree)',\n ' --tag <tag> Only clean up registrations carrying the given tag (repeatable)',\n '',\n 'Examples:',\n ` ${command} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree --tag api`,\n ` ${command} release-process --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree`,\n ].join('\\n');\n}\n\nasync function runReleaseProcess(options: ReleaseProcessCommandOptions): Promise<void> {\n const repositoryPath = normalizeRepositoryPath(options.repositoryPath ?? process.cwd());\n const serviceName = options.serviceName?.trim();\n const tags = Array.from(new Set((options.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n const service = new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);\n const releaseFilters = {\n repositoryPath,\n ...(serviceName ? { serviceName } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n };\n\n await service.cleanupStaleProcesses(releaseFilters);\n\n const releaseResult = await service.releaseProcesses({\n ...releaseFilters,\n kill: true,\n releasePort: true,\n });\n\n if (!releaseResult.success) {\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n if (releaseResult.released > 0) {\n writeStderr(\n `Failed to release ${releaseResult.released} process registration(s) for ${\n target ? `${target} ` : ''\n }in worktree ${repositoryPath}: ${releaseResult.error ?? 'Unknown error'}`,\n );\n process.exit(1);\n }\n\n writeStderr(\n `No matching process registrations found for ${target ? `${target} ` : ''}in worktree ${repositoryPath}`,\n );\n process.exit(1);\n }\n\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n\n if (target) {\n writeStdout(`Released ${releaseResult.released} process registration(s) for ${target} in ${repositoryPath}`);\n return;\n }\n\n writeStdout(`Released ${releaseResult.released} process registration(s) for worktree ${repositoryPath}`);\n}\n\nasync function main() {\n const [command = '', ...rawArgs] = process.argv.slice(2);\n\n if (command === '-h' || command === '--help' || command === 'help') {\n writeStdout(formatUsage());\n return;\n }\n\n if (command !== 'release-process') {\n writeStderr(`Unknown command: ${command || '(none)'}`);\n writeStderr(formatUsage());\n process.exit(1);\n }\n\n try {\n const options = parseReleaseProcessArgs(rawArgs);\n await runReleaseProcess(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'help') {\n writeStdout(formatUsage());\n return;\n }\n\n writeStderr(error instanceof Error ? error.message : 'Failed to execute command');\n process.exit(1);\n }\n}\n\nif (process.argv[1] && /[\\\\/](cli\\.(ts|mjs|cjs))$/.test(process.argv[1])) {\n void main();\n}\n\nexport { parseReleaseProcessArgs, runReleaseProcess };\n"],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
3
|
-
`)}
|
|
2
|
+
import{S as e,t}from"./ProcessRegistryService.mjs";import{basename as n}from"node:path";function r(e){process.stdout.write(`${e}\n`)}function i(e){process.stderr.write(`${e}\n`)}function a(e,t,n){if(e.includes(`=`)){let t=e.indexOf(`=`),r=t>=0?e.slice(t+1):``;if(r.length===0)throw Error(`Missing value for ${n}`);return[r,0]}if(!t||t.startsWith(`--`))throw Error(`Missing value for ${n}`);return[t,1]}function o(e){let t={};for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`--repository-path`){let[i,o]=a(r,e[n+1],`--repository-path`);t.repositoryPath=i,n+=o;continue}if(r.startsWith(`--repository-path=`)){let[e]=a(r,void 0,`--repository-path`);t.repositoryPath=e;continue}if(r===`--service-name`){let[i,o]=a(r,e[n+1],`--service-name`);t.serviceName=i,n+=o;continue}if(r.startsWith(`--service-name=`)){let[e]=a(r,void 0,`--service-name`);t.serviceName=e;continue}if(r===`--tag`){let[i,o]=a(r,e[n+1],`--tag`);t.tags=[...t.tags??[],i],n+=o;continue}if(r.startsWith(`--tag=`)){let[e]=a(r,void 0,`--tag`);t.tags=[...t.tags??[],e];continue}throw r===`--help`||r===`-h`?Error(`help`):r.startsWith(`--`)?Error(`Unknown option: ${r}`):Error(`Unknown argument: ${r}`)}return t}function s(){let e=n(process.argv[1]||`process-registry`);return[`Usage: ${e} release-process [options]`,``,`Options:`,` --repository-path <path> Worktree root to clean up (default: process.cwd())`,` --service-name <name> Service name to clean up (default: all services in worktree)`,` --tag <tag> Only clean up registrations carrying the given tag (repeatable)`,``,`Examples:`,` ${e} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,` ${e} release-process --repository-path /path/to/worktree --tag api`,` ${e} release-process --service-name mcp-proxy-http`,` ${e} release-process --repository-path /path/to/worktree`].join(`
|
|
3
|
+
`)}async function c(n){let a=e(n.repositoryPath??process.cwd()),o=n.serviceName?.trim(),s=Array.from(new Set((n.tags??[]).map(e=>e.trim()).filter(e=>e.length>0))),c=new t(process.env.PROCESS_REGISTRY_PATH),l={repositoryPath:a,...o?{serviceName:o}:{},...s.length>0?{tags:s}:{}};await c.cleanupStaleProcesses(l);let u=await c.releaseProcesses({...l,kill:!0,releasePort:!0});if(!u.success){let e=[o?`service ${o}`:``,s.length>0?`tag ${s.join(`, `)}`:``].filter(Boolean).join(` and `);u.released>0&&(i(`Failed to release ${u.released} process registration(s) for ${e?`${e} `:``}in worktree ${a}: ${u.error??`Unknown error`}`),process.exit(1)),i(`No matching process registrations found for ${e?`${e} `:``}in worktree ${a}`),process.exit(1)}let d=[o?`service ${o}`:``,s.length>0?`tag ${s.join(`, `)}`:``].filter(Boolean).join(` and `);if(d){r(`Released ${u.released} process registration(s) for ${d} in ${a}`);return}r(`Released ${u.released} process registration(s) for worktree ${a}`)}async function l(){let[e=``,...t]=process.argv.slice(2);if(e===`-h`||e===`--help`||e===`help`){r(s());return}e!==`release-process`&&(i(`Unknown command: ${e||`(none)`}`),i(s()),process.exit(1));try{await c(o(t))}catch(e){if(e instanceof Error&&e.message===`help`){r(s());return}i(e instanceof Error?e.message:`Failed to execute command`),process.exit(1)}}process.argv[1]&&/[\\/](cli\.(ts|mjs|cjs))$/.test(process.argv[1])&&l();export{o as parseReleaseProcessArgs,c as runReleaseProcess};
|
|
4
4
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { basename } from 'node:path';\nimport { ProcessRegistryService } from './services';\nimport { normalizeRepositoryPath } from './utils';\nimport { type ProcessRegistryRecord } from './types';\n\ntype ReleaseProcessCommandOptions = {\n repositoryPath?: string;\n serviceName?: string;\n tags?: string[];\n};\n\nfunction parseValueFromArg(arg: string, nextValue: string | undefined, optionName: string): [string, number] {\n if (arg.includes('=')) {\n const equalsIndex = arg.indexOf('=');\n const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : '';\n if (value.length === 0) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [value, 0];\n }\n\n if (!nextValue || nextValue.startsWith('--')) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [nextValue, 1];\n}\n\nfunction parseReleaseProcessArgs(argv: string[]): ReleaseProcessCommandOptions {\n const parsed: ReleaseProcessCommandOptions = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (arg === '--repository-path') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--repository-path');\n parsed.repositoryPath = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--repository-path=')) {\n const [value] = parseValueFromArg(arg, undefined, '--repository-path');\n parsed.repositoryPath = value;\n continue;\n }\n\n if (arg === '--service-name') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--service-name');\n parsed.serviceName = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--service-name=')) {\n const [value] = parseValueFromArg(arg, undefined, '--service-name');\n parsed.serviceName = value;\n continue;\n }\n\n if (arg === '--tag') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--tag=')) {\n const [value] = parseValueFromArg(arg, undefined, '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n continue;\n }\n\n if (arg === '--help' || arg === '-h') {\n throw new Error('help');\n }\n\n if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n }\n\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n return parsed;\n}\n\nfunction formatUsage() {\n const command = basename(process.argv[1] || 'process-registry');\n\n return [\n `Usage: ${command} release-process [options]`,\n '',\n 'Options:',\n ' --repository-path <path> Worktree root to clean up (default: process.cwd())',\n ' --service-name <name> Service name to clean up (default: all services in worktree)',\n ' --tag <tag> Only clean up registrations carrying the given tag (repeatable)',\n '',\n 'Examples:',\n ` ${command} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree --tag api`,\n ` ${command} release-process --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree`,\n ].join('\\n');\n}\n\nfunction dedupeRecords(records: ProcessRegistryRecord[]): ProcessRegistryRecord[] {\n const byKey = new Map<string, ProcessRegistryRecord>();\n\n for (const record of records) {\n const key = [\n record.repositoryPath,\n record.serviceName,\n record.serviceType,\n record.environment ?? '',\n String(record.pid ?? ''),\n ].join('|');\n if (!byKey.has(key)) {\n byKey.set(key, record);\n }\n }\n\n return [...byKey.values()];\n}\n\nasync function runReleaseProcess(options: ReleaseProcessCommandOptions): Promise<void> {\n const repositoryPath = normalizeRepositoryPath(options.repositoryPath ?? process.cwd());\n const serviceName = options.serviceName?.trim();\n const tags = Array.from(new Set((options.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n const service = new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);\n const releaseFilters = {\n repositoryPath,\n ...(serviceName ? { serviceName } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n };\n\n const matches = await service.listProcesses(releaseFilters);\n\n if (matches.length === 0) {\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n console.error(\n `No matching process registrations found for ${target ? `${target} ` : ''}in worktree ${repositoryPath}`,\n );\n process.exit(1);\n }\n\n const releases = dedupeRecords(matches);\n const releaseErrors: string[] = [];\n\n for (const entry of releases) {\n const response = await service.releaseProcess({\n repositoryPath,\n serviceName: entry.serviceName,\n serviceType: entry.serviceType,\n environment: entry.environment,\n ...(entry.pid ? { pid: entry.pid } : {}),\n kill: true,\n releasePort: true,\n });\n\n if (!response.success) {\n releaseErrors.push(`Service ${entry.serviceName}: ${response.error ?? 'Unknown error'}`);\n }\n }\n\n if (releaseErrors.length > 0) {\n console.error(`Failed to release ${releaseErrors.length} entr${releaseErrors.length === 1 ? 'y' : 'ies'}:`);\n for (const failure of releaseErrors) {\n console.error(`- ${failure}`);\n }\n process.exit(1);\n }\n\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n\n if (target) {\n console.log(`Released ${releases.length} process registration(s) for ${target} in ${repositoryPath}`);\n return;\n }\n\n console.log(`Released ${releases.length} process registration(s) for worktree ${repositoryPath}`);\n}\n\nasync function main() {\n const [command = '', ...rawArgs] = process.argv.slice(2);\n\n if (command === '-h' || command === '--help' || command === 'help') {\n console.log(formatUsage());\n return;\n }\n\n if (command !== 'release-process') {\n console.error(`Unknown command: ${command || '(none)'}`);\n console.error(formatUsage());\n process.exit(1);\n }\n\n try {\n const options = parseReleaseProcessArgs(rawArgs);\n await runReleaseProcess(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'help') {\n console.log(formatUsage());\n return;\n }\n\n console.error(error instanceof Error ? error.message : 'Failed to execute command');\n process.exit(1);\n }\n}\n\nif (process.argv[1] && /[\\\\/](cli\\.(ts|mjs|cjs))$/.test(process.argv[1])) {\n void main();\n}\n\nexport { parseReleaseProcessArgs, runReleaseProcess };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { basename } from 'node:path';\nimport { ProcessRegistryService } from './services';\nimport { normalizeRepositoryPath } from './utils';\n\ntype ReleaseProcessCommandOptions = {\n repositoryPath?: string;\n serviceName?: string;\n tags?: string[];\n};\n\nfunction writeStdout(message: string): void {\n process.stdout.write(`${message}\\n`);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n\nfunction parseValueFromArg(arg: string, nextValue: string | undefined, optionName: string): [string, number] {\n if (arg.includes('=')) {\n const equalsIndex = arg.indexOf('=');\n const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : '';\n if (value.length === 0) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [value, 0];\n }\n\n if (!nextValue || nextValue.startsWith('--')) {\n throw new Error(`Missing value for ${optionName}`);\n }\n\n return [nextValue, 1];\n}\n\nfunction parseReleaseProcessArgs(argv: string[]): ReleaseProcessCommandOptions {\n const parsed: ReleaseProcessCommandOptions = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index];\n if (arg === '--repository-path') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--repository-path');\n parsed.repositoryPath = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--repository-path=')) {\n const [value] = parseValueFromArg(arg, undefined, '--repository-path');\n parsed.repositoryPath = value;\n continue;\n }\n\n if (arg === '--service-name') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--service-name');\n parsed.serviceName = value;\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--service-name=')) {\n const [value] = parseValueFromArg(arg, undefined, '--service-name');\n parsed.serviceName = value;\n continue;\n }\n\n if (arg === '--tag') {\n const [value, consumed] = parseValueFromArg(arg, argv[index + 1], '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n index += consumed;\n continue;\n }\n\n if (arg.startsWith('--tag=')) {\n const [value] = parseValueFromArg(arg, undefined, '--tag');\n parsed.tags = [...(parsed.tags ?? []), value];\n continue;\n }\n\n if (arg === '--help' || arg === '-h') {\n throw new Error('help');\n }\n\n if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n }\n\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n return parsed;\n}\n\nfunction formatUsage() {\n const command = basename(process.argv[1] || 'process-registry');\n\n return [\n `Usage: ${command} release-process [options]`,\n '',\n 'Options:',\n ' --repository-path <path> Worktree root to clean up (default: process.cwd())',\n ' --service-name <name> Service name to clean up (default: all services in worktree)',\n ' --tag <tag> Only clean up registrations carrying the given tag (repeatable)',\n '',\n 'Examples:',\n ` ${command} release-process --repository-path /path/to/worktree --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree --tag api`,\n ` ${command} release-process --service-name mcp-proxy-http`,\n ` ${command} release-process --repository-path /path/to/worktree`,\n ].join('\\n');\n}\n\nasync function runReleaseProcess(options: ReleaseProcessCommandOptions): Promise<void> {\n const repositoryPath = normalizeRepositoryPath(options.repositoryPath ?? process.cwd());\n const serviceName = options.serviceName?.trim();\n const tags = Array.from(new Set((options.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0)));\n const service = new ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);\n const releaseFilters = {\n repositoryPath,\n ...(serviceName ? { serviceName } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n };\n\n await service.cleanupStaleProcesses(releaseFilters);\n\n const releaseResult = await service.releaseProcesses({\n ...releaseFilters,\n kill: true,\n releasePort: true,\n });\n\n if (!releaseResult.success) {\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n if (releaseResult.released > 0) {\n writeStderr(\n `Failed to release ${releaseResult.released} process registration(s) for ${\n target ? `${target} ` : ''\n }in worktree ${repositoryPath}: ${releaseResult.error ?? 'Unknown error'}`,\n );\n process.exit(1);\n }\n\n writeStderr(\n `No matching process registrations found for ${target ? `${target} ` : ''}in worktree ${repositoryPath}`,\n );\n process.exit(1);\n }\n\n const target = [serviceName ? `service ${serviceName}` : '', tags.length > 0 ? `tag ${tags.join(', ')}` : '']\n .filter(Boolean)\n .join(' and ');\n\n if (target) {\n writeStdout(`Released ${releaseResult.released} process registration(s) for ${target} in ${repositoryPath}`);\n return;\n }\n\n writeStdout(`Released ${releaseResult.released} process registration(s) for worktree ${repositoryPath}`);\n}\n\nasync function main() {\n const [command = '', ...rawArgs] = process.argv.slice(2);\n\n if (command === '-h' || command === '--help' || command === 'help') {\n writeStdout(formatUsage());\n return;\n }\n\n if (command !== 'release-process') {\n writeStderr(`Unknown command: ${command || '(none)'}`);\n writeStderr(formatUsage());\n process.exit(1);\n }\n\n try {\n const options = parseReleaseProcessArgs(rawArgs);\n await runReleaseProcess(options);\n } catch (error) {\n if (error instanceof Error && error.message === 'help') {\n writeStdout(formatUsage());\n return;\n }\n\n writeStderr(error instanceof Error ? error.message : 'Failed to execute command');\n process.exit(1);\n }\n}\n\nif (process.argv[1] && /[\\\\/](cli\\.(ts|mjs|cjs))$/.test(process.argv[1])) {\n void main();\n}\n\nexport { parseReleaseProcessArgs, runReleaseProcess };\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./ProcessRegistryService.cjs`);let t=require(`node:crypto`),n=require(`node:fs`);n=e.
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./ProcessRegistryService.cjs`);let t=require(`node:crypto`),n=require(`node:fs`);n=e.T(n,1);let r=require(`node:fs/promises`);r=e.T(r,1);let i=require(`node:path`);i=e.T(i,1);const a=[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`],o=new Map,s=new Map;function c(e){try{return process.kill(e,0),!0}catch{return!1}}function l(e){return new Promise(t=>setTimeout(t,e))}function u(e,t){try{let r=n.default.readFileSync(e,`utf-8`);JSON.parse(r).token===t&&n.default.unlinkSync(e)}catch{}}function d(){if(s.size>0)return;let e=()=>{for(let[e,t]of o)u(e,t);o.clear()};for(let t of a)process.once(t,e),s.set(t,e)}function f(){if(!(o.size>0)){for(let[e,t]of s)process.off(e,t);s.clear()}}async function p(e,t){try{let n=await r.default.readFile(e,`utf-8`),i=JSON.parse(n);if(i.pid&&c(i.pid)){let e=Date.now()-new Date(i.createdAt).getTime();return!(Number.isFinite(e)&&e<t)}return!0}catch{return!0}}async function m(e,n,a){await r.default.mkdir(i.default.dirname(e),{recursive:!0});for(let i=0;i<a.maxRetries;i+=1){let s={pid:process.pid,token:n,createdAt:new Date().toISOString()},c=`${e}.${process.pid}.${(0,t.randomBytes)(4).toString(`hex`)}.acquire`;try{await r.default.writeFile(c,JSON.stringify(s),`utf-8`),await r.default.link(c,e),o.set(e,n),d();return}catch(t){if(t.code!==`EEXIST`)throw Error(`Failed to acquire file lock '${e}': ${t instanceof Error?t.message:String(t)}`,{cause:t});if(await p(e,a.staleAfterMs)){await r.default.unlink(e).catch(()=>void 0),--i;continue}await l(a.retryDelayMs)}finally{await r.default.unlink(c).catch(()=>void 0)}}throw Error(`Unable to acquire file lock '${e}' (timeout)`)}async function h(e,n,i){let a=`${e}.${process.pid}.${(0,t.randomBytes)(4).toString(`hex`)}.tmp`;try{let t=await r.default.readFile(e,`utf-8`);if(JSON.parse(t).token!==n||i())return;let o={pid:process.pid,token:n,createdAt:new Date().toISOString()};if(await r.default.writeFile(a,JSON.stringify(o),`utf-8`),i())return;await r.default.rename(a,e)}catch{}finally{await r.default.unlink(a).catch(()=>void 0)}}async function g(e,t){o.delete(e);try{let n=await r.default.readFile(e,`utf-8`);JSON.parse(n).token===t&&await r.default.unlink(e)}catch{}finally{f()}}async function _(e,n,r={}){let i=r.retryDelayMs??75,a=r.maxRetries??80,o=r.staleAfterMs??5e3,s=r.heartbeatIntervalMs??Math.max(500,Math.floor(o/3)),c=`${process.pid}-${(0,t.randomBytes)(6).toString(`hex`)}`;await m(e,c,{retryDelayMs:i,maxRetries:a,staleAfterMs:o});let l=!1,u=null,d=setInterval(()=>{l||u||(u=h(e,c,()=>l).finally(()=>{u=null}))},s);d.unref?.();try{return await n()}finally{l=!0,clearInterval(d),u&&await u,await g(e,c)}}function v(t){return new e.t(t??process.env.PROCESS_REGISTRY_PATH)}async function y(t,n){let r=n??v(),i=t.pid??process.pid,a=t.serviceType??`tool`,o=t.environment??process.env.NODE_ENV??`development`,s={repositoryPath:t.repositoryPath,serviceName:t.serviceName,serviceType:a,environment:o,pid:i,port:t.port,host:t.host,command:t.command,args:t.args,tags:e.C(t.tags),metadata:t.metadata,force:t.force??!0},c=await r.registerProcess(s);if(!c.success||!c.record)throw Error(c.error||`Failed to register process for ${t.serviceName}`);let l=!1;return{release:async e=>{if(l)return;l=!0;let n=await r.releaseProcess({repositoryPath:t.repositoryPath,serviceName:t.serviceName,serviceType:a,pid:i,environment:o,kill:e?.kill??!0,releasePort:e?.releasePort??!0});if(!n.success&&n.error&&!n.error.includes(`No matching process entry`))throw Error(n.error||`Failed to release process for ${t.serviceName}`)}}}exports.DEFAULT_REGISTRY_LOCK_PATH=e.h,exports.DEFAULT_REGISTRY_PATH=e.g,exports.LOCK_MAX_RETRIES=e._,exports.LOCK_RETRY_DELAY_MS=e.v,exports.LOCK_STALE_AFTER_MS=e.y,exports.ListProcessFiltersSchema=e.n,exports.PROCESS_REGISTRY_TAG_ENV_VAR=e.b,exports.ProcessMetadataSchema=e.r,exports.ProcessRegistryBulkReleaseResponseSchema=e.i,exports.ProcessRegistryCleanupResponseSchema=e.a,exports.ProcessRegistryError=e.o,exports.ProcessRegistryRecordSchema=e.s,exports.ProcessRegistryResponseSchema=e.c,exports.ProcessRegistryService=e.t,exports.ProcessRegistryStateSchema=e.l,exports.REGISTRY_VERSION=e.u,exports.RegisterProcessRequestSchema=e.d,exports.ReleaseProcessRequestSchema=e.f,exports.ReleaseProcessesRequestSchema=e.p,exports.ServiceCategorySchema=e.m,exports.createProcessLease=y,exports.createProcessRegistryService=v,exports.makeTempPath=e.x,exports.normalizeRepositoryPath=e.S,exports.resolveProcessTags=e.C,exports.resolveSiblingRegistryPath=e.w,exports.withFileLock=_;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["fsSync","fs","path","ProcessRegistryService","resolveProcessTags"],"sources":["../src/services/FileLock.ts","../src/services/ProcessLease.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS } from '../utils';\n\n/**\n * Generic cross-process advisory file lock.\n *\n * Reuses the same mechanism the registries use internally (an exclusive `wx`\n * lockfile with stale-lock detection), but is reusable around any critical\n * section — not just a single registry write. It adds a heartbeat so a\n * legitimately long hold (e.g. waiting for a child runtime to boot) is not\n * mistaken for a stale lock, and reference-counted process cleanup so a crash\n * always releases the lock without permanently altering crash semantics.\n */\n\ninterface FileLockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\nexport interface WithFileLockOptions {\n /** Delay between acquisition attempts (ms). Default {@link LOCK_RETRY_DELAY_MS}. */\n retryDelayMs?: number;\n /** Max acquisition attempts before throwing. Default {@link LOCK_MAX_RETRIES}. */\n maxRetries?: number;\n /** Age after which a lock held by a live PID is reclaimable (ms). Default {@link LOCK_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Interval at which a held lock refreshes its timestamp (ms). Default `staleAfterMs / 3`. */\n heartbeatIntervalMs?: number;\n}\n\nconst CLEANUP_EVENTS = ['exit', 'SIGINT', 'SIGTERM', 'uncaughtException', 'unhandledRejection'] as const;\ntype CleanupEvent = (typeof CLEANUP_EVENTS)[number];\n\n/** Locks currently held by this process, keyed by lock path → token. Released synchronously on process exit. */\nconst heldLocks = new Map<string, string>();\nconst cleanupHandlers = new Map<CleanupEvent, () => void>();\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction releaseLockSync(lockPath: string, token: string): void {\n try {\n const existing = fsSync.readFileSync(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(lockPath);\n }\n } catch {\n // best-effort cleanup\n }\n}\n\nfunction registerCleanupIfNeeded(): void {\n if (cleanupHandlers.size > 0) {\n return;\n }\n\n const cleanup = (): void => {\n for (const [lockPath, token] of heldLocks) {\n releaseLockSync(lockPath, token);\n }\n heldLocks.clear();\n };\n\n for (const event of CLEANUP_EVENTS) {\n process.once(event, cleanup);\n cleanupHandlers.set(event, cleanup);\n }\n}\n\nfunction unregisterCleanupIfIdle(): void {\n if (heldLocks.size > 0) {\n return;\n }\n\n for (const [event, handler] of cleanupHandlers) {\n process.off(event, handler);\n }\n cleanupHandlers.clear();\n}\n\nasync function isStaleLock(lockPath: string, staleAfterMs: number): Promise<boolean> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n\n if (parsed.pid && isProcessRunning(parsed.pid)) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < staleAfterMs);\n }\n\n return true;\n } catch {\n return true;\n }\n}\n\nasync function acquireLock(\n lockPath: string,\n token: string,\n options: Required<Omit<WithFileLockOptions, 'heartbeatIntervalMs'>>,\n): Promise<void> {\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < options.maxRetries; attempt += 1) {\n try {\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n await fs.writeFile(lockPath, JSON.stringify(state), { flag: 'wx' });\n heldLocks.set(lockPath, token);\n registerCleanupIfNeeded();\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new Error(\n `Failed to acquire file lock '${lockPath}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (await isStaleLock(lockPath, options.staleAfterMs)) {\n await fs.unlink(lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await delay(options.retryDelayMs);\n }\n }\n\n throw new Error(`Unable to acquire file lock '${lockPath}' (timeout)`);\n}\n\nasync function refreshLock(lockPath: string, token: string): Promise<void> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n if (parsed.token !== token) {\n return;\n }\n\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n await fs.rename(tempPath, lockPath);\n } catch {\n // ignore — if the lockfile vanished, release handles the rest\n }\n}\n\nasync function releaseLock(lockPath: string, token: string): Promise<void> {\n heldLocks.delete(lockPath);\n try {\n const existing = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n await fs.unlink(lockPath);\n }\n } catch {\n // ignore\n } finally {\n unregisterCleanupIfIdle();\n }\n}\n\n/**\n * Run `fn` while holding an exclusive cross-process lock at `lockPath`.\n * The lock is always released when `fn` settles (or the process exits).\n */\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: WithFileLockOptions = {},\n): Promise<T> {\n const retryDelayMs = options.retryDelayMs ?? LOCK_RETRY_DELAY_MS;\n const maxRetries = options.maxRetries ?? LOCK_MAX_RETRIES;\n const staleAfterMs = options.staleAfterMs ?? LOCK_STALE_AFTER_MS;\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? Math.max(500, Math.floor(staleAfterMs / 3));\n const token = `${process.pid}-${randomBytes(6).toString('hex')}`;\n\n await acquireLock(lockPath, token, { retryDelayMs, maxRetries, staleAfterMs });\n\n const heartbeat = setInterval(() => {\n void refreshLock(lockPath, token);\n }, heartbeatIntervalMs);\n // Never keep the event loop alive solely for the heartbeat.\n heartbeat.unref?.();\n\n try {\n return await fn();\n } finally {\n clearInterval(heartbeat);\n await releaseLock(lockPath, token);\n }\n}\n","import type { RegisterProcessRequest, ServiceCategory } from '../types';\nimport { resolveProcessTags } from '../utils';\nimport { ProcessRegistryService } from './ProcessRegistryService';\n\nexport interface ProcessLease {\n release(options?: { kill?: boolean; releasePort?: boolean }): Promise<void>;\n}\n\nexport interface ProcessLeaseOptions {\n repositoryPath: string;\n serviceName: string;\n serviceType?: ServiceCategory;\n environment?: string;\n pid?: number;\n port?: number;\n host?: string;\n command?: string;\n args?: string[];\n tags?: string[];\n metadata?: Record<string, unknown>;\n force?: boolean;\n}\n\nexport function createProcessRegistryService(registryPath?: string): ProcessRegistryService {\n return new ProcessRegistryService(registryPath ?? process.env.PROCESS_REGISTRY_PATH);\n}\n\nexport async function createProcessLease(\n options: ProcessLeaseOptions,\n service?: ProcessRegistryService,\n): Promise<ProcessLease> {\n const registry = service ?? createProcessRegistryService();\n const pid = options.pid ?? process.pid;\n const serviceType = options.serviceType ?? 'tool';\n const environment = options.environment ?? process.env.NODE_ENV ?? 'development';\n\n const request: RegisterProcessRequest = {\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n environment,\n pid,\n port: options.port,\n host: options.host,\n command: options.command,\n args: options.args,\n tags: resolveProcessTags(options.tags),\n metadata: options.metadata,\n force: options.force ?? true,\n };\n\n const result = await registry.registerProcess(request);\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to register process for ${options.serviceName}`);\n }\n\n let released = false;\n return {\n release: async (releaseOptions?: { kill?: boolean; releasePort?: boolean }): Promise<void> => {\n if (released) {\n return;\n }\n\n released = true;\n const releaseResult = await registry.releaseProcess({\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n pid,\n environment,\n kill: releaseOptions?.kill ?? true,\n releasePort: releaseOptions?.releasePort ?? true,\n });\n\n if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes('No matching process entry')) {\n throw new Error(releaseResult.error || `Failed to release process for ${options.serviceName}`);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["fsSync","fs","path","ProcessRegistryService","resolveProcessTags"],"sources":["../src/services/FileLock.ts","../src/services/ProcessLease.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS } from '../utils';\n\n/**\n * Generic cross-process advisory file lock.\n *\n * Reuses the same mechanism the registries use internally (an exclusive `wx`\n * lockfile with stale-lock detection), but is reusable around any critical\n * section — not just a single registry write. It adds a heartbeat so a\n * legitimately long hold (e.g. waiting for a child runtime to boot) is not\n * mistaken for a stale lock, and reference-counted process cleanup so a crash\n * always releases the lock without permanently altering crash semantics.\n */\n\ninterface FileLockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\nexport interface WithFileLockOptions {\n /** Delay between acquisition attempts (ms). Default {@link LOCK_RETRY_DELAY_MS}. */\n retryDelayMs?: number;\n /** Max acquisition attempts before throwing. Default {@link LOCK_MAX_RETRIES}. */\n maxRetries?: number;\n /** Age after which a lock held by a live PID is reclaimable (ms). Default {@link LOCK_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Interval at which a held lock refreshes its timestamp (ms). Default `staleAfterMs / 3`. */\n heartbeatIntervalMs?: number;\n}\n\nconst CLEANUP_EVENTS = ['exit', 'SIGINT', 'SIGTERM', 'uncaughtException', 'unhandledRejection'] as const;\ntype CleanupEvent = (typeof CLEANUP_EVENTS)[number];\n\n/** Locks currently held by this process, keyed by lock path → token. Released synchronously on process exit. */\nconst heldLocks = new Map<string, string>();\nconst cleanupHandlers = new Map<CleanupEvent, () => void>();\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction releaseLockSync(lockPath: string, token: string): void {\n try {\n const existing = fsSync.readFileSync(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(lockPath);\n }\n } catch {\n // best-effort cleanup\n }\n}\n\nfunction registerCleanupIfNeeded(): void {\n if (cleanupHandlers.size > 0) {\n return;\n }\n\n const cleanup = (): void => {\n for (const [lockPath, token] of heldLocks) {\n releaseLockSync(lockPath, token);\n }\n heldLocks.clear();\n };\n\n for (const event of CLEANUP_EVENTS) {\n process.once(event, cleanup);\n cleanupHandlers.set(event, cleanup);\n }\n}\n\nfunction unregisterCleanupIfIdle(): void {\n if (heldLocks.size > 0) {\n return;\n }\n\n for (const [event, handler] of cleanupHandlers) {\n process.off(event, handler);\n }\n cleanupHandlers.clear();\n}\n\nasync function isStaleLock(lockPath: string, staleAfterMs: number): Promise<boolean> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n\n if (parsed.pid && isProcessRunning(parsed.pid)) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < staleAfterMs);\n }\n\n return true;\n } catch {\n return true;\n }\n}\n\nasync function acquireLock(\n lockPath: string,\n token: string,\n options: Required<Omit<WithFileLockOptions, 'heartbeatIntervalMs'>>,\n): Promise<void> {\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < options.maxRetries; attempt += 1) {\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.acquire`;\n\n try {\n // Publish the lock payload atomically: write it in full to a private temp\n // file, then hard-link that into place. `link` is an atomic exclusive\n // create (EEXIST when the lock is held), so the lock file is never visible\n // half-written. A bare `wx` write briefly exposes a zero-byte file between\n // create and write, which a concurrent acquirer reads as a corrupt lock\n // and wrongly reclaims — breaking mutual exclusion.\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n await fs.link(tempPath, lockPath);\n heldLocks.set(lockPath, token);\n registerCleanupIfNeeded();\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new Error(\n `Failed to acquire file lock '${lockPath}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (await isStaleLock(lockPath, options.staleAfterMs)) {\n await fs.unlink(lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await delay(options.retryDelayMs);\n } finally {\n await fs.unlink(tempPath).catch(() => undefined);\n }\n }\n\n throw new Error(`Unable to acquire file lock '${lockPath}' (timeout)`);\n}\n\nasync function refreshLock(lockPath: string, token: string, shouldAbort: () => boolean): Promise<void> {\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n if (parsed.token !== token || shouldAbort()) {\n return;\n }\n\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n // Re-check after the async write: once release has begun, renaming would\n // resurrect a lock file that releaseLock already removed, leaking it on disk.\n if (shouldAbort()) {\n return;\n }\n await fs.rename(tempPath, lockPath);\n } catch {\n // ignore — if the lockfile vanished, release handles the rest\n } finally {\n // The rename consumes tempPath on success; clean it up on every other path\n // so an interrupted refresh never leaves a stray file in the lock directory.\n await fs.unlink(tempPath).catch(() => undefined);\n }\n}\n\nasync function releaseLock(lockPath: string, token: string): Promise<void> {\n heldLocks.delete(lockPath);\n try {\n const existing = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n await fs.unlink(lockPath);\n }\n } catch {\n // ignore\n } finally {\n unregisterCleanupIfIdle();\n }\n}\n\n/**\n * Run `fn` while holding an exclusive cross-process lock at `lockPath`.\n * The lock is always released when `fn` settles (or the process exits).\n */\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: WithFileLockOptions = {},\n): Promise<T> {\n const retryDelayMs = options.retryDelayMs ?? LOCK_RETRY_DELAY_MS;\n const maxRetries = options.maxRetries ?? LOCK_MAX_RETRIES;\n const staleAfterMs = options.staleAfterMs ?? LOCK_STALE_AFTER_MS;\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? Math.max(500, Math.floor(staleAfterMs / 3));\n const token = `${process.pid}-${randomBytes(6).toString('hex')}`;\n\n await acquireLock(lockPath, token, { retryDelayMs, maxRetries, staleAfterMs });\n\n let releasing = false;\n let inFlightRefresh: Promise<void> | null = null;\n\n const heartbeat = setInterval(() => {\n // Single-flight: skip ticks while a refresh is still running or once we have\n // started releasing, so at most one refresh is ever outstanding.\n if (releasing || inFlightRefresh) {\n return;\n }\n inFlightRefresh = refreshLock(lockPath, token, () => releasing).finally(() => {\n inFlightRefresh = null;\n });\n }, heartbeatIntervalMs);\n // Never keep the event loop alive solely for the heartbeat.\n heartbeat.unref?.();\n\n try {\n return await fn();\n } finally {\n releasing = true;\n clearInterval(heartbeat);\n // Let any in-flight refresh settle before deleting the lock, otherwise its\n // pending rename could recreate the lock file right after releaseLock removes it.\n if (inFlightRefresh) {\n await inFlightRefresh;\n }\n await releaseLock(lockPath, token);\n }\n}\n","import type { RegisterProcessRequest, ServiceCategory } from '../types';\nimport { resolveProcessTags } from '../utils';\nimport { ProcessRegistryService } from './ProcessRegistryService';\n\nexport interface ProcessLease {\n release(options?: { kill?: boolean; releasePort?: boolean }): Promise<void>;\n}\n\nexport interface ProcessLeaseOptions {\n repositoryPath: string;\n serviceName: string;\n serviceType?: ServiceCategory;\n environment?: string;\n pid?: number;\n port?: number;\n host?: string;\n command?: string;\n args?: string[];\n tags?: string[];\n metadata?: Record<string, unknown>;\n force?: boolean;\n}\n\nexport function createProcessRegistryService(registryPath?: string): ProcessRegistryService {\n return new ProcessRegistryService(registryPath ?? process.env.PROCESS_REGISTRY_PATH);\n}\n\nexport async function createProcessLease(\n options: ProcessLeaseOptions,\n service?: ProcessRegistryService,\n): Promise<ProcessLease> {\n const registry = service ?? createProcessRegistryService();\n const pid = options.pid ?? process.pid;\n const serviceType = options.serviceType ?? 'tool';\n const environment = options.environment ?? process.env.NODE_ENV ?? 'development';\n\n const request: RegisterProcessRequest = {\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n environment,\n pid,\n port: options.port,\n host: options.host,\n command: options.command,\n args: options.args,\n tags: resolveProcessTags(options.tags),\n metadata: options.metadata,\n force: options.force ?? true,\n };\n\n const result = await registry.registerProcess(request);\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to register process for ${options.serviceName}`);\n }\n\n let released = false;\n return {\n release: async (releaseOptions?: { kill?: boolean; releasePort?: boolean }): Promise<void> => {\n if (released) {\n return;\n }\n\n released = true;\n const releaseResult = await registry.releaseProcess({\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n pid,\n environment,\n kill: releaseOptions?.kill ?? true,\n releasePort: releaseOptions?.releasePort ?? true,\n });\n\n if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes('No matching process entry')) {\n throw new Error(releaseResult.error || `Failed to release process for ${options.serviceName}`);\n }\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -106,6 +106,20 @@ declare const ListProcessFiltersSchema: z.ZodObject<{
|
|
|
106
106
|
environment: z.ZodOptional<z.ZodString>;
|
|
107
107
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
108
108
|
}, z.core.$strip>;
|
|
109
|
+
declare const ReleaseProcessesRequestSchema: z.ZodObject<{
|
|
110
|
+
repositoryPath: z.ZodOptional<z.ZodString>;
|
|
111
|
+
serviceType: z.ZodOptional<z.ZodEnum<{
|
|
112
|
+
service: "service";
|
|
113
|
+
tool: "tool";
|
|
114
|
+
}>>;
|
|
115
|
+
serviceName: z.ZodOptional<z.ZodString>;
|
|
116
|
+
environment: z.ZodOptional<z.ZodString>;
|
|
117
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
118
|
+
pid: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
120
|
+
kill: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
121
|
+
releasePort: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
122
|
+
}, z.core.$strip>;
|
|
109
123
|
declare const ProcessRegistryResponseSchema: z.ZodObject<{
|
|
110
124
|
success: z.ZodBoolean;
|
|
111
125
|
pid: z.ZodOptional<z.ZodNumber>;
|
|
@@ -129,14 +143,28 @@ declare const ProcessRegistryResponseSchema: z.ZodObject<{
|
|
|
129
143
|
}, z.core.$strip>>;
|
|
130
144
|
error: z.ZodOptional<z.ZodString>;
|
|
131
145
|
}, z.core.$strip>;
|
|
146
|
+
declare const ProcessRegistryCleanupResponseSchema: z.ZodObject<{
|
|
147
|
+
success: z.ZodBoolean;
|
|
148
|
+
scanned: z.ZodNumber;
|
|
149
|
+
removed: z.ZodNumber;
|
|
150
|
+
error: z.ZodOptional<z.ZodString>;
|
|
151
|
+
}, z.core.$strip>;
|
|
152
|
+
declare const ProcessRegistryBulkReleaseResponseSchema: z.ZodObject<{
|
|
153
|
+
success: z.ZodBoolean;
|
|
154
|
+
released: z.ZodNumber;
|
|
155
|
+
error: z.ZodOptional<z.ZodString>;
|
|
156
|
+
}, z.core.$strip>;
|
|
132
157
|
type ServiceCategory = z.infer<typeof ServiceCategorySchema>;
|
|
133
158
|
type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;
|
|
134
159
|
type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;
|
|
135
160
|
type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;
|
|
136
161
|
type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;
|
|
137
162
|
type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;
|
|
163
|
+
type ReleaseProcessesRequest = z.infer<typeof ReleaseProcessesRequestSchema>;
|
|
138
164
|
type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;
|
|
139
165
|
type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;
|
|
166
|
+
type ProcessRegistryCleanupResponse = z.infer<typeof ProcessRegistryCleanupResponseSchema>;
|
|
167
|
+
type ProcessRegistryBulkReleaseResponse = z.infer<typeof ProcessRegistryBulkReleaseResponseSchema>;
|
|
140
168
|
type ProcessRegistryErrorCode = 'INVALID_REQUEST' | 'REGISTRY_READ_FAILED' | 'REGISTRY_WRITE_FAILED' | 'REGISTRY_LOCK_FAILED' | 'NO_PROCESS_FOUND';
|
|
141
169
|
declare class ProcessRegistryError extends Error {
|
|
142
170
|
readonly code: ProcessRegistryErrorCode;
|
|
@@ -155,13 +183,18 @@ declare class ProcessRegistryService {
|
|
|
155
183
|
static resolveRegistryPath(inputPath?: string): string;
|
|
156
184
|
registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse>;
|
|
157
185
|
releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse>;
|
|
186
|
+
releaseProcesses(rawRequest: ReleaseProcessesRequest): Promise<ProcessRegistryBulkReleaseResponse>;
|
|
158
187
|
listProcesses(filters?: ListProcessFilters): Promise<ProcessRegistryRecord[]>;
|
|
188
|
+
cleanupStaleProcesses(filters?: ListProcessFilters): Promise<ProcessRegistryCleanupResponse>;
|
|
159
189
|
private withLock;
|
|
160
190
|
private loadState;
|
|
161
191
|
private saveState;
|
|
162
192
|
private pruneState;
|
|
163
193
|
private pruneMissingRepositories;
|
|
164
194
|
private pruneDeadProcesses;
|
|
195
|
+
private collectMatchingEntries;
|
|
196
|
+
private collectStaleEntries;
|
|
197
|
+
private removeEntriesByKey;
|
|
165
198
|
private acquireLock;
|
|
166
199
|
private releaseLock;
|
|
167
200
|
private isStaleLock;
|
|
@@ -172,6 +205,10 @@ declare class ProcessRegistryService {
|
|
|
172
205
|
private terminateProcess;
|
|
173
206
|
private releaseAssociatedPort;
|
|
174
207
|
private entryKey;
|
|
208
|
+
private normalizeListFilters;
|
|
209
|
+
private normalizeReleaseFilters;
|
|
210
|
+
private matchesListFilters;
|
|
211
|
+
private matchesReleaseFilters;
|
|
175
212
|
private findEntry;
|
|
176
213
|
private removeMatchingEntries;
|
|
177
214
|
private createSuccessResponse;
|
|
@@ -217,5 +254,5 @@ declare const makeTempPath: (filePath: string) => string;
|
|
|
217
254
|
declare function resolveProcessTags(tags?: string[]): string[] | undefined;
|
|
218
255
|
declare function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined;
|
|
219
256
|
//#endregion
|
|
220
|
-
export { DEFAULT_REGISTRY_LOCK_PATH, DEFAULT_REGISTRY_PATH, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS, ListProcessFilters, ListProcessFiltersSchema, PROCESS_REGISTRY_TAG_ENV_VAR, ProcessLease, ProcessLeaseOptions, ProcessMetadata, ProcessMetadataSchema, ProcessRegistryError, ProcessRegistryErrorCode, ProcessRegistryRecord, ProcessRegistryRecordSchema, ProcessRegistryResponse, ProcessRegistryResponseSchema, ProcessRegistryService, ProcessRegistryState, ProcessRegistryStateSchema, REGISTRY_VERSION, RegisterProcessRequest, RegisterProcessRequestSchema, ReleaseProcessRequest, ReleaseProcessRequestSchema, ServiceCategory, ServiceCategorySchema, WithFileLockOptions, createProcessLease, createProcessRegistryService, makeTempPath, normalizeRepositoryPath, resolveProcessTags, resolveSiblingRegistryPath, withFileLock };
|
|
257
|
+
export { DEFAULT_REGISTRY_LOCK_PATH, DEFAULT_REGISTRY_PATH, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS, ListProcessFilters, ListProcessFiltersSchema, PROCESS_REGISTRY_TAG_ENV_VAR, ProcessLease, ProcessLeaseOptions, ProcessMetadata, ProcessMetadataSchema, ProcessRegistryBulkReleaseResponse, ProcessRegistryBulkReleaseResponseSchema, ProcessRegistryCleanupResponse, ProcessRegistryCleanupResponseSchema, ProcessRegistryError, ProcessRegistryErrorCode, ProcessRegistryRecord, ProcessRegistryRecordSchema, ProcessRegistryResponse, ProcessRegistryResponseSchema, ProcessRegistryService, ProcessRegistryState, ProcessRegistryStateSchema, REGISTRY_VERSION, RegisterProcessRequest, RegisterProcessRequestSchema, ReleaseProcessRequest, ReleaseProcessRequestSchema, ReleaseProcessesRequest, ReleaseProcessesRequestSchema, ServiceCategory, ServiceCategorySchema, WithFileLockOptions, createProcessLease, createProcessRegistryService, makeTempPath, normalizeRepositoryPath, resolveProcessTags, resolveSiblingRegistryPath, withFileLock };
|
|
221
258
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -106,6 +106,20 @@ declare const ListProcessFiltersSchema: z.ZodObject<{
|
|
|
106
106
|
environment: z.ZodOptional<z.ZodString>;
|
|
107
107
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
108
108
|
}, z.core.$strip>;
|
|
109
|
+
declare const ReleaseProcessesRequestSchema: z.ZodObject<{
|
|
110
|
+
repositoryPath: z.ZodOptional<z.ZodString>;
|
|
111
|
+
serviceType: z.ZodOptional<z.ZodEnum<{
|
|
112
|
+
service: "service";
|
|
113
|
+
tool: "tool";
|
|
114
|
+
}>>;
|
|
115
|
+
serviceName: z.ZodOptional<z.ZodString>;
|
|
116
|
+
environment: z.ZodOptional<z.ZodString>;
|
|
117
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
118
|
+
pid: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
120
|
+
kill: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
121
|
+
releasePort: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
122
|
+
}, z.core.$strip>;
|
|
109
123
|
declare const ProcessRegistryResponseSchema: z.ZodObject<{
|
|
110
124
|
success: z.ZodBoolean;
|
|
111
125
|
pid: z.ZodOptional<z.ZodNumber>;
|
|
@@ -129,14 +143,28 @@ declare const ProcessRegistryResponseSchema: z.ZodObject<{
|
|
|
129
143
|
}, z.core.$strip>>;
|
|
130
144
|
error: z.ZodOptional<z.ZodString>;
|
|
131
145
|
}, z.core.$strip>;
|
|
146
|
+
declare const ProcessRegistryCleanupResponseSchema: z.ZodObject<{
|
|
147
|
+
success: z.ZodBoolean;
|
|
148
|
+
scanned: z.ZodNumber;
|
|
149
|
+
removed: z.ZodNumber;
|
|
150
|
+
error: z.ZodOptional<z.ZodString>;
|
|
151
|
+
}, z.core.$strip>;
|
|
152
|
+
declare const ProcessRegistryBulkReleaseResponseSchema: z.ZodObject<{
|
|
153
|
+
success: z.ZodBoolean;
|
|
154
|
+
released: z.ZodNumber;
|
|
155
|
+
error: z.ZodOptional<z.ZodString>;
|
|
156
|
+
}, z.core.$strip>;
|
|
132
157
|
type ServiceCategory = z.infer<typeof ServiceCategorySchema>;
|
|
133
158
|
type ProcessMetadata = z.infer<typeof ProcessMetadataSchema>;
|
|
134
159
|
type ProcessRegistryRecord = z.infer<typeof ProcessRegistryRecordSchema>;
|
|
135
160
|
type ProcessRegistryState = z.infer<typeof ProcessRegistryStateSchema>;
|
|
136
161
|
type RegisterProcessRequest = z.infer<typeof RegisterProcessRequestSchema>;
|
|
137
162
|
type ReleaseProcessRequest = z.infer<typeof ReleaseProcessRequestSchema>;
|
|
163
|
+
type ReleaseProcessesRequest = z.infer<typeof ReleaseProcessesRequestSchema>;
|
|
138
164
|
type ListProcessFilters = z.infer<typeof ListProcessFiltersSchema>;
|
|
139
165
|
type ProcessRegistryResponse = z.infer<typeof ProcessRegistryResponseSchema>;
|
|
166
|
+
type ProcessRegistryCleanupResponse = z.infer<typeof ProcessRegistryCleanupResponseSchema>;
|
|
167
|
+
type ProcessRegistryBulkReleaseResponse = z.infer<typeof ProcessRegistryBulkReleaseResponseSchema>;
|
|
140
168
|
type ProcessRegistryErrorCode = 'INVALID_REQUEST' | 'REGISTRY_READ_FAILED' | 'REGISTRY_WRITE_FAILED' | 'REGISTRY_LOCK_FAILED' | 'NO_PROCESS_FOUND';
|
|
141
169
|
declare class ProcessRegistryError extends Error {
|
|
142
170
|
readonly code: ProcessRegistryErrorCode;
|
|
@@ -155,13 +183,18 @@ declare class ProcessRegistryService {
|
|
|
155
183
|
static resolveRegistryPath(inputPath?: string): string;
|
|
156
184
|
registerProcess(rawRequest: RegisterProcessRequest): Promise<ProcessRegistryResponse>;
|
|
157
185
|
releaseProcess(rawRequest: ReleaseProcessRequest): Promise<ProcessRegistryResponse>;
|
|
186
|
+
releaseProcesses(rawRequest: ReleaseProcessesRequest): Promise<ProcessRegistryBulkReleaseResponse>;
|
|
158
187
|
listProcesses(filters?: ListProcessFilters): Promise<ProcessRegistryRecord[]>;
|
|
188
|
+
cleanupStaleProcesses(filters?: ListProcessFilters): Promise<ProcessRegistryCleanupResponse>;
|
|
159
189
|
private withLock;
|
|
160
190
|
private loadState;
|
|
161
191
|
private saveState;
|
|
162
192
|
private pruneState;
|
|
163
193
|
private pruneMissingRepositories;
|
|
164
194
|
private pruneDeadProcesses;
|
|
195
|
+
private collectMatchingEntries;
|
|
196
|
+
private collectStaleEntries;
|
|
197
|
+
private removeEntriesByKey;
|
|
165
198
|
private acquireLock;
|
|
166
199
|
private releaseLock;
|
|
167
200
|
private isStaleLock;
|
|
@@ -172,6 +205,10 @@ declare class ProcessRegistryService {
|
|
|
172
205
|
private terminateProcess;
|
|
173
206
|
private releaseAssociatedPort;
|
|
174
207
|
private entryKey;
|
|
208
|
+
private normalizeListFilters;
|
|
209
|
+
private normalizeReleaseFilters;
|
|
210
|
+
private matchesListFilters;
|
|
211
|
+
private matchesReleaseFilters;
|
|
175
212
|
private findEntry;
|
|
176
213
|
private removeMatchingEntries;
|
|
177
214
|
private createSuccessResponse;
|
|
@@ -217,5 +254,5 @@ declare const makeTempPath: (filePath: string) => string;
|
|
|
217
254
|
declare function resolveProcessTags(tags?: string[]): string[] | undefined;
|
|
218
255
|
declare function resolveSiblingRegistryPath(registryPath: string | undefined, fileName: string): string | undefined;
|
|
219
256
|
//#endregion
|
|
220
|
-
export { DEFAULT_REGISTRY_LOCK_PATH, DEFAULT_REGISTRY_PATH, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS, ListProcessFilters, ListProcessFiltersSchema, PROCESS_REGISTRY_TAG_ENV_VAR, ProcessLease, ProcessLeaseOptions, ProcessMetadata, ProcessMetadataSchema, ProcessRegistryError, ProcessRegistryErrorCode, ProcessRegistryRecord, ProcessRegistryRecordSchema, ProcessRegistryResponse, ProcessRegistryResponseSchema, ProcessRegistryService, ProcessRegistryState, ProcessRegistryStateSchema, REGISTRY_VERSION, RegisterProcessRequest, RegisterProcessRequestSchema, ReleaseProcessRequest, ReleaseProcessRequestSchema, ServiceCategory, ServiceCategorySchema, WithFileLockOptions, createProcessLease, createProcessRegistryService, makeTempPath, normalizeRepositoryPath, resolveProcessTags, resolveSiblingRegistryPath, withFileLock };
|
|
257
|
+
export { DEFAULT_REGISTRY_LOCK_PATH, DEFAULT_REGISTRY_PATH, LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS, ListProcessFilters, ListProcessFiltersSchema, PROCESS_REGISTRY_TAG_ENV_VAR, ProcessLease, ProcessLeaseOptions, ProcessMetadata, ProcessMetadataSchema, ProcessRegistryBulkReleaseResponse, ProcessRegistryBulkReleaseResponseSchema, ProcessRegistryCleanupResponse, ProcessRegistryCleanupResponseSchema, ProcessRegistryError, ProcessRegistryErrorCode, ProcessRegistryRecord, ProcessRegistryRecordSchema, ProcessRegistryResponse, ProcessRegistryResponseSchema, ProcessRegistryService, ProcessRegistryState, ProcessRegistryStateSchema, REGISTRY_VERSION, RegisterProcessRequest, RegisterProcessRequestSchema, ReleaseProcessRequest, ReleaseProcessRequestSchema, ReleaseProcessesRequest, ReleaseProcessesRequestSchema, ServiceCategory, ServiceCategorySchema, WithFileLockOptions, createProcessLease, createProcessRegistryService, makeTempPath, normalizeRepositoryPath, resolveProcessTags, resolveSiblingRegistryPath, withFileLock };
|
|
221
258
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as l,i as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./ProcessRegistryService.mjs";import{randomBytes as w}from"node:crypto";import T from"node:fs";import E from"node:fs/promises";import D from"node:path";const O=[`exit`,`SIGINT`,`SIGTERM`,`uncaughtException`,`unhandledRejection`],k=new Map,A=new Map;function j(e){try{return process.kill(e,0),!0}catch{return!1}}function M(e){return new Promise(t=>setTimeout(t,e))}function N(e,t){try{let n=T.readFileSync(e,`utf-8`);JSON.parse(n).token===t&&T.unlinkSync(e)}catch{}}function P(){if(A.size>0)return;let e=()=>{for(let[e,t]of k)N(e,t);k.clear()};for(let t of O)process.once(t,e),A.set(t,e)}function F(){if(!(k.size>0)){for(let[e,t]of A)process.off(e,t);A.clear()}}async function I(e,t){try{let n=await E.readFile(e,`utf-8`),r=JSON.parse(n);if(r.pid&&j(r.pid)){let e=Date.now()-new Date(r.createdAt).getTime();return!(Number.isFinite(e)&&e<t)}return!0}catch{return!0}}async function L(e,t,n){await E.mkdir(D.dirname(e),{recursive:!0});for(let r=0;r<n.maxRetries;r+=1){let i={pid:process.pid,token:t,createdAt:new Date().toISOString()},a=`${e}.${process.pid}.${w(4).toString(`hex`)}.acquire`;try{await E.writeFile(a,JSON.stringify(i),`utf-8`),await E.link(a,e),k.set(e,t),P();return}catch(t){if(t.code!==`EEXIST`)throw Error(`Failed to acquire file lock '${e}': ${t instanceof Error?t.message:String(t)}`,{cause:t});if(await I(e,n.staleAfterMs)){await E.unlink(e).catch(()=>void 0),--r;continue}await M(n.retryDelayMs)}finally{await E.unlink(a).catch(()=>void 0)}}throw Error(`Unable to acquire file lock '${e}' (timeout)`)}async function R(e,t,n){let r=`${e}.${process.pid}.${w(4).toString(`hex`)}.tmp`;try{let i=await E.readFile(e,`utf-8`);if(JSON.parse(i).token!==t||n())return;let a={pid:process.pid,token:t,createdAt:new Date().toISOString()};if(await E.writeFile(r,JSON.stringify(a),`utf-8`),n())return;await E.rename(r,e)}catch{}finally{await E.unlink(r).catch(()=>void 0)}}async function z(e,t){k.delete(e);try{let n=await E.readFile(e,`utf-8`);JSON.parse(n).token===t&&await E.unlink(e)}catch{}finally{F()}}async function B(e,t,n={}){let r=n.retryDelayMs??75,i=n.maxRetries??80,a=n.staleAfterMs??5e3,o=n.heartbeatIntervalMs??Math.max(500,Math.floor(a/3)),s=`${process.pid}-${w(6).toString(`hex`)}`;await L(e,s,{retryDelayMs:r,maxRetries:i,staleAfterMs:a});let c=!1,l=null,u=setInterval(()=>{c||l||(l=R(e,s,()=>c).finally(()=>{l=null}))},o);u.unref?.();try{return await t()}finally{c=!0,clearInterval(u),l&&await l,await z(e,s)}}function V(e){return new v(e??process.env.PROCESS_REGISTRY_PATH)}async function H(t,n){let r=n??V(),i=t.pid??process.pid,a=t.serviceType??`tool`,o=t.environment??process.env.NODE_ENV??`development`,s={repositoryPath:t.repositoryPath,serviceName:t.serviceName,serviceType:a,environment:o,pid:i,port:t.port,host:t.host,command:t.command,args:t.args,tags:e(t.tags),metadata:t.metadata,force:t.force??!0},c=await r.registerProcess(s);if(!c.success||!c.record)throw Error(c.error||`Failed to register process for ${t.serviceName}`);let l=!1;return{release:async e=>{if(l)return;l=!0;let n=await r.releaseProcess({repositoryPath:t.repositoryPath,serviceName:t.serviceName,serviceType:a,pid:i,environment:o,kill:e?.kill??!0,releasePort:e?.releasePort??!0});if(!n.success&&n.error&&!n.error.includes(`No matching process entry`))throw Error(n.error||`Failed to release process for ${t.serviceName}`)}}}export{l as DEFAULT_REGISTRY_LOCK_PATH,c as DEFAULT_REGISTRY_PATH,n as LOCK_MAX_RETRIES,b as LOCK_RETRY_DELAY_MS,C as LOCK_STALE_AFTER_MS,p as ListProcessFiltersSchema,i as PROCESS_REGISTRY_TAG_ENV_VAR,g as ProcessMetadataSchema,u as ProcessRegistryBulkReleaseResponseSchema,r as ProcessRegistryCleanupResponseSchema,m as ProcessRegistryError,_ as ProcessRegistryRecordSchema,a as ProcessRegistryResponseSchema,v as ProcessRegistryService,d as ProcessRegistryStateSchema,y as REGISTRY_VERSION,o as RegisterProcessRequestSchema,s as ReleaseProcessRequestSchema,h as ReleaseProcessesRequestSchema,f as ServiceCategorySchema,H as createProcessLease,V as createProcessRegistryService,S as makeTempPath,t as normalizeRepositoryPath,e as resolveProcessTags,x as resolveSiblingRegistryPath,B as withFileLock};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/services/FileLock.ts","../src/services/ProcessLease.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS } from '../utils';\n\n/**\n * Generic cross-process advisory file lock.\n *\n * Reuses the same mechanism the registries use internally (an exclusive `wx`\n * lockfile with stale-lock detection), but is reusable around any critical\n * section — not just a single registry write. It adds a heartbeat so a\n * legitimately long hold (e.g. waiting for a child runtime to boot) is not\n * mistaken for a stale lock, and reference-counted process cleanup so a crash\n * always releases the lock without permanently altering crash semantics.\n */\n\ninterface FileLockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\nexport interface WithFileLockOptions {\n /** Delay between acquisition attempts (ms). Default {@link LOCK_RETRY_DELAY_MS}. */\n retryDelayMs?: number;\n /** Max acquisition attempts before throwing. Default {@link LOCK_MAX_RETRIES}. */\n maxRetries?: number;\n /** Age after which a lock held by a live PID is reclaimable (ms). Default {@link LOCK_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Interval at which a held lock refreshes its timestamp (ms). Default `staleAfterMs / 3`. */\n heartbeatIntervalMs?: number;\n}\n\nconst CLEANUP_EVENTS = ['exit', 'SIGINT', 'SIGTERM', 'uncaughtException', 'unhandledRejection'] as const;\ntype CleanupEvent = (typeof CLEANUP_EVENTS)[number];\n\n/** Locks currently held by this process, keyed by lock path → token. Released synchronously on process exit. */\nconst heldLocks = new Map<string, string>();\nconst cleanupHandlers = new Map<CleanupEvent, () => void>();\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction releaseLockSync(lockPath: string, token: string): void {\n try {\n const existing = fsSync.readFileSync(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(lockPath);\n }\n } catch {\n // best-effort cleanup\n }\n}\n\nfunction registerCleanupIfNeeded(): void {\n if (cleanupHandlers.size > 0) {\n return;\n }\n\n const cleanup = (): void => {\n for (const [lockPath, token] of heldLocks) {\n releaseLockSync(lockPath, token);\n }\n heldLocks.clear();\n };\n\n for (const event of CLEANUP_EVENTS) {\n process.once(event, cleanup);\n cleanupHandlers.set(event, cleanup);\n }\n}\n\nfunction unregisterCleanupIfIdle(): void {\n if (heldLocks.size > 0) {\n return;\n }\n\n for (const [event, handler] of cleanupHandlers) {\n process.off(event, handler);\n }\n cleanupHandlers.clear();\n}\n\nasync function isStaleLock(lockPath: string, staleAfterMs: number): Promise<boolean> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n\n if (parsed.pid && isProcessRunning(parsed.pid)) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < staleAfterMs);\n }\n\n return true;\n } catch {\n return true;\n }\n}\n\nasync function acquireLock(\n lockPath: string,\n token: string,\n options: Required<Omit<WithFileLockOptions, 'heartbeatIntervalMs'>>,\n): Promise<void> {\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < options.maxRetries; attempt += 1) {\n try {\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n await fs.writeFile(lockPath, JSON.stringify(state), { flag: 'wx' });\n heldLocks.set(lockPath, token);\n registerCleanupIfNeeded();\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new Error(\n `Failed to acquire file lock '${lockPath}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (await isStaleLock(lockPath, options.staleAfterMs)) {\n await fs.unlink(lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await delay(options.retryDelayMs);\n }\n }\n\n throw new Error(`Unable to acquire file lock '${lockPath}' (timeout)`);\n}\n\nasync function refreshLock(lockPath: string, token: string): Promise<void> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n if (parsed.token !== token) {\n return;\n }\n\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n await fs.rename(tempPath, lockPath);\n } catch {\n // ignore — if the lockfile vanished, release handles the rest\n }\n}\n\nasync function releaseLock(lockPath: string, token: string): Promise<void> {\n heldLocks.delete(lockPath);\n try {\n const existing = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n await fs.unlink(lockPath);\n }\n } catch {\n // ignore\n } finally {\n unregisterCleanupIfIdle();\n }\n}\n\n/**\n * Run `fn` while holding an exclusive cross-process lock at `lockPath`.\n * The lock is always released when `fn` settles (or the process exits).\n */\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: WithFileLockOptions = {},\n): Promise<T> {\n const retryDelayMs = options.retryDelayMs ?? LOCK_RETRY_DELAY_MS;\n const maxRetries = options.maxRetries ?? LOCK_MAX_RETRIES;\n const staleAfterMs = options.staleAfterMs ?? LOCK_STALE_AFTER_MS;\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? Math.max(500, Math.floor(staleAfterMs / 3));\n const token = `${process.pid}-${randomBytes(6).toString('hex')}`;\n\n await acquireLock(lockPath, token, { retryDelayMs, maxRetries, staleAfterMs });\n\n const heartbeat = setInterval(() => {\n void refreshLock(lockPath, token);\n }, heartbeatIntervalMs);\n // Never keep the event loop alive solely for the heartbeat.\n heartbeat.unref?.();\n\n try {\n return await fn();\n } finally {\n clearInterval(heartbeat);\n await releaseLock(lockPath, token);\n }\n}\n","import type { RegisterProcessRequest, ServiceCategory } from '../types';\nimport { resolveProcessTags } from '../utils';\nimport { ProcessRegistryService } from './ProcessRegistryService';\n\nexport interface ProcessLease {\n release(options?: { kill?: boolean; releasePort?: boolean }): Promise<void>;\n}\n\nexport interface ProcessLeaseOptions {\n repositoryPath: string;\n serviceName: string;\n serviceType?: ServiceCategory;\n environment?: string;\n pid?: number;\n port?: number;\n host?: string;\n command?: string;\n args?: string[];\n tags?: string[];\n metadata?: Record<string, unknown>;\n force?: boolean;\n}\n\nexport function createProcessRegistryService(registryPath?: string): ProcessRegistryService {\n return new ProcessRegistryService(registryPath ?? process.env.PROCESS_REGISTRY_PATH);\n}\n\nexport async function createProcessLease(\n options: ProcessLeaseOptions,\n service?: ProcessRegistryService,\n): Promise<ProcessLease> {\n const registry = service ?? createProcessRegistryService();\n const pid = options.pid ?? process.pid;\n const serviceType = options.serviceType ?? 'tool';\n const environment = options.environment ?? process.env.NODE_ENV ?? 'development';\n\n const request: RegisterProcessRequest = {\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n environment,\n pid,\n port: options.port,\n host: options.host,\n command: options.command,\n args: options.args,\n tags: resolveProcessTags(options.tags),\n metadata: options.metadata,\n force: options.force ?? true,\n };\n\n const result = await registry.registerProcess(request);\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to register process for ${options.serviceName}`);\n }\n\n let released = false;\n return {\n release: async (releaseOptions?: { kill?: boolean; releasePort?: boolean }): Promise<void> => {\n if (released) {\n return;\n }\n\n released = true;\n const releaseResult = await registry.releaseProcess({\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n pid,\n environment,\n kill: releaseOptions?.kill ?? true,\n releasePort: releaseOptions?.releasePort ?? true,\n });\n\n if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes('No matching process entry')) {\n throw new Error(releaseResult.error || `Failed to release process for ${options.serviceName}`);\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/services/FileLock.ts","../src/services/ProcessLease.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport fsSync from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { LOCK_MAX_RETRIES, LOCK_RETRY_DELAY_MS, LOCK_STALE_AFTER_MS } from '../utils';\n\n/**\n * Generic cross-process advisory file lock.\n *\n * Reuses the same mechanism the registries use internally (an exclusive `wx`\n * lockfile with stale-lock detection), but is reusable around any critical\n * section — not just a single registry write. It adds a heartbeat so a\n * legitimately long hold (e.g. waiting for a child runtime to boot) is not\n * mistaken for a stale lock, and reference-counted process cleanup so a crash\n * always releases the lock without permanently altering crash semantics.\n */\n\ninterface FileLockState {\n pid: number;\n token: string;\n createdAt: string;\n}\n\nexport interface WithFileLockOptions {\n /** Delay between acquisition attempts (ms). Default {@link LOCK_RETRY_DELAY_MS}. */\n retryDelayMs?: number;\n /** Max acquisition attempts before throwing. Default {@link LOCK_MAX_RETRIES}. */\n maxRetries?: number;\n /** Age after which a lock held by a live PID is reclaimable (ms). Default {@link LOCK_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Interval at which a held lock refreshes its timestamp (ms). Default `staleAfterMs / 3`. */\n heartbeatIntervalMs?: number;\n}\n\nconst CLEANUP_EVENTS = ['exit', 'SIGINT', 'SIGTERM', 'uncaughtException', 'unhandledRejection'] as const;\ntype CleanupEvent = (typeof CLEANUP_EVENTS)[number];\n\n/** Locks currently held by this process, keyed by lock path → token. Released synchronously on process exit. */\nconst heldLocks = new Map<string, string>();\nconst cleanupHandlers = new Map<CleanupEvent, () => void>();\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction releaseLockSync(lockPath: string, token: string): void {\n try {\n const existing = fsSync.readFileSync(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n fsSync.unlinkSync(lockPath);\n }\n } catch {\n // best-effort cleanup\n }\n}\n\nfunction registerCleanupIfNeeded(): void {\n if (cleanupHandlers.size > 0) {\n return;\n }\n\n const cleanup = (): void => {\n for (const [lockPath, token] of heldLocks) {\n releaseLockSync(lockPath, token);\n }\n heldLocks.clear();\n };\n\n for (const event of CLEANUP_EVENTS) {\n process.once(event, cleanup);\n cleanupHandlers.set(event, cleanup);\n }\n}\n\nfunction unregisterCleanupIfIdle(): void {\n if (heldLocks.size > 0) {\n return;\n }\n\n for (const [event, handler] of cleanupHandlers) {\n process.off(event, handler);\n }\n cleanupHandlers.clear();\n}\n\nasync function isStaleLock(lockPath: string, staleAfterMs: number): Promise<boolean> {\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n\n if (parsed.pid && isProcessRunning(parsed.pid)) {\n const age = Date.now() - new Date(parsed.createdAt).getTime();\n return !(Number.isFinite(age) && age < staleAfterMs);\n }\n\n return true;\n } catch {\n return true;\n }\n}\n\nasync function acquireLock(\n lockPath: string,\n token: string,\n options: Required<Omit<WithFileLockOptions, 'heartbeatIntervalMs'>>,\n): Promise<void> {\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n for (let attempt = 0; attempt < options.maxRetries; attempt += 1) {\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.acquire`;\n\n try {\n // Publish the lock payload atomically: write it in full to a private temp\n // file, then hard-link that into place. `link` is an atomic exclusive\n // create (EEXIST when the lock is held), so the lock file is never visible\n // half-written. A bare `wx` write briefly exposes a zero-byte file between\n // create and write, which a concurrent acquirer reads as a corrupt lock\n // and wrongly reclaims — breaking mutual exclusion.\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n await fs.link(tempPath, lockPath);\n heldLocks.set(lockPath, token);\n registerCleanupIfNeeded();\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new Error(\n `Failed to acquire file lock '${lockPath}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n if (await isStaleLock(lockPath, options.staleAfterMs)) {\n await fs.unlink(lockPath).catch(() => undefined);\n attempt -= 1;\n continue;\n }\n\n await delay(options.retryDelayMs);\n } finally {\n await fs.unlink(tempPath).catch(() => undefined);\n }\n }\n\n throw new Error(`Unable to acquire file lock '${lockPath}' (timeout)`);\n}\n\nasync function refreshLock(lockPath: string, token: string, shouldAbort: () => boolean): Promise<void> {\n const tempPath = `${lockPath}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n try {\n const content = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(content) as FileLockState;\n if (parsed.token !== token || shouldAbort()) {\n return;\n }\n\n const state: FileLockState = { pid: process.pid, token, createdAt: new Date().toISOString() };\n await fs.writeFile(tempPath, JSON.stringify(state), 'utf-8');\n // Re-check after the async write: once release has begun, renaming would\n // resurrect a lock file that releaseLock already removed, leaking it on disk.\n if (shouldAbort()) {\n return;\n }\n await fs.rename(tempPath, lockPath);\n } catch {\n // ignore — if the lockfile vanished, release handles the rest\n } finally {\n // The rename consumes tempPath on success; clean it up on every other path\n // so an interrupted refresh never leaves a stray file in the lock directory.\n await fs.unlink(tempPath).catch(() => undefined);\n }\n}\n\nasync function releaseLock(lockPath: string, token: string): Promise<void> {\n heldLocks.delete(lockPath);\n try {\n const existing = await fs.readFile(lockPath, 'utf-8');\n const parsed = JSON.parse(existing) as { token?: string };\n if (parsed.token === token) {\n await fs.unlink(lockPath);\n }\n } catch {\n // ignore\n } finally {\n unregisterCleanupIfIdle();\n }\n}\n\n/**\n * Run `fn` while holding an exclusive cross-process lock at `lockPath`.\n * The lock is always released when `fn` settles (or the process exits).\n */\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: WithFileLockOptions = {},\n): Promise<T> {\n const retryDelayMs = options.retryDelayMs ?? LOCK_RETRY_DELAY_MS;\n const maxRetries = options.maxRetries ?? LOCK_MAX_RETRIES;\n const staleAfterMs = options.staleAfterMs ?? LOCK_STALE_AFTER_MS;\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? Math.max(500, Math.floor(staleAfterMs / 3));\n const token = `${process.pid}-${randomBytes(6).toString('hex')}`;\n\n await acquireLock(lockPath, token, { retryDelayMs, maxRetries, staleAfterMs });\n\n let releasing = false;\n let inFlightRefresh: Promise<void> | null = null;\n\n const heartbeat = setInterval(() => {\n // Single-flight: skip ticks while a refresh is still running or once we have\n // started releasing, so at most one refresh is ever outstanding.\n if (releasing || inFlightRefresh) {\n return;\n }\n inFlightRefresh = refreshLock(lockPath, token, () => releasing).finally(() => {\n inFlightRefresh = null;\n });\n }, heartbeatIntervalMs);\n // Never keep the event loop alive solely for the heartbeat.\n heartbeat.unref?.();\n\n try {\n return await fn();\n } finally {\n releasing = true;\n clearInterval(heartbeat);\n // Let any in-flight refresh settle before deleting the lock, otherwise its\n // pending rename could recreate the lock file right after releaseLock removes it.\n if (inFlightRefresh) {\n await inFlightRefresh;\n }\n await releaseLock(lockPath, token);\n }\n}\n","import type { RegisterProcessRequest, ServiceCategory } from '../types';\nimport { resolveProcessTags } from '../utils';\nimport { ProcessRegistryService } from './ProcessRegistryService';\n\nexport interface ProcessLease {\n release(options?: { kill?: boolean; releasePort?: boolean }): Promise<void>;\n}\n\nexport interface ProcessLeaseOptions {\n repositoryPath: string;\n serviceName: string;\n serviceType?: ServiceCategory;\n environment?: string;\n pid?: number;\n port?: number;\n host?: string;\n command?: string;\n args?: string[];\n tags?: string[];\n metadata?: Record<string, unknown>;\n force?: boolean;\n}\n\nexport function createProcessRegistryService(registryPath?: string): ProcessRegistryService {\n return new ProcessRegistryService(registryPath ?? process.env.PROCESS_REGISTRY_PATH);\n}\n\nexport async function createProcessLease(\n options: ProcessLeaseOptions,\n service?: ProcessRegistryService,\n): Promise<ProcessLease> {\n const registry = service ?? createProcessRegistryService();\n const pid = options.pid ?? process.pid;\n const serviceType = options.serviceType ?? 'tool';\n const environment = options.environment ?? process.env.NODE_ENV ?? 'development';\n\n const request: RegisterProcessRequest = {\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n environment,\n pid,\n port: options.port,\n host: options.host,\n command: options.command,\n args: options.args,\n tags: resolveProcessTags(options.tags),\n metadata: options.metadata,\n force: options.force ?? true,\n };\n\n const result = await registry.registerProcess(request);\n\n if (!result.success || !result.record) {\n throw new Error(result.error || `Failed to register process for ${options.serviceName}`);\n }\n\n let released = false;\n return {\n release: async (releaseOptions?: { kill?: boolean; releasePort?: boolean }): Promise<void> => {\n if (released) {\n return;\n }\n\n released = true;\n const releaseResult = await registry.releaseProcess({\n repositoryPath: options.repositoryPath,\n serviceName: options.serviceName,\n serviceType,\n pid,\n environment,\n kill: releaseOptions?.kill ?? true,\n releasePort: releaseOptions?.releasePort ?? true,\n });\n\n if (!releaseResult.success && releaseResult.error && !releaseResult.error.includes('No matching process entry')) {\n throw new Error(releaseResult.error || `Failed to release process for ${options.serviceName}`);\n }\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/foundation-process-registry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Long-running process registry and cleanup coordination across worktrees",
|
|
5
5
|
"bin": {
|
|
6
6
|
"process-registry": "./dist/cli.cjs"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"zod": "4.4.1",
|
|
17
|
-
"@agimon-ai/foundation-port-registry": "0.
|
|
17
|
+
"@agimon-ai/foundation-port-registry": "0.18.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "25.6.0",
|