import * as Effect from "effect/Effect"; import { HostProcessArguments } from "@t3tools/shared/hostProcess"; import packageJson from "../../package.json" with { type: "json" }; export type CliRunner = "npx" | "pnpm dlx" | "bunx"; /** * How the CLI was launched, judged by where its entry script lives. Each * package runner executes out of a distinctive cache/temp layout: * * npx ~/.npm/_npx//node_modules/... * pnpm dlx ~/.cache/pnpm/dlx/..., $PNPM_HOME/.pnpm/dlx/..., * or %LOCALAPPDATA%/pnpm-cache/dlx/... on Windows * bunx ~/.bun/install/cache/... or $TMPDIR/bunx--/... * * Global installs and repo checkouts match none of these and return null. * Detection is best-effort; callers must fail closed to a plain `t3` command. */ export function detectCliRunner(entryPath: string): CliRunner | null { const path = entryPath.replaceAll("\\", "/"); if (path.includes("/_npx/")) { return "npx"; } if ( path.includes("/pnpm/dlx/") || path.includes("/.pnpm/dlx/") || path.includes("/pnpm-cache/dlx/") ) { return "pnpm dlx"; } if (path.includes("/.bun/install/cache/") || path.includes("/bunx-")) { return "bunx"; } return null; } /** * The `t3` package spec to suggest. The literal spec the user typed (e.g. * `t3@nightly`) is resolved away before our process starts, so re-derive it * from the running version: nightly builds re-suggest the nightly channel, * anything else suggests the bare package. */ export function suggestedPackageSpec(version: string): string { return version.includes("-nightly.") ? "t3@nightly" : "t3"; } /** * Render a `t3 ` suggestion that matches how this process was * launched, so copy/pasting it actually works: `npx t3 connect` suggests * `npx t3 serve`, a global install suggests `t3 serve`, and a nightly build * keeps the `@nightly` tag. */ export function formatCliCommand(input: { readonly subcommand: string; readonly entryPath: string; readonly version: string; }): string { const runner = detectCliRunner(input.entryPath); if (runner === null) { return `t3 ${input.subcommand}`; } return `${runner} ${suggestedPackageSpec(input.version)} ${input.subcommand}`; } /** `formatCliCommand` against this process's real entry path and version. */ export const resolveCliCommand = (subcommand: string) => Effect.map(HostProcessArguments, (processArguments) => formatCliCommand({ subcommand, entryPath: processArguments[1] ?? "", version: packageJson.version, }), );