// @effect-diagnostics nodeBuiltinImport:off globalFetchInEffect:off preferSchemaOverJson:off - verifies generated remote scripts using real shell and Node processes.import * as Effect from "effect/Effect";import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import { describe, expect, it } from "@effect/vitest";import * as NodeChildProcess from "node:child_process";import * as NodeFSP from "node:fs/promises";import * as NodeOS from "node:os";import * as NodePath from "node:path";import * as NodeUtil from "node:util";import { quoteRemoteArg, remoteDeviceEnvironment, remoteDeviceScript } from "./sshDeviceScript.ts";import { AGENT_DEVICE_VERSION, DEVICE_HUB_VERSION } from "./DeviceToolchain.ts"; const exec = NodeUtil.promisify(NodeChildProcess.execFile); it.effect("finds Android Studio Java for a non-interactive SSH session", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) === "win32") return; yield* Effect.promise(async () => { const home = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-ssh-java-")); try { const javaHome = NodePath.join(home, ".local/opt/android-studio/jbr"); await NodeFSP.mkdir(NodePath.join(javaHome, "bin"), { recursive: true }); await NodeFSP.writeFile( NodePath.join(javaHome, "bin/java"), "#!/bin/sh\necho test-java\n", { mode: 0o755 }, ); const result = await exec("/bin/sh", ["-c", `${remoteDeviceEnvironment}\njava`], { env: { HOME: home, PATH: "/nonexistent", JAVA_HOME: "" }, }); expect(result.stdout.trim()).toBe("test-java"); } finally { await NodeFSP.rm(home, { recursive: true, force: true }); } }); }),); it.effect("preserves shell metacharacters and newlines in remote arguments", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) === "win32") return; yield* Effect.promise(async () => { const value = "quotes ' \" ; $(echo expanded) $HOME\nnext line"; const result = await exec("sh", ["-c", `printf %s ${quoteRemoteArg(value)}`]); expect(result.stdout).toBe(value); }); }),); describe("remote helper lifecycle", () => { it.effect("reuses its own healthy helpers and stops only its own runtime", () => Effect.gen(function* () { if ((yield* HostProcessPlatform) === "win32") return; yield* Effect.promise(async () => { const home = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-remote-script-")); const bin = NodePath.join(home, "bin"); await NodeFSP.mkdir(bin); await NodeFSP.writeFile(NodePath.join(bin, "adb"), "#!/bin/sh\nexit 0\n", { mode: 0o755 }); const root = NodePath.join(home, ".t3/device"); const hubDir = NodePath.join(root, `tools/expo-device-hub@${DEVICE_HUB_VERSION}`); const agentDir = NodePath.join(root, `tools/agent-device@${AGENT_DEVICE_VERSION}`); const hub = NodePath.join(hubDir, "node_modules/expo-device-hub/dist/server/cli.mjs"); const agent = NodePath.join(agentDir, "node_modules/agent-device/bin/agent-device.mjs"); await NodeFSP.mkdir(NodePath.join(hubDir, "node_modules/expo-device-hub/dist/server"), { recursive: true, }); await NodeFSP.mkdir(NodePath.join(agentDir, "node_modules/agent-device/bin"), { recursive: true, }); await NodeFSP.writeFile(NodePath.join(hubDir, ".install-complete"), DEVICE_HUB_VERSION); await NodeFSP.writeFile(NodePath.join(agentDir, ".install-complete"), AGENT_DEVICE_VERSION); await NodeFSP.writeFile( hub, `import http from 'node:http'; import fs from 'node:fs';if(fs.existsSync('fail-start-once')) {fs.unlinkSync('fail-start-once');process.exit(1);}const args=process.argv.slice(2); http.createServer((req,res)=>{res.statusCode=fs.existsSync('unhealthy-'+process.pid)?503:200;res.end('ok');}).listen(Number(args[args.indexOf('--port')+1]),'127.0.0.1');`, ); await NodeFSP.writeFile( agent, `import fs from 'node:fs'; import path from 'node:path'; import http from 'node:http'; import {spawn} from 'node:child_process';const args=process.argv.slice(2);const state=process.env.AGENT_DEVICE_STATE_DIR || args[args.indexOf('--state-dir')+1];const file=path.join(state,'daemon.json');if(args[0]==='daemon') { const data=JSON.parse(fs.readFileSync(file,'utf8')); fs.writeFileSync(path.join(state,'stopped-agent'),String(data.pid)); try {process.kill(data.pid,'SIGTERM')} catch {} }else if(args[0]==='serve') { const server=http.createServer((req,res)=>{res.statusCode=fs.existsSync(path.join(state,'unhealthy-agent-'+process.pid))?503:200;res.end('ok');}); server.listen(0,'127.0.0.1',()=>{fs.writeFileSync(file,JSON.stringify({httpPort:server.address().port,pid:process.pid,token:'test'}));process.send?.('ready');process.disconnect?.();}); }else { const child=spawn(process.execPath,[process.argv[1],'serve'],{detached:true,stdio:['ignore','ignore','ignore','ipc'],env:process.env});await new Promise((resolve,reject)=>{child.once('message',resolve);child.once('error',reject);});child.unref(); }`, ); const nextHubVersion = DEVICE_HUB_VERSION + "-upgrade"; const nextAgentVersion = AGENT_DEVICE_VERSION + "-upgrade"; let invocation = 0; const invoke = async ( owner: string, mode: "start" | "agent-start" | "stop-agent" | "stop", upgraded = false, ) => { const file = NodePath.join(home, `${owner}-${mode}-${invocation++}.cjs`); await NodeFSP.writeFile( file, `const originalKill = process.kill; process.kill = (pid, signal) => { if (signal === 'SIGTERM') require('node:fs').appendFileSync(${JSON.stringify(NodePath.join(home, "stops"))}, pid+'\\n'); return originalKill(pid, signal); };\n` + remoteDeviceScript(owner, mode) .replace(DEVICE_HUB_VERSION, upgraded ? nextHubVersion : DEVICE_HUB_VERSION) .replace(AGENT_DEVICE_VERSION, upgraded ? nextAgentVersion : AGENT_DEVICE_VERSION), ); const result = await exec(process.execPath, [file], { env: { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` }, }); return result.stdout ? JSON.parse(result.stdout) : null; }; const template = NodePath.join(home, "hub-template"); await NodeFSP.cp(hubDir, template, { recursive: true }); await NodeFSP.rm(NodePath.join(hubDir, ".install-complete")); const installLock = hubDir + ".lock"; await NodeFSP.symlink("2147483647:exited-installer", installLock); await NodeFSP.writeFile( NodePath.join(bin, "npm"), `#!${process.execPath}\nconst fs=require('node:fs');const args=process.argv.slice(2);fs.cpSync(${JSON.stringify(template)},args[args.indexOf('--prefix')+1],{recursive:true});`, { mode: 0o755 }, ); await NodeFSP.mkdir(NodePath.join(root, "hosts/one"), { recursive: true }); await NodeFSP.writeFile(NodePath.join(root, "hosts/one/fail-start-once"), ""); try { const [manual, concurrent] = await Promise.all([ invoke("one", "start"), invoke("one", "start"), ]); expect(concurrent.hubPort).toBe(manual.hubPort); expect(manual.daemonPort).toBeUndefined(); await expect( NodeFSP.stat(NodePath.join(root, "hosts/one/daemon.json")), ).rejects.toThrow(); const [first, concurrentAgent] = await Promise.all([ invoke("one", "agent-start"), invoke("one", "agent-start"), ]); expect(concurrentAgent.hubPort).toBe(first.hubPort); expect(concurrentAgent.daemonPort).toBe(first.daemonPort); const second = await invoke("two", "agent-start"); const reused = await invoke("one", "agent-start"); expect(reused.hubPort).toBe(first.hubPort); expect(reused.daemonPort).toBe(first.daemonPort); expect(second.hubPort).not.toBe(first.hubPort); expect(second.daemonPort).not.toBe(first.daemonPort); const firstHub = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/one/hub.json"), "utf8"), ); const secondHub = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/two/hub.json"), "utf8"), ); await NodeFSP.writeFile(NodePath.join(root, `hosts/one/unhealthy-${firstHub.pid}`), ""); let repaired = await invoke("one", "agent-start"); expect(repaired.hubPort).not.toBe(first.hubPort); const stopped = (await NodeFSP.readFile(NodePath.join(home, "stops"), "utf8")) .trim() .split("\n"); expect(stopped).toContain(String(firstHub.pid)); expect(stopped).not.toContain(String(secondHub.pid)); const previousDaemon = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"), ); for (const [source, name, version] of [ [hubDir, "expo-device-hub", nextHubVersion], [agentDir, "agent-device", nextAgentVersion], ]) { const destination = NodePath.join(root, `tools/${name}@${version}`); await NodeFSP.cp(source!, destination, { recursive: true }); await NodeFSP.writeFile(NodePath.join(destination, ".install-complete"), version!); } const upgraded = await invoke("one", "agent-start", true); expect(upgraded.entryPath).toContain(nextAgentVersion); const upgradedHub = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/one/hub.json"), "utf8"), ); expect(upgradedHub.entryPath).toContain(nextHubVersion); const upgradedDaemon = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"), ); expect(upgradedDaemon.pid).not.toBe(previousDaemon.pid); expect(await invoke("one", "agent-start", true)).toEqual(upgraded); await NodeFSP.writeFile( NodePath.join(root, `hosts/one/unhealthy-agent-${upgradedDaemon.pid}`), "", ); repaired = await invoke("one", "agent-start", true); expect( await NodeFSP.readFile(NodePath.join(root, "hosts/one/stopped-agent"), "utf8"), ).toBe(String(upgradedDaemon.pid)); expect(repaired.daemonPort).not.toBe(upgraded.daemonPort); // Stop still uses the recorded entry when a future pinned package is not installed yet. const originalScript = remoteDeviceScript("one", "stop-agent"); const upgradedStop = NodePath.join(home, "upgraded-stop.cjs"); await NodeFSP.writeFile( upgradedStop, originalScript.replace(AGENT_DEVICE_VERSION, "999.0.0"), ); await exec(process.execPath, [upgradedStop], { env: { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` }, }); const daemon = JSON.parse( await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"), ); expect( await NodeFSP.readFile(NodePath.join(root, "hosts/one/stopped-agent"), "utf8"), ).toBe(String(daemon.pid)); expect((await fetch(`http://127.0.0.1:${repaired.hubPort}/readyz`)).ok).toBe(true); await invoke("one", "stop"); expect((await fetch(`http://127.0.0.1:${second.hubPort}/readyz`)).ok).toBe(true); expect( JSON.parse(await NodeFSP.readFile(NodePath.join(root, "hosts/two/hub.json"), "utf8")) .owner, ).toBe("two"); } finally { await invoke("one", "stop").catch(() => {}); await invoke("two", "stop").catch(() => {}); await NodeFSP.rm(home, { recursive: true, force: true }); } }); }), );});
// @effect-diagnostics nodeBuiltinImport:off globalFetchInEffect:off preferSchemaOverJson:off - verifies generated remote scripts using real shell and Node processes.
import * as Effect from "effect/Effect";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import { describe, expect, it } from "@effect/vitest";
import * as NodeChildProcess from "node:child_process";
import * as NodeFSP from "node:fs/promises";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeUtil from "node:util";
import { quoteRemoteArg, remoteDeviceEnvironment, remoteDeviceScript } from "./sshDeviceScript.ts";
import { AGENT_DEVICE_VERSION, DEVICE_HUB_VERSION } from "./DeviceToolchain.ts";
const exec = NodeUtil.promisify(NodeChildProcess.execFile);
it.effect("finds Android Studio Java for a non-interactive SSH session", () =>
Effect.gen(function* () {
if ((yield* HostProcessPlatform) === "win32") return;
yield* Effect.promise(async () => {
const home = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-ssh-java-"));
try {
const javaHome = NodePath.join(home, ".local/opt/android-studio/jbr");
await NodeFSP.mkdir(NodePath.join(javaHome, "bin"), { recursive: true });
await NodeFSP.writeFile(
NodePath.join(javaHome, "bin/java"),
"#!/bin/sh\necho test-java\n",
{ mode: 0o755 },
);
const result = await exec("/bin/sh", ["-c", `${remoteDeviceEnvironment}\njava`], {
env: { HOME: home, PATH: "/nonexistent", JAVA_HOME: "" },
});
expect(result.stdout.trim()).toBe("test-java");
} finally {
await NodeFSP.rm(home, { recursive: true, force: true });
}
});
}),
);
it.effect("preserves shell metacharacters and newlines in remote arguments", () =>
Effect.gen(function* () {
if ((yield* HostProcessPlatform) === "win32") return;
yield* Effect.promise(async () => {
const value = "quotes ' \" ; $(echo expanded) $HOME\nnext line";
const result = await exec("sh", ["-c", `printf %s ${quoteRemoteArg(value)}`]);
expect(result.stdout).toBe(value);
});
}),
);
describe("remote helper lifecycle", () => {
it.effect("reuses its own healthy helpers and stops only its own runtime", () =>
Effect.gen(function* () {
if ((yield* HostProcessPlatform) === "win32") return;
yield* Effect.promise(async () => {
const home = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-remote-script-"));
const bin = NodePath.join(home, "bin");
await NodeFSP.mkdir(bin);
await NodeFSP.writeFile(NodePath.join(bin, "adb"), "#!/bin/sh\nexit 0\n", { mode: 0o755 });
const root = NodePath.join(home, ".t3/device");
const hubDir = NodePath.join(root, `tools/expo-device-hub@${DEVICE_HUB_VERSION}`);
const agentDir = NodePath.join(root, `tools/agent-device@${AGENT_DEVICE_VERSION}`);
const hub = NodePath.join(hubDir, "node_modules/expo-device-hub/dist/server/cli.mjs");
const agent = NodePath.join(agentDir, "node_modules/agent-device/bin/agent-device.mjs");
await NodeFSP.mkdir(NodePath.join(hubDir, "node_modules/expo-device-hub/dist/server"), {
recursive: true,
});
await NodeFSP.mkdir(NodePath.join(agentDir, "node_modules/agent-device/bin"), {
recursive: true,
});
await NodeFSP.writeFile(NodePath.join(hubDir, ".install-complete"), DEVICE_HUB_VERSION);
await NodeFSP.writeFile(NodePath.join(agentDir, ".install-complete"), AGENT_DEVICE_VERSION);
await NodeFSP.writeFile(
hub,
`import http from 'node:http'; import fs from 'node:fs';
if(fs.existsSync('fail-start-once')) {fs.unlinkSync('fail-start-once');process.exit(1);}
const args=process.argv.slice(2); http.createServer((req,res)=>{res.statusCode=fs.existsSync('unhealthy-'+process.pid)?503:200;res.end('ok');}).listen(Number(args[args.indexOf('--port')+1]),'127.0.0.1');`,
);
await NodeFSP.writeFile(
agent,
`import fs from 'node:fs'; import path from 'node:path'; import http from 'node:http'; import {spawn} from 'node:child_process';
const args=process.argv.slice(2);
const state=process.env.AGENT_DEVICE_STATE_DIR || args[args.indexOf('--state-dir')+1];
const file=path.join(state,'daemon.json');
if(args[0]==='daemon') { const data=JSON.parse(fs.readFileSync(file,'utf8')); fs.writeFileSync(path.join(state,'stopped-agent'),String(data.pid)); try {process.kill(data.pid,'SIGTERM')} catch {} }
else if(args[0]==='serve') { const server=http.createServer((req,res)=>{res.statusCode=fs.existsSync(path.join(state,'unhealthy-agent-'+process.pid))?503:200;res.end('ok');}); server.listen(0,'127.0.0.1',()=>{fs.writeFileSync(file,JSON.stringify({httpPort:server.address().port,pid:process.pid,token:'test'}));process.send?.('ready');process.disconnect?.();}); }
else { const child=spawn(process.execPath,[process.argv[1],'serve'],{detached:true,stdio:['ignore','ignore','ignore','ipc'],env:process.env});await new Promise((resolve,reject)=>{child.once('message',resolve);child.once('error',reject);});child.unref(); }
`,
);
const nextHubVersion = DEVICE_HUB_VERSION + "-upgrade";
const nextAgentVersion = AGENT_DEVICE_VERSION + "-upgrade";
let invocation = 0;
const invoke = async (
owner: string,
mode: "start" | "agent-start" | "stop-agent" | "stop",
upgraded = false,
) => {
const file = NodePath.join(home, `${owner}-${mode}-${invocation++}.cjs`);
await NodeFSP.writeFile(
file,
`const originalKill = process.kill; process.kill = (pid, signal) => { if (signal === 'SIGTERM') require('node:fs').appendFileSync(${JSON.stringify(NodePath.join(home, "stops"))}, pid+'\\n'); return originalKill(pid, signal); };\n` +
remoteDeviceScript(owner, mode)
.replace(DEVICE_HUB_VERSION, upgraded ? nextHubVersion : DEVICE_HUB_VERSION)
.replace(AGENT_DEVICE_VERSION, upgraded ? nextAgentVersion : AGENT_DEVICE_VERSION),
);
const result = await exec(process.execPath, [file], {
env: { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` },
});
return result.stdout ? JSON.parse(result.stdout) : null;
};
const template = NodePath.join(home, "hub-template");
await NodeFSP.cp(hubDir, template, { recursive: true });
await NodeFSP.rm(NodePath.join(hubDir, ".install-complete"));
const installLock = hubDir + ".lock";
await NodeFSP.symlink("2147483647:exited-installer", installLock);
await NodeFSP.writeFile(
NodePath.join(bin, "npm"),
`#!${process.execPath}\nconst fs=require('node:fs');const args=process.argv.slice(2);fs.cpSync(${JSON.stringify(template)},args[args.indexOf('--prefix')+1],{recursive:true});`,
{ mode: 0o755 },
);
await NodeFSP.mkdir(NodePath.join(root, "hosts/one"), { recursive: true });
await NodeFSP.writeFile(NodePath.join(root, "hosts/one/fail-start-once"), "");
try {
const [manual, concurrent] = await Promise.all([
invoke("one", "start"),
invoke("one", "start"),
]);
expect(concurrent.hubPort).toBe(manual.hubPort);
expect(manual.daemonPort).toBeUndefined();
await expect(
NodeFSP.stat(NodePath.join(root, "hosts/one/daemon.json")),
).rejects.toThrow();
const [first, concurrentAgent] = await Promise.all([
invoke("one", "agent-start"),
invoke("one", "agent-start"),
]);
expect(concurrentAgent.hubPort).toBe(first.hubPort);
expect(concurrentAgent.daemonPort).toBe(first.daemonPort);
const second = await invoke("two", "agent-start");
const reused = await invoke("one", "agent-start");
expect(reused.hubPort).toBe(first.hubPort);
expect(reused.daemonPort).toBe(first.daemonPort);
expect(second.hubPort).not.toBe(first.hubPort);
expect(second.daemonPort).not.toBe(first.daemonPort);
const firstHub = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/hub.json"), "utf8"),
);
const secondHub = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/two/hub.json"), "utf8"),
);
await NodeFSP.writeFile(NodePath.join(root, `hosts/one/unhealthy-${firstHub.pid}`), "");
let repaired = await invoke("one", "agent-start");
expect(repaired.hubPort).not.toBe(first.hubPort);
const stopped = (await NodeFSP.readFile(NodePath.join(home, "stops"), "utf8"))
.trim()
.split("\n");
expect(stopped).toContain(String(firstHub.pid));
expect(stopped).not.toContain(String(secondHub.pid));
const previousDaemon = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"),
);
for (const [source, name, version] of [
[hubDir, "expo-device-hub", nextHubVersion],
[agentDir, "agent-device", nextAgentVersion],
]) {
const destination = NodePath.join(root, `tools/${name}@${version}`);
await NodeFSP.cp(source!, destination, { recursive: true });
await NodeFSP.writeFile(NodePath.join(destination, ".install-complete"), version!);
}
const upgraded = await invoke("one", "agent-start", true);
expect(upgraded.entryPath).toContain(nextAgentVersion);
const upgradedHub = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/hub.json"), "utf8"),
);
expect(upgradedHub.entryPath).toContain(nextHubVersion);
const upgradedDaemon = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"),
);
expect(upgradedDaemon.pid).not.toBe(previousDaemon.pid);
expect(await invoke("one", "agent-start", true)).toEqual(upgraded);
await NodeFSP.writeFile(
NodePath.join(root, `hosts/one/unhealthy-agent-${upgradedDaemon.pid}`),
"",
);
repaired = await invoke("one", "agent-start", true);
expect(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/stopped-agent"), "utf8"),
).toBe(String(upgradedDaemon.pid));
expect(repaired.daemonPort).not.toBe(upgraded.daemonPort);
// Stop still uses the recorded entry when a future pinned package is not installed yet.
const originalScript = remoteDeviceScript("one", "stop-agent");
const upgradedStop = NodePath.join(home, "upgraded-stop.cjs");
await NodeFSP.writeFile(
upgradedStop,
originalScript.replace(AGENT_DEVICE_VERSION, "999.0.0"),
);
await exec(process.execPath, [upgradedStop], {
env: { ...process.env, HOME: home, PATH: `${bin}:${process.env.PATH}` },
});
const daemon = JSON.parse(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/daemon.json"), "utf8"),
);
expect(
await NodeFSP.readFile(NodePath.join(root, "hosts/one/stopped-agent"), "utf8"),
).toBe(String(daemon.pid));
expect((await fetch(`http://127.0.0.1:${repaired.hubPort}/readyz`)).ok).toBe(true);
await invoke("one", "stop");
expect((await fetch(`http://127.0.0.1:${second.hubPort}/readyz`)).ok).toBe(true);
expect(
JSON.parse(await NodeFSP.readFile(NodePath.join(root, "hosts/two/hub.json"), "utf8"))
.owner,
).toBe("two");
} finally {
await invoke("one", "stop").catch(() => {});
await invoke("two", "stop").catch(() => {});
await NodeFSP.rm(home, { recursive: true, force: true });
}
});
}),
);
});