Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions packages/ssh/src/runnerProcess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,143 @@ if (args.includes("--package")) {
);
},
);

describe.skipIf(HostProcessPlatform.defaultValue() === "win32")(
"remote runner install diagnostics",
() => {
const decodeArguments = Schema.decodeUnknownSync(
Schema.fromJsonString(Schema.Array(Schema.String)),
);
const cases = (["npx", "npm"] as const).flatMap((packageManager) =>
(
[
"etarget",
"network",
"empty-success",
"success",
"failed-with-path",
"existing-cli",
"node-override",
] as const
).map((mode) => ({ packageManager, mode })),
);

it.live.each(cases)("handles $packageManager/$mode", ({ packageManager, mode }) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const fixture = yield* fs.makeTempDirectoryScoped({ prefix: "t3-runner-install-" });
const bin = path.join(fixture, "bin");
const cliPath = path.join(fixture, "installed cli.mjs");
const callsPath = path.join(fixture, "installer-calls.jsonl");
const packageSpec = "t3@0.0.39-nightly.20260905.1286";
const args = ["serve", "a path with spaces"];
yield* fs.makeDirectory(bin);
yield* fs.symlink(process.execPath, path.join(bin, "node"));
yield* fs.writeFileString(
cliPath,
`#!/usr/bin/env node
process.stdout.write(JSON.stringify(process.argv.slice(2)) + "\\n");
`,
);
yield* fs.chmod(cliPath, 0o700);
yield* fs.writeFileString(callsPath, "");
yield* fs.writeFileString(
path.join(bin, packageManager),
`#!/usr/bin/env node
const fs = require("node:fs");
fs.appendFileSync(process.env.T3_TEST_CALLS, JSON.stringify(process.argv.slice(2)) + "\\n");
const mode = process.env.T3_TEST_MODE;
if (mode === "success" || mode === "failed-with-path") {
process.stdout.write(process.env.T3_TEST_CLI + "\\n");
}
if (mode === "etarget" || mode === "failed-with-path") {
process.stderr.write("npm error code ETARGET\\nnpm error notarget No matching version found.\\n");
process.exitCode = 42;
} else if (mode === "network") {
process.stderr.write("npm error code ENETUNREACH\\n");
process.exitCode = 43;
}
`,
);
yield* fs.chmod(path.join(bin, packageManager), 0o700);
if (mode === "existing-cli") yield* fs.symlink(cliPath, path.join(bin, "t3"));

const child = yield* spawner.spawn(
ChildProcess.make("/bin/sh", ["-s", "--", ...args], {
cwd: fixture,
extendEnv: false,
env: {
PATH: bin,
T3_TEST_MODE: mode,
T3_TEST_CLI: cliPath,
T3_TEST_CALLS: callsPath,
},
stdin: Stream.make(
new TextEncoder().encode(
buildRemoteT3RunnerScript({
packageSpec,
...(mode === "node-override" ? { nodeScriptPath: cliPath } : {}),
}),
),
),
}),
);
const { stdout, stderr, exitCode } = yield* Effect.all(
{
stdout: child.stdout.pipe(Stream.decodeText(), Stream.mkString),
stderr: child.stderr.pipe(Stream.decodeText(), Stream.mkString),
exitCode: child.exitCode,
},
{ concurrency: "unbounded" },
);
const installFailed =
mode === "etarget" || mode === "network" || mode === "failed-with-path";
const missingExecutable = mode === "empty-success";
assert.equal(exitCode, installFailed || missingExecutable ? 1 : 0);
if (installFailed || missingExecutable) {
assert.equal(stdout, "");
} else {
assert.deepEqual(decodeArguments(stdout), args);
}
if (installFailed) {
const npmError = mode === "network" ? "ENETUNREACH" : "ETARGET";
assert.include(stderr, `npm error code ${npmError}\n`);
assert.include(stderr, `Remote host could not install ${packageSpec}.`);
assert.notInclude(stderr, "Remote host installed");
assert.notInclude(stderr, "Install a C toolchain");
} else if (missingExecutable) {
assert.include(stderr, `Remote host installed ${packageSpec}`);
assert.include(stderr, "npm produced no t3 executable");
assert.include(stderr, "Install a C toolchain");
} else {
assert.equal(stderr, "");
}
const expectedCall = [
...(packageManager === "npm" ? ["exec"] : []),
"--yes",
"--package",
packageSpec,
"--",
"sh",
"-c",
"command -v t3",
];
const usesInstaller = mode !== "existing-cli" && mode !== "node-override";
const calls = yield* fs.readFileString(callsPath);
if (usesInstaller) {
assert.deepEqual(
calls
.trim()
.split("\n")
.map((line) => decodeArguments(line)),
[expectedCall],
);
} else {
assert.equal(calls, "");
}
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
);
},
);
5 changes: 4 additions & 1 deletion packages/ssh/src/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,10 @@ fi
# never becomes ready. Resolve the CLI once up front so that install failure is
# reported here, with npm's own output on stderr.
require_installed_t3_cli() {
T3_CLI_PATH="$("$@" -- sh -c 'command -v t3' || true)"
if ! T3_CLI_PATH="$("$@" -- sh -c 'command -v t3')"; then
printf 'Remote host could not install %s. See npm output above for the cause.\\n' @@T3_PACKAGE_SPEC@@ >&2
return 1
fi
if [ -n "$T3_CLI_PATH" ]; then
return 0
fi
Expand Down
Loading