Now that the CLI and the Next.js app are one package, nesting it inside app/ served no purpose - the repo root itself becomes the published npm package. Merges app/.gitignore and app/README.md into the root versions, drops the now-duplicate app/LICENSE, and updates path references (README, docs/ARCHITECTURE.md, docs/CONFIG_REFERENCE.md, package.json's repository.directory) that assumed the app/ nesting. Also fixes a real bug this surfaced: the in-app docs viewer resolved docs/ relative to process.cwd(), which only worked by accident when the CLI happened to be invoked from app/'s parent directory. A first attempt at fixing it with import.meta.dirname broke instead, for the same cross-module-graph reason config-path resolution already documented - Next compiles Route Handlers through a separate module graph that doesn't preserve source-relative import.meta paths. Fixed by exposing the app root via TRIGGERSHELL_APP_ROOT (set once in server.ts, where import.meta *does* resolve correctly), the same pattern already used for TRIGGERSHELL_CONFIG_PATH. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
import net from "node:net";
|
|
|
|
export function isPortFree(host: string, port: number): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
const socket = net.connect({ host, port, timeout: 500 });
|
|
socket.once("connect", () => {
|
|
socket.destroy();
|
|
resolve(false);
|
|
});
|
|
socket.once("timeout", () => {
|
|
socket.destroy();
|
|
resolve(true);
|
|
});
|
|
socket.once("error", () => {
|
|
resolve(true);
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function waitUntilReady(
|
|
url: string,
|
|
timeoutMs: number,
|
|
intervalMs = 400,
|
|
): Promise<boolean> {
|
|
const deadline = Date.now() + timeoutMs;
|
|
while (Date.now() < deadline) {
|
|
try {
|
|
const response = await fetch(url, { signal: AbortSignal.timeout(1500) });
|
|
if (response.status === 200) return true;
|
|
} catch {
|
|
// not ready yet
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function openBrowser(url: string): void {
|
|
const command =
|
|
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
const args = process.platform === "win32" ? ["", url] : [url];
|
|
try {
|
|
spawn(command, args, { detached: true, stdio: "ignore", shell: process.platform === "win32" }).unref();
|
|
} catch {
|
|
// best-effort - not fatal if no browser opener is available
|
|
}
|
|
}
|