// @effect-diagnostics nodeBuiltinImport:off - CLI integration exercises Node HTTP and filesystem boundaries.import * as NodeHttp from "node:http";import * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices";import * as NetService from "@t3tools/shared/Net";import { assert, describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as TestConsole from "effect/testing/TestConsole";import { Command } from "effect/unstable/cli"; import { cli } from "../bin.ts";import { makePersistedServerRuntimeState, persistServerRuntimeState, type PersistedServerRuntimeState,} from "../serverRuntimeState.ts";import { DevServerNotProxiableError, resolveDirectPairingBaseUrl, resolveTailscaleLocalTarget,} from "./pair.ts"; const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer); const baseState = { version: 1, pid: 123, port: 3_773, origin: "http://127.0.0.1:3773", startedAt: "2026-06-20T00:00:00.000Z",} as const satisfies PersistedServerRuntimeState; describe("pair base URL selection", () => { it("pairs through the dev web origin when the server fronts a dev server", () => { expect(resolveDirectPairingBaseUrl({ ...baseState, devUrl: "http://localhost:5733/" })).toBe( "http://localhost:5733/", ); }); it("pairs through the bound host when there is no dev server", () => { expect(resolveDirectPairingBaseUrl({ ...baseState, host: "100.64.0.7" })).toBe( "http://100.64.0.7:3773", ); expect(resolveDirectPairingBaseUrl(baseState)).toBe("http://localhost:3773"); });}); describe("pair tailscale local target", () => { it("proxies the dev web port for dev servers", () => { expect(resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://localhost:5733/" })).toEqual( { localPort: 5_733 }, ); // A dev server on a non-loopback interface must be proxied at that // interface; tailscale serve defaults to 127.0.0.1 otherwise. expect( resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://192.168.1.10:5733/" }), ).toEqual({ localPort: 5_733, localHost: "192.168.1.10" }); // URL.hostname keeps IPv6 brackets, so the serve target stays valid. expect( resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://[fd7a:115c::1]:5733/" }), ).toEqual({ localPort: 5_733, localHost: "[fd7a:115c::1]" }); }); it("rejects HTTPS dev URLs, which tailscale serve cannot proxy", () => { expect( resolveTailscaleLocalTarget({ ...baseState, devUrl: "https://localhost:5733/" }), ).toBeInstanceOf(DevServerNotProxiableError); }); it("proxies the backend port directly otherwise", () => { expect(resolveTailscaleLocalTarget(baseState)).toEqual({ localPort: 3_773 }); expect(resolveTailscaleLocalTarget({ ...baseState, host: "0.0.0.0" })).toEqual({ localPort: 3_773, }); expect(resolveTailscaleLocalTarget({ ...baseState, host: "192.168.1.42" })).toEqual({ localPort: 3_773, localHost: "192.168.1.42", }); });}); const runCli = (args: ReadonlyArray<string>) => Command.runWith(cli, { version: "0.0.0" })(args); const provideCliTestLayers = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.provide(effect, Layer.mergeAll(CliRuntimeLayer, TestConsole.layer)); // Console output accumulates across CLI runs within a test, and each// Console.log call is one entry — so the latest command's output is the last// entry, even when it spans many lines.const captureStdout = <A, E, R>(effect: Effect.Effect<A, E, R>) => provideCliTestLayers( Effect.gen(function* () { yield* effect; return ( (yield* TestConsole.logLines).findLast( (line): line is string => typeof line === "string", ) ?? "" ); }), ); const testDescriptor = { environmentId: "pair-test-environment", label: "pair-test", platform: { os: "linux", arch: "x64" }, serverVersion: "0.0.1", capabilities: { repositoryIdentity: true },}; const withDescriptorServer = <A, E, R>(run: (origin: string) => Effect.Effect<A, E, R>) => Effect.acquireUseRelease( Effect.callback<NodeHttp.Server>((resume) => { const server = NodeHttp.createServer((request, response) => { if (request.url === "/.well-known/t3/environment") { response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify(testDescriptor)); return; } response.writeHead(404); response.end(); }); server.listen(0, "127.0.0.1", () => resume(Effect.succeed(server))); }), (server) => { const address = server.address(); if (address === null || typeof address === "string") { return Effect.die(new Error("Expected a TCP address")); } return run(`http://127.0.0.1:${String(address.port)}`); }, (server) => Effect.sync(() => server.close()), ); describe("t3 pair", () => { it.effect("mints a token and prints a QR pairing URL for a live server", () => withDescriptorServer((origin) => Effect.gen(function* () { const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-test-")); const port = Number(new URL(origin).port); const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json"); yield* persistServerRuntimeState({ path: statePath, state: yield* makePersistedServerRuntimeState({ config: { host: "127.0.0.1", devUrl: undefined }, port, }), }); const output = yield* captureStdout(runCli(["pair", "--base-dir", baseDir])); assert.include(output, `Pairing with pair-test (${origin})`); assert.include(output, `Pairing URL: ${origin}/pair#token=`); assert.isTrue(output.includes("█") || output.includes("▀") || output.includes("▄")); // Loopback origins are not reachable from a phone; the output must say so. assert.include(output, "only reachable from this machine"); const token = /#token=([A-Z2-9]+)/.exec(output)?.[1]; assert.isString(token); // The token must be in the same store the running server reads. const listed = yield* captureStdout( runCli(["auth", "pairing", "list", "--base-dir", baseDir, "--json"]), ); // @effect-diagnostics-next-line preferSchemaOverJson:off - CLI JSON output is decoded as a presentation DTO. const credentials = JSON.parse(listed) as ReadonlyArray<{ readonly label?: string }>; assert.equal(credentials.length, 1); assert.equal(credentials[0]?.label, "t3 pair"); }), ).pipe(Effect.provide(NodeServices.layer)), ); it.effect("pairs through the recorded dev web URL for dev servers", () => withDescriptorServer((origin) => Effect.gen(function* () { const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-dev-test-")); const port = Number(new URL(origin).port); const statePath = NodePath.join(baseDir, "dev", "server-runtime.json"); yield* persistServerRuntimeState({ path: statePath, state: yield* makePersistedServerRuntimeState({ config: { host: undefined, devUrl: new URL("http://localhost:5733") }, port, }), }); const output = yield* captureStdout(runCli(["pair", "--base-dir", baseDir])); assert.include(output, "Pairing URL: http://localhost:5733/pair#token="); }), ).pipe(Effect.provide(NodeServices.layer)), ); it.effect("directs to t3 serve or t3 connect when no server is running", () => Effect.gen(function* () { const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-none-test-")); const error = yield* provideCliTestLayers( runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip), ); const rendered = String( typeof error === "object" && error !== null && "cause" in error ? error.cause : error, ); assert.include(rendered, "No running T3 Code server found."); assert.include(rendered, "npx t3 serve"); assert.include(rendered, "npx t3 connect"); }).pipe(Effect.provide(NodeServices.layer)), ); it.effect("ignores runtime state whose recorded pid is no longer alive", () => withDescriptorServer((origin) => Effect.gen(function* () { const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-pid-test-")); const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json"); // The origin answers (another server reused the port), but the pid // that wrote this state file is dead — pairing must not mint a token // into the dead server's database. const state = yield* makePersistedServerRuntimeState({ config: { host: "127.0.0.1", devUrl: undefined }, port: Number(new URL(origin).port), }); yield* persistServerRuntimeState({ path: statePath, // pid 2**22 + 1 exceeds any default Linux/macOS pid range. state: { ...state, pid: 4_194_305 }, }); const error = yield* provideCliTestLayers( runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip), ); const rendered = String( typeof error === "object" && error !== null && "cause" in error ? error.cause : error, ); assert.include(rendered, "No running T3 Code server found."); }), ).pipe(Effect.provide(NodeServices.layer)), ); it.effect("ignores stale runtime state pointing at a dead server", () => Effect.gen(function* () { const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-stale-test-")); const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json"); // A port from the dynamic range with nothing listening: the probe fails // fast with ECONNREFUSED and discovery moves on. yield* persistServerRuntimeState({ path: statePath, state: yield* makePersistedServerRuntimeState({ config: { host: "127.0.0.1", devUrl: undefined }, port: 1, }), }); const error = yield* provideCliTestLayers( runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip), ); const rendered = String( typeof error === "object" && error !== null && "cause" in error ? error.cause : error, ); assert.include(rendered, "No running T3 Code server found."); }).pipe(Effect.provide(NodeServices.layer)), );});
// @effect-diagnostics nodeBuiltinImport:off - CLI integration exercises Node HTTP and filesystem boundaries.
import * as NodeHttp from "node:http";
import * as NodeFS from "node:fs";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as NetService from "@t3tools/shared/Net";
import { assert, describe, expect, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as TestConsole from "effect/testing/TestConsole";
import { Command } from "effect/unstable/cli";
import { cli } from "../bin.ts";
import {
makePersistedServerRuntimeState,
persistServerRuntimeState,
type PersistedServerRuntimeState,
} from "../serverRuntimeState.ts";
import {
DevServerNotProxiableError,
resolveDirectPairingBaseUrl,
resolveTailscaleLocalTarget,
} from "./pair.ts";
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);
const baseState = {
version: 1,
pid: 123,
port: 3_773,
origin: "http://127.0.0.1:3773",
startedAt: "2026-06-20T00:00:00.000Z",
} as const satisfies PersistedServerRuntimeState;
describe("pair base URL selection", () => {
it("pairs through the dev web origin when the server fronts a dev server", () => {
expect(resolveDirectPairingBaseUrl({ ...baseState, devUrl: "http://localhost:5733/" })).toBe(
"http://localhost:5733/",
);
});
it("pairs through the bound host when there is no dev server", () => {
expect(resolveDirectPairingBaseUrl({ ...baseState, host: "100.64.0.7" })).toBe(
"http://100.64.0.7:3773",
);
expect(resolveDirectPairingBaseUrl(baseState)).toBe("http://localhost:3773");
});
});
describe("pair tailscale local target", () => {
it("proxies the dev web port for dev servers", () => {
expect(resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://localhost:5733/" })).toEqual(
{ localPort: 5_733 },
);
// A dev server on a non-loopback interface must be proxied at that
// interface; tailscale serve defaults to 127.0.0.1 otherwise.
expect(
resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://192.168.1.10:5733/" }),
).toEqual({ localPort: 5_733, localHost: "192.168.1.10" });
// URL.hostname keeps IPv6 brackets, so the serve target stays valid.
expect(
resolveTailscaleLocalTarget({ ...baseState, devUrl: "http://[fd7a:115c::1]:5733/" }),
).toEqual({ localPort: 5_733, localHost: "[fd7a:115c::1]" });
});
it("rejects HTTPS dev URLs, which tailscale serve cannot proxy", () => {
expect(
resolveTailscaleLocalTarget({ ...baseState, devUrl: "https://localhost:5733/" }),
).toBeInstanceOf(DevServerNotProxiableError);
});
it("proxies the backend port directly otherwise", () => {
expect(resolveTailscaleLocalTarget(baseState)).toEqual({ localPort: 3_773 });
expect(resolveTailscaleLocalTarget({ ...baseState, host: "0.0.0.0" })).toEqual({
localPort: 3_773,
});
expect(resolveTailscaleLocalTarget({ ...baseState, host: "192.168.1.42" })).toEqual({
localPort: 3_773,
localHost: "192.168.1.42",
});
});
});
const runCli = (args: ReadonlyArray<string>) => Command.runWith(cli, { version: "0.0.0" })(args);
const provideCliTestLayers = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
Effect.provide(effect, Layer.mergeAll(CliRuntimeLayer, TestConsole.layer));
// Console output accumulates across CLI runs within a test, and each
// Console.log call is one entry — so the latest command's output is the last
// entry, even when it spans many lines.
const captureStdout = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
provideCliTestLayers(
Effect.gen(function* () {
yield* effect;
return (
(yield* TestConsole.logLines).findLast(
(line): line is string => typeof line === "string",
) ?? ""
);
}),
);
const testDescriptor = {
environmentId: "pair-test-environment",
label: "pair-test",
platform: { os: "linux", arch: "x64" },
serverVersion: "0.0.1",
capabilities: { repositoryIdentity: true },
};
const withDescriptorServer = <A, E, R>(run: (origin: string) => Effect.Effect<A, E, R>) =>
Effect.acquireUseRelease(
Effect.callback<NodeHttp.Server>((resume) => {
const server = NodeHttp.createServer((request, response) => {
if (request.url === "/.well-known/t3/environment") {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify(testDescriptor));
return;
}
response.writeHead(404);
response.end();
});
server.listen(0, "127.0.0.1", () => resume(Effect.succeed(server)));
}),
(server) => {
const address = server.address();
if (address === null || typeof address === "string") {
return Effect.die(new Error("Expected a TCP address"));
}
return run(`http://127.0.0.1:${String(address.port)}`);
},
(server) => Effect.sync(() => server.close()),
);
describe("t3 pair", () => {
it.effect("mints a token and prints a QR pairing URL for a live server", () =>
withDescriptorServer((origin) =>
Effect.gen(function* () {
const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-test-"));
const port = Number(new URL(origin).port);
const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json");
yield* persistServerRuntimeState({
path: statePath,
state: yield* makePersistedServerRuntimeState({
config: { host: "127.0.0.1", devUrl: undefined },
port,
}),
});
const output = yield* captureStdout(runCli(["pair", "--base-dir", baseDir]));
assert.include(output, `Pairing with pair-test (${origin})`);
assert.include(output, `Pairing URL: ${origin}/pair#token=`);
assert.isTrue(output.includes("█") || output.includes("▀") || output.includes("▄"));
// Loopback origins are not reachable from a phone; the output must say so.
assert.include(output, "only reachable from this machine");
const token = /#token=([A-Z2-9]+)/.exec(output)?.[1];
assert.isString(token);
// The token must be in the same store the running server reads.
const listed = yield* captureStdout(
runCli(["auth", "pairing", "list", "--base-dir", baseDir, "--json"]),
);
// @effect-diagnostics-next-line preferSchemaOverJson:off - CLI JSON output is decoded as a presentation DTO.
const credentials = JSON.parse(listed) as ReadonlyArray<{ readonly label?: string }>;
assert.equal(credentials.length, 1);
assert.equal(credentials[0]?.label, "t3 pair");
}),
).pipe(Effect.provide(NodeServices.layer)),
);
it.effect("pairs through the recorded dev web URL for dev servers", () =>
withDescriptorServer((origin) =>
Effect.gen(function* () {
const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-dev-test-"));
const port = Number(new URL(origin).port);
const statePath = NodePath.join(baseDir, "dev", "server-runtime.json");
yield* persistServerRuntimeState({
path: statePath,
state: yield* makePersistedServerRuntimeState({
config: { host: undefined, devUrl: new URL("http://localhost:5733") },
port,
}),
});
const output = yield* captureStdout(runCli(["pair", "--base-dir", baseDir]));
assert.include(output, "Pairing URL: http://localhost:5733/pair#token=");
}),
).pipe(Effect.provide(NodeServices.layer)),
);
it.effect("directs to t3 serve or t3 connect when no server is running", () =>
Effect.gen(function* () {
const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-none-test-"));
const error = yield* provideCliTestLayers(
runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip),
);
const rendered = String(
typeof error === "object" && error !== null && "cause" in error ? error.cause : error,
);
assert.include(rendered, "No running T3 Code server found.");
assert.include(rendered, "npx t3 serve");
assert.include(rendered, "npx t3 connect");
}).pipe(Effect.provide(NodeServices.layer)),
);
it.effect("ignores runtime state whose recorded pid is no longer alive", () =>
withDescriptorServer((origin) =>
Effect.gen(function* () {
const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-pid-test-"));
const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json");
// The origin answers (another server reused the port), but the pid
// that wrote this state file is dead — pairing must not mint a token
// into the dead server's database.
const state = yield* makePersistedServerRuntimeState({
config: { host: "127.0.0.1", devUrl: undefined },
port: Number(new URL(origin).port),
});
yield* persistServerRuntimeState({
path: statePath,
// pid 2**22 + 1 exceeds any default Linux/macOS pid range.
state: { ...state, pid: 4_194_305 },
});
const error = yield* provideCliTestLayers(
runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip),
);
const rendered = String(
typeof error === "object" && error !== null && "cause" in error ? error.cause : error,
);
assert.include(rendered, "No running T3 Code server found.");
}),
).pipe(Effect.provide(NodeServices.layer)),
);
it.effect("ignores stale runtime state pointing at a dead server", () =>
Effect.gen(function* () {
const baseDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-pair-stale-test-"));
const statePath = NodePath.join(baseDir, "userdata", "server-runtime.json");
// A port from the dynamic range with nothing listening: the probe fails
// fast with ECONNREFUSED and discovery moves on.
yield* persistServerRuntimeState({
path: statePath,
state: yield* makePersistedServerRuntimeState({
config: { host: "127.0.0.1", devUrl: undefined },
port: 1,
}),
});
const error = yield* provideCliTestLayers(
runCli(["pair", "--base-dir", baseDir]).pipe(Effect.flip),
);
const rendered = String(
typeof error === "object" && error !== null && "cause" in error ? error.cause : error,
);
assert.include(rendered, "No running T3 Code server found.");
}).pipe(Effect.provide(NodeServices.layer)),
);
});