@danypops/pi-packed 0.21.9 → 0.21.10

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/client.d.ts CHANGED
@@ -105,5 +105,12 @@ export interface EnsureClientDeps {
105
105
  * creating a competing one.
106
106
  */
107
107
  export declare function ensureClient(deps: EnsureClientDeps): Promise<PackedClient>;
108
+ /**
109
+ * An auto-spawned daemon is detached and long-lived, so it keeps whatever
110
+ * PATH its host process had at spawn time forever. Resolve `pi`'s absolute
111
+ * path now and pin it, rather than trust a bare "pi" to keep resolving in
112
+ * that frozen PATH. Leaves an explicit PI_PACKED_PI_BIN/PI_BIN untouched.
113
+ */
114
+ export declare function resolvePiBinForSpawn(env?: NodeJS.ProcessEnv): string | undefined;
108
115
  export declare function ensurePackedClient(paths?: PackedClientPaths, transport?: FetchTransport): Promise<PackedClient>;
109
116
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../service/src/public/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../service/src/public/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{spawn as E}from"node:child_process";import{readFileSync as g}from"node:fs";import{dirname as S,join as f,resolve as A}from"node:path";import{fileURLToPath as I}from"node:url";class j{baseUrl;token;opsPath;transport;label;constructor(z,J,G){this.baseUrl=z;this.token=J;this.label=G.label,this.opsPath=G.opsPath??"/api/v1/ops",this.transport=G.transport??fetch}authedRequest(z,J={}){return this.transport(new Request(`${this.baseUrl}${z}`,{...J,headers:{...J.headers,authorization:`Bearer ${this.token}`}}))}async call(z,J){let G=await this.authedRequest(this.opsPath,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({op:z,input:J})}),Z=await G.json();if(!G.ok)throw Error(Z.error??`${this.label} operation failed with HTTP ${G.status}`);return Z.result}async operations(){let z=await this.authedRequest(this.opsPath),J=await z.json();if(!z.ok)throw Error(J.error??`${this.label} discovery failed with HTTP ${z.status}`);return J.operations??[]}async ready(){let z=await this.authedRequest("/ready");if(z.status===503)return!1;if(!z.ok)throw Error(`${this.label} readiness check failed with HTTP ${z.status}`);return!0}async health(){let z=await this.authedRequest("/health"),J=await z.json();if(!z.ok||J.ok!==!0||typeof J.version!=="string")throw Error(J.error??`${this.label} health check failed`);return{ok:!0,version:J.version}}}import{chmodSync as d,existsSync as c,mkdirSync as l,readFileSync as C,renameSync as i,rmSync as r,writeFileSync as p}from"node:fs";import{homedir as D,tmpdir as b}from"node:os";import{dirname as t,join as K,win32 as Q}from"node:path";var N="127.0.0.1";function q(z,J={}){let G=J.platform??process.platform,Z=J.home??D();if(G==="darwin")return R(z,Z);if(G==="win32")return L(z,J.env??process.env,Z);return _(z,J,Z)}function _(z,J,G){let Z=J.env??process.env,$=J.uid??process.getuid?.()??0,W=Z.XDG_DATA_HOME??K(G,".local","share"),Y=Z.XDG_STATE_HOME??K(G,".local","state"),V=Z.XDG_RUNTIME_DIR??K("/run","user",String($)),x=Z.XDG_CONFIG_HOME??K(G,".config");return{database:K(W,z.stateDirectoryName,z.databaseFilename),token:K(Y,z.stateDirectoryName,z.tokenFilename),handle:K(V,z.stateDirectoryName,z.handleFilename),serviceDescriptor:K(x,"systemd","user",z.systemdUnitName)}}function R(z,J){let G=K(J,"Library"),Z=K(G,"Application Support",z.stateDirectoryName);return{database:K(Z,z.databaseFilename),token:K(Z,z.tokenFilename),handle:K(b(),z.stateDirectoryName,z.handleFilename),serviceDescriptor:K(Z,z.systemdUnitName)}}function L(z,J,G){let Z=J.LOCALAPPDATA??Q.join(G,"AppData","Local"),$=J.APPDATA??Q.join(G,"AppData","Roaming"),W=Q.join(Z,z.stateDirectoryName,"Data");return{database:Q.join(W,z.databaseFilename),token:Q.join(W,z.tokenFilename),handle:Q.join(Z,"Temp",z.stateDirectoryName,z.handleFilename),serviceDescriptor:Q.join($,z.stateDirectoryName,"Config",z.systemdUnitName)}}function B(z){try{let J=JSON.parse(C(z,"utf8"));if(J.host!==N||!Number.isInteger(J.port)||J.port<1||J.port>65535||!Number.isInteger(J.pid))return null;return J}catch{return null}}import{execFileSync as M}from"node:child_process";import{fileURLToPath as w}from"node:url";function X(z,J){let G=J.runCommand(process.execPath,[J.armadaCliPath,"status","--json"]);if(!G.ok)return!1;try{return JSON.parse(G.output).vehicles?.some(($)=>$.name===z)??!1}catch{return!1}}function H(){return{armadaCliPath:w(import.meta.resolve("@danypops/armada/cli")),runCommand:(z,J,G)=>{try{return{ok:!0,output:M(z,J,{encoding:"utf8",input:G,stdio:[G===void 0?"ignore":"pipe","pipe","pipe"]})}}catch(Z){let $=Z;return{ok:!1,output:($.stdout??"")+($.stderr??$.message)}}}}}class O extends Error{notADaemon;constructor(z,J){super(z);this.notADaemon=J;this.name="InstallServiceError"}}var y="pi-packed",F="pi-packed.service";function U(z={}){let J=z.env??process.env;if(J.PI_PACKED_HOME){let Z=A(J.PI_PACKED_HOME);return{token:`${Z}/token`,handle:`${Z}/handle.json`,serviceDescriptor:`${Z}/${F}`}}let G=q({stateDirectoryName:"pi-packed",databaseFilename:"packages.db",tokenFilename:"token",handleFilename:"handle.json",systemdUnitName:F},z);return{token:G.token,handle:G.handle,serviceDescriptor:G.serviceDescriptor}}function k(){return X(y,H())}function P(z,J=30000){return(G)=>z(new Request(G,{signal:AbortSignal.any([G.signal,AbortSignal.timeout(J)])}))}class T{rpc;constructor(z,J,G=fetch){this.rpc=new j(z,J,{label:"Packed",transport:P(G)})}call(z,J){return this.rpc.call(z,J)}health(){return this.rpc.health()}search(z,J,G=!1){return this.call("package.search",{query:z,limit:J,offline:G})}info(z){return this.call("package.info",{name:z})}installed(){return this.call("package.installed",{})}async updates(){return(await this.call("package.updates",{})).updates}security(){return this.call("package.security.get",{})}setMutationApproval(z,J=!1){return this.call("package.security.set",{mutationApproval:z,approved:J})}async install(z,J=!1){let G=await this.call("package.install",{source:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async installService(z,J=!1){let G=await this.call("package.install_service",{source:z,approved:J});if(!G.ok)throw new O(G.output,G.notADaemon);return{output:G.output,spec:G.spec}}async remove(z,J=!1){let G=await this.call("package.remove",{name:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async update(z,J=!1){let G=await this.call("package.update",{source:z,approved:J});if(!G.ok)throw Error(G.output);return{output:G.output,reloadRequired:G.reloadRequired??!0,alreadyUpToDate:G.alreadyUpToDate??!1,pinned:G.pinned??!1,previousVersion:G.previousVersion,currentVersion:G.currentVersion}}setupPlan(z,J=!1){return this.call("setup.plan",{manifestPath:z,prune:J})}setupApply(z,J=!1,G=!1){return this.call("setup.apply",{manifestPath:z,approved:J,prune:G})}listResources(z){return this.call("resources.list",{projectRoot:z})}async toggleResource(z,J,G,Z,$,W=!1){let Y=await this.call("resources.toggle",{source:z,field:J,path:G,enabled:Z,projectRoot:$,approved:W});if(!Y.ok)throw Error(Y.output);return Y.output}piStatus(){return this.call("pi.status",{})}}async function u(z=U(),J=fetch){let G=B(z.handle);if(!G)throw Error("Packed daemon is not running");let Z;try{Z=g(z.token,"utf8").trim()}catch{throw Error("Packed daemon token is unavailable")}if(!/^[a-f0-9]{64}$/.test(Z))throw Error("Packed daemon token is invalid");let $=new T(`http://${G.host}:${G.port}`,Z,J);return await $.health(),$}async function v(z){try{return await z.connect()}catch{}let J=z.retryAttempts??30,G=z.retryDelayMs??100,Z=z.isServiceInstalled();if(!Z)z.spawn();for(let W=0;W<J;W++){await z.sleep(G);try{return await z.connect()}catch{}}let $=J*G/1000;throw Error(Z?`The managed Packed service is registered but unreachable after ${$} seconds. Its supervisor may have stopped it or it may be failing; restart the managed service and inspect its status and logs. Packed will not auto-spawn a competing process.`:`Packed daemon did not become ready within ${$} seconds`)}async function Yz(z=U(),J=fetch){return v({connect:()=>u(z,J),isServiceInstalled:()=>k(),spawn:()=>{let G=process.env.PI_PACKED_BIN,Z=G??process.env.PI_PACKED_BUN??"bun",$=G?["serve"]:[f(S(I(import.meta.url)),"../service/src/cli/cli.ts"),"serve"],W=E(Z,$,{detached:!0,stdio:"ignore",env:{...process.env,DAEMON_KIT_LAUNCH_PROVENANCE:"auto-spawn"}});W.once("error",()=>{}),W.unref()},sleep:(G)=>new Promise((Z)=>setTimeout(Z,G))})}export{U as resolvePackedClientPaths,Yz as ensurePackedClient,v as ensureClient,u as connectPackedClient,T as PackedClient,O as InstallServiceError};
1
+ import{spawn as g}from"node:child_process";import{accessSync as A,constants as f,readFileSync as S}from"node:fs";import{delimiter as I,dirname as y,join as O,resolve as k}from"node:path";import{fileURLToPath as P}from"node:url";class j{baseUrl;token;opsPath;transport;label;constructor(z,J,G){this.baseUrl=z;this.token=J;this.label=G.label,this.opsPath=G.opsPath??"/api/v1/ops",this.transport=G.transport??fetch}authedRequest(z,J={}){return this.transport(new Request(`${this.baseUrl}${z}`,{...J,headers:{...J.headers,authorization:`Bearer ${this.token}`}}))}async call(z,J){let G=await this.authedRequest(this.opsPath,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({op:z,input:J})}),Z=await G.json();if(!G.ok)throw Error(Z.error??`${this.label} operation failed with HTTP ${G.status}`);return Z.result}async operations(){let z=await this.authedRequest(this.opsPath),J=await z.json();if(!z.ok)throw Error(J.error??`${this.label} discovery failed with HTTP ${z.status}`);return J.operations??[]}async ready(){let z=await this.authedRequest("/ready");if(z.status===503)return!1;if(!z.ok)throw Error(`${this.label} readiness check failed with HTTP ${z.status}`);return!0}async health(){let z=await this.authedRequest("/health"),J=await z.json();if(!z.ok||J.ok!==!0||typeof J.version!=="string")throw Error(J.error??`${this.label} health check failed`);return{ok:!0,version:J.version}}}import{chmodSync as r,existsSync as p,mkdirSync as o,readFileSync as C,renameSync as n,rmSync as t,writeFileSync as a}from"node:fs";import{homedir as b,tmpdir as N}from"node:os";import{dirname as zz,join as W,win32 as Y}from"node:path";var _="127.0.0.1";function q(z,J={}){let G=J.platform??process.platform,Z=J.home??b();if(G==="darwin")return L(z,Z);if(G==="win32")return M(z,J.env??process.env,Z);return R(z,J,Z)}function R(z,J,G){let Z=J.env??process.env,$=J.uid??process.getuid?.()??0,K=Z.XDG_DATA_HOME??W(G,".local","share"),Q=Z.XDG_STATE_HOME??W(G,".local","state"),x=Z.XDG_RUNTIME_DIR??W("/run","user",String($)),D=Z.XDG_CONFIG_HOME??W(G,".config");return{database:W(K,z.stateDirectoryName,z.databaseFilename),token:W(Q,z.stateDirectoryName,z.tokenFilename),handle:W(x,z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(D,"systemd","user",z.systemdUnitName)}}function L(z,J){let G=W(J,"Library"),Z=W(G,"Application Support",z.stateDirectoryName);return{database:W(Z,z.databaseFilename),token:W(Z,z.tokenFilename),handle:W(N(),z.stateDirectoryName,z.handleFilename),serviceDescriptor:W(Z,z.systemdUnitName)}}function M(z,J,G){let Z=J.LOCALAPPDATA??Y.join(G,"AppData","Local"),$=J.APPDATA??Y.join(G,"AppData","Roaming"),K=Y.join(Z,z.stateDirectoryName,"Data");return{database:Y.join(K,z.databaseFilename),token:Y.join(K,z.tokenFilename),handle:Y.join(Z,"Temp",z.stateDirectoryName,z.handleFilename),serviceDescriptor:Y.join($,z.stateDirectoryName,"Config",z.systemdUnitName)}}function X(z){try{let J=JSON.parse(C(z,"utf8"));if(J.host!==_||!Number.isInteger(J.port)||J.port<1||J.port>65535||!Number.isInteger(J.pid))return null;return J}catch{return null}}import{execFileSync as E}from"node:child_process";import{fileURLToPath as w}from"node:url";function H(z,J){let G=J.runCommand(process.execPath,[J.armadaCliPath,"status","--json"]);if(!G.ok)return!1;try{return JSON.parse(G.output).vehicles?.some(($)=>$.name===z)??!1}catch{return!1}}function B(){return{armadaCliPath:w(import.meta.resolve("@danypops/armada/cli")),runCommand:(z,J,G)=>{try{return{ok:!0,output:E(z,J,{encoding:"utf8",input:G,stdio:[G===void 0?"ignore":"pipe","pipe","pipe"]})}}catch(Z){let $=Z;return{ok:!1,output:($.stdout??"")+($.stderr??$.message)}}}}}class U extends Error{notADaemon;constructor(z,J){super(z);this.notADaemon=J;this.name="InstallServiceError"}}var u="pi-packed",F="pi-packed.service";function T(z={}){let J=z.env??process.env;if(J.PI_PACKED_HOME){let Z=k(J.PI_PACKED_HOME);return{token:`${Z}/token`,handle:`${Z}/handle.json`,serviceDescriptor:`${Z}/${F}`}}let G=q({stateDirectoryName:"pi-packed",databaseFilename:"packages.db",tokenFilename:"token",handleFilename:"handle.json",systemdUnitName:F},z);return{token:G.token,handle:G.handle,serviceDescriptor:G.serviceDescriptor}}function v(){return H(u,B())}function h(z,J=30000){return(G)=>z(new Request(G,{signal:AbortSignal.any([G.signal,AbortSignal.timeout(J)])}))}class V{rpc;constructor(z,J,G=fetch){this.rpc=new j(z,J,{label:"Packed",transport:h(G)})}call(z,J){return this.rpc.call(z,J)}health(){return this.rpc.health()}search(z,J,G=!1){return this.call("package.search",{query:z,limit:J,offline:G})}info(z){return this.call("package.info",{name:z})}installed(){return this.call("package.installed",{})}async updates(){return(await this.call("package.updates",{})).updates}security(){return this.call("package.security.get",{})}setMutationApproval(z,J=!1){return this.call("package.security.set",{mutationApproval:z,approved:J})}async install(z,J=!1){let G=await this.call("package.install",{source:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async installService(z,J=!1){let G=await this.call("package.install_service",{source:z,approved:J});if(!G.ok)throw new U(G.output,G.notADaemon);return{output:G.output,spec:G.spec}}async remove(z,J=!1){let G=await this.call("package.remove",{name:z,approved:J});if(!G.ok)throw Error(G.output);return G.output}async update(z,J=!1){let G=await this.call("package.update",{source:z,approved:J});if(!G.ok)throw Error(G.output);return{output:G.output,reloadRequired:G.reloadRequired??!0,alreadyUpToDate:G.alreadyUpToDate??!1,pinned:G.pinned??!1,previousVersion:G.previousVersion,currentVersion:G.currentVersion}}setupPlan(z,J=!1){return this.call("setup.plan",{manifestPath:z,prune:J})}setupApply(z,J=!1,G=!1){return this.call("setup.apply",{manifestPath:z,approved:J,prune:G})}listResources(z){return this.call("resources.list",{projectRoot:z})}async toggleResource(z,J,G,Z,$,K=!1){let Q=await this.call("resources.toggle",{source:z,field:J,path:G,enabled:Z,projectRoot:$,approved:K});if(!Q.ok)throw Error(Q.output);return Q.output}piStatus(){return this.call("pi.status",{})}}async function m(z=T(),J=fetch){let G=X(z.handle);if(!G)throw Error("Packed daemon is not running");let Z;try{Z=S(z.token,"utf8").trim()}catch{throw Error("Packed daemon token is unavailable")}if(!/^[a-f0-9]{64}$/.test(Z))throw Error("Packed daemon token is invalid");let $=new V(`http://${G.host}:${G.port}`,Z,J);return await $.health(),$}async function d(z){try{return await z.connect()}catch{}let J=z.retryAttempts??30,G=z.retryDelayMs??100,Z=z.isServiceInstalled();if(!Z)z.spawn();for(let K=0;K<J;K++){await z.sleep(G);try{return await z.connect()}catch{}}let $=J*G/1000;throw Error(Z?`The managed Packed service is registered but unreachable after ${$} seconds. Its supervisor may have stopped it or it may be failing; restart the managed service and inspect its status and logs. Packed will not auto-spawn a competing process.`:`Packed daemon did not become ready within ${$} seconds`)}function c(z=process.env){if(z.PI_PACKED_PI_BIN||z.PI_BIN)return;let J=z.PATH;if(!J)return;let G=process.platform==="win32"?["pi.exe","pi.cmd","pi.bat","pi"]:["pi"];for(let Z of J.split(I)){if(!Z)continue;for(let $ of G){let K=O(Z,$);try{return A(K,f.X_OK),K}catch{}}}return}async function Hz(z=T(),J=fetch){return d({connect:()=>m(z,J),isServiceInstalled:()=>v(),spawn:()=>{let G=process.env.PI_PACKED_BIN,Z=G??process.env.PI_PACKED_BUN??"bun",$=G?["serve"]:[O(y(P(import.meta.url)),"../service/src/cli/cli.ts"),"serve"],K=c(),Q=g(Z,$,{detached:!0,stdio:"ignore",env:{...process.env,DAEMON_KIT_LAUNCH_PROVENANCE:"auto-spawn",...K?{PI_BIN:K}:{}}});Q.once("error",()=>{}),Q.unref()},sleep:(G)=>new Promise((Z)=>setTimeout(Z,G))})}export{c as resolvePiBinForSpawn,T as resolvePackedClientPaths,Hz as ensurePackedClient,d as ensureClient,m as connectPackedClient,V as PackedClient,U as InstallServiceError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.21.9",
3
+ "version": "0.21.10",
4
4
  "description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
- import { dirname, join, resolve } from "node:path";
2
+ import { accessSync, constants as fsConstants, readFileSync } from "node:fs";
3
+ import { delimiter, dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { AuthenticatedRpcClient } from "@danypops/vehicle-client/rpc-client";
6
6
  import { readDaemonHandle, resolveDaemonPaths } from "@danypops/vehicle-server/paths";
@@ -244,6 +244,30 @@ export async function ensureClient(deps: EnsureClientDeps): Promise<PackedClient
244
244
  );
245
245
  }
246
246
 
247
+ /**
248
+ * An auto-spawned daemon is detached and long-lived, so it keeps whatever
249
+ * PATH its host process had at spawn time forever. Resolve `pi`'s absolute
250
+ * path now and pin it, rather than trust a bare "pi" to keep resolving in
251
+ * that frozen PATH. Leaves an explicit PI_PACKED_PI_BIN/PI_BIN untouched.
252
+ */
253
+ export function resolvePiBinForSpawn(env: NodeJS.ProcessEnv = process.env): string | undefined {
254
+ if (env.PI_PACKED_PI_BIN || env.PI_BIN) return undefined; // explicit override already present; nothing to pin
255
+ const path = env.PATH;
256
+ if (!path) return undefined;
257
+ const names = process.platform === "win32" ? ["pi.exe", "pi.cmd", "pi.bat", "pi"] : ["pi"];
258
+ for (const dir of path.split(delimiter)) {
259
+ if (!dir) continue;
260
+ for (const name of names) {
261
+ const candidate = join(dir, name);
262
+ try {
263
+ accessSync(candidate, fsConstants.X_OK);
264
+ return candidate;
265
+ } catch {}
266
+ }
267
+ }
268
+ return undefined;
269
+ }
270
+
247
271
  export async function ensurePackedClient(paths = resolvePackedClientPaths(), transport: FetchTransport = fetch): Promise<PackedClient> {
248
272
  return ensureClient({
249
273
  connect: () => connectPackedClient(paths, transport),
@@ -252,10 +276,15 @@ export async function ensurePackedClient(paths = resolvePackedClientPaths(), tra
252
276
  const override = process.env.PI_PACKED_BIN;
253
277
  const command = override ?? process.env.PI_PACKED_BUN ?? "bun";
254
278
  const args = override ? ["serve"] : [join(dirname(fileURLToPath(import.meta.url)), "../service/src/cli/cli.ts"), "serve"];
279
+ const resolvedPiBin = resolvePiBinForSpawn();
255
280
  const child = spawn(command, args, {
256
281
  detached: true,
257
282
  stdio: "ignore",
258
- env: { ...process.env, DAEMON_KIT_LAUNCH_PROVENANCE: "auto-spawn" },
283
+ env: {
284
+ ...process.env,
285
+ DAEMON_KIT_LAUNCH_PROVENANCE: "auto-spawn",
286
+ ...(resolvedPiBin ? { PI_BIN: resolvedPiBin } : {}),
287
+ },
259
288
  });
260
289
  child.once("error", () => {});
261
290
  child.unref();
@@ -1,5 +1,8 @@
1
+ import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  import { describe, expect, it } from "bun:test";
2
- import { ensureClient } from "../src/public/client.ts";
5
+ import { ensureClient, resolvePiBinForSpawn } from "../src/public/client.ts";
3
6
 
4
7
  describe("ensureClient (packed daemon auto-spawn-or-wait decision)", () => {
5
8
  it("connects immediately without ever checking for a service or spawning, when already reachable", async () => {
@@ -132,3 +135,41 @@ describe("ensureClient (packed daemon auto-spawn-or-wait decision)", () => {
132
135
  expect(serviceChecks).toBe(1);
133
136
  });
134
137
  });
138
+
139
+ describe("resolvePiBinForSpawn", () => {
140
+ it("leaves an explicit PI_PACKED_PI_BIN untouched", () => {
141
+ expect(resolvePiBinForSpawn({ PI_PACKED_PI_BIN: "/somewhere/pi", PATH: "/usr/bin" })).toBeUndefined();
142
+ });
143
+
144
+ it("leaves an explicit PI_BIN untouched", () => {
145
+ expect(resolvePiBinForSpawn({ PI_BIN: "/somewhere/pi", PATH: "/usr/bin" })).toBeUndefined();
146
+ });
147
+
148
+ it("returns undefined when PATH is missing", () => {
149
+ expect(resolvePiBinForSpawn({})).toBeUndefined();
150
+ });
151
+
152
+ it("returns undefined when no PATH directory has an executable `pi`", () => {
153
+ const dir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-"));
154
+ expect(resolvePiBinForSpawn({ PATH: dir })).toBeUndefined();
155
+ });
156
+
157
+ it("resolves the absolute path to an executable `pi` on PATH", () => {
158
+ const dir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-"));
159
+ const piPath = join(dir, "pi");
160
+ writeFileSync(piPath, "#!/bin/sh\necho pi\n");
161
+ chmodSync(piPath, 0o755);
162
+ expect(resolvePiBinForSpawn({ PATH: dir })).toBe(piPath);
163
+ });
164
+
165
+ it("skips a non-executable `pi` earlier on PATH and resolves a later one", () => {
166
+ const deadDir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-dead-"));
167
+ const liveDir = mkdtempSync(join(tmpdir(), "packed-resolve-pi-bin-live-"));
168
+ writeFileSync(join(deadDir, "pi"), "not executable");
169
+ chmodSync(join(deadDir, "pi"), 0o644);
170
+ const livePi = join(liveDir, "pi");
171
+ writeFileSync(livePi, "#!/bin/sh\necho pi\n");
172
+ chmodSync(livePi, 0o755);
173
+ expect(resolvePiBinForSpawn({ PATH: `${deadDir}:${liveDir}` })).toBe(livePi);
174
+ });
175
+ });