/** * Open - Browser/editor launch service interface. * * Owns process launch helpers for opening URLs in a browser, workspace paths in * a configured editor, and generic external targets through the platform's * default opener. * * @module Open */import OS from "node:os";import { spawn as spawnNodeChildProcess } from "node:child_process";import { EDITORS, type EditorId } from "@t3tools/contracts";import { Array, Effect, FileSystem, Layer, Option, Path, Scope } from "effect";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";import { LINE_COLUMN_SUFFIX_PATTERN, makeWindowsEditorProtocolTarget } from "../../editorProtocol";import { isWindowsBatchShim, makeWindowsCmdSpawnArguments, resolveWindowsCommandShell,} from "../windowsCommand";import { DesktopLauncherCommandNotFoundError, DesktopLauncherDiscoveryError, DesktopLauncherLaunchAttemptsExhaustedError, DesktopLauncherNonZeroExitError, DesktopLauncherSpawnError, DesktopLauncherUnknownEditorError, DesktopLauncherValidationError, DesktopLauncher, type DesktopLauncherShape, type OpenApplicationInput, type OpenExternalInput,} from "../Services/DesktopLauncher"; export interface DetachedSpawnInput { readonly command: string; readonly args: ReadonlyArray<string>; readonly detached?: boolean; readonly shell?: boolean; readonly windowsVerbatimArguments?: boolean; readonly windowsHide?: boolean; readonly stdin?: "ignore"; readonly stdout?: "ignore"; readonly stderr?: "ignore";} type DesktopLauncherOperation = | "getAvailableEditors" | "openBrowser" | "openExternal" | "openInEditor"; interface LaunchContext { readonly operation: DesktopLauncherOperation; readonly target?: string; readonly editor?: EditorId;} export interface LaunchRuntimeOptions { readonly platform?: NodeJS.Platform; readonly env?: NodeJS.ProcessEnv; readonly isWsl?: boolean; readonly isInsideContainer?: boolean; readonly powerShellCommand?: string; readonly spawnDetached?: ( input: DetachedSpawnInput, context: LaunchContext, ) => Effect.Effect<void, DesktopLauncherSpawnError>;} interface LaunchRuntime { readonly platform: NodeJS.Platform; readonly env: NodeJS.ProcessEnv; readonly isWsl: boolean; readonly isInsideContainer: boolean; readonly powerShellCandidates: ReadonlyArray<string>; readonly windowsPathExtensions: ReadonlyArray<string>;} interface LaunchPlan { readonly command: string; readonly args: ReadonlyArray<string>; readonly wait: boolean; readonly allowNonzeroExitCode: boolean; readonly detached?: boolean; readonly shell?: boolean; readonly stdio?: "ignore";} interface ResolvedCommand { readonly path: string; readonly usesCmdWrapper: boolean;} interface OpenApplicationCandidate { readonly name: string; readonly arguments: ReadonlyArray<string>;} interface LaunchAttemptFailure { readonly command: string; readonly args: ReadonlyArray<string>; readonly reason: "commandNotFound" | "spawnFailed" | "nonZeroExit"; readonly detail: string; readonly exitCode?: number;} type LaunchAttemptError = | DesktopLauncherCommandNotFoundError | DesktopLauncherNonZeroExitError | DesktopLauncherSpawnError; type LaunchPlanError = LaunchAttemptError | DesktopLauncherLaunchAttemptsExhaustedError; const WINDOWS_POWERSHELL_CANDIDATES = ["powershell.exe", "powershell", "pwsh.exe", "pwsh"] as const;const WSL_POWERSHELL_CANDIDATES = [ "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", "/mnt/c/Program Files/PowerShell/7/pwsh.exe", "powershell.exe", "pwsh.exe",] as const;function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean { return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target);} function stripWrappingQuotes(value: string): string { return value.replace(/^"+|"+$/g, "");} function resolvePathEnvironmentVariable(env: NodeJS.ProcessEnv): string { return env.PATH ?? env.Path ?? env.path ?? "";} function resolveWindowsPathExtensions(env: NodeJS.ProcessEnv): ReadonlyArray<string> { const rawValue = env.PATHEXT; const fallback = [".COM", ".EXE", ".BAT", ".CMD"]; if (!rawValue) return fallback; const parsed = rawValue .split(";") .map((entry) => entry.trim()) .filter((entry) => entry.length > 0) .map((entry) => (entry.startsWith(".") ? entry.toUpperCase() : `.${entry.toUpperCase()}`)); return parsed.length > 0 ? Array.dedupe(parsed) : fallback;} function resolveCommandCandidates( command: string, runtime: LaunchRuntime, pathService: Path.Path,): ReadonlyArray<string> { if (runtime.platform !== "win32") return [command]; const extension = pathService.extname(command); const normalizedExtension = extension.toUpperCase(); if (extension.length > 0 && runtime.windowsPathExtensions.includes(normalizedExtension)) { const commandWithoutExtension = command.slice(0, -extension.length); return Array.dedupe([ command, `${commandWithoutExtension}${normalizedExtension}`, `${commandWithoutExtension}${normalizedExtension.toLowerCase()}`, ]); } const candidates: string[] = []; for (const extensionName of runtime.windowsPathExtensions) { candidates.push(`${command}${extensionName}`); candidates.push(`${command}${extensionName.toLowerCase()}`); } return Array.dedupe(candidates);} function resolvePathDelimiter(platform: NodeJS.Platform): string { return platform === "win32" ? ";" : ":";} function detectWsl(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): boolean { if (platform !== "linux") return false; if (typeof env.WSL_DISTRO_NAME === "string" || typeof env.WSL_INTEROP === "string") { return true; } return OS.release().toLowerCase().includes("microsoft");} function quotePowerShellValue(value: string): string { return `'${value.replaceAll("'", "''")}'`;} function quotePowerShellArgument(value: string): string { return `"\`"${value.replaceAll("`", "``").replaceAll('"', '`"')}\`""`;} function encodePowerShellCommand(command: string): string { return Buffer.from(command, "utf16le").toString("base64");} function normalizeAppCandidates( app: OpenApplicationInput | ReadonlyArray<OpenApplicationInput> | undefined,): ReadonlyArray<OpenApplicationCandidate | undefined> { if (!app) return [undefined]; const apps = Array.ensure(app); const candidates: Array<OpenApplicationCandidate> = []; for (const appDef of apps) { const names = Array.ensure(appDef.name); for (const name of names) { candidates.push({ name, arguments: appDef.arguments ?? [] }); } } return candidates.length > 0 ? candidates : [undefined];} function isUriLikeTarget(target: string): boolean { return /^[A-Za-z][A-Za-z\d+.-]*:/.test(target);} function shouldPreferWindowsOpenerOnWsl(input: OpenExternalInput, runtime: LaunchRuntime): boolean { return runtime.isWsl && !runtime.isInsideContainer && isUriLikeTarget(input.target);} function makeLaunchPlan( command: string, args: ReadonlyArray<string>, options: { readonly wait: boolean; readonly allowNonzeroExitCode: boolean; readonly detached?: boolean; readonly shell?: boolean; readonly stdio?: "ignore"; },): LaunchPlan { return { command, args, wait: options.wait, allowNonzeroExitCode: options.allowNonzeroExitCode, shell: options.shell ?? false, ...(options.detached !== undefined ? { detached: options.detached } : {}), ...(options.stdio !== undefined ? { stdio: options.stdio } : {}), };} function makeDarwinDefaultPlan(input: OpenExternalInput): LaunchPlan { const args: string[] = []; const wait = input.wait ?? false; if (wait) args.push("--wait-apps"); if (input.background) args.push("--background"); if (input.newInstance) args.push("--new"); args.push(input.target); return makeLaunchPlan("open", args, { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, });} function makeDarwinApplicationPlan( input: OpenExternalInput, app: OpenApplicationCandidate,): LaunchPlan { const args: string[] = []; const wait = input.wait ?? false; if (wait) args.push("--wait-apps"); if (input.background) args.push("--background"); if (input.newInstance) args.push("--new"); args.push("-a", app.name); args.push(input.target); if (app.arguments.length > 0) { args.push("--args", ...app.arguments); } return makeLaunchPlan("open", args, { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, });} function makePowerShellPlan(input: OpenExternalInput, powerShellCommand: string): LaunchPlan { const encodedParts = ["Start"]; const wait = input.wait ?? false; if (wait) encodedParts.push("-Wait"); encodedParts.push(quotePowerShellValue(input.target)); return makeLaunchPlan( powerShellCommand, [ "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShellCommand(encodedParts.join(" ")), ], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, }, );} function makePowerShellStartProcessArgs( commandPath: string, args: ReadonlyArray<string>,): ReadonlyArray<string> { const argumentList = args.length > 0 ? ` -ArgumentList ${args.map(quotePowerShellArgument).join(",")}` : ""; const command = `Start ${quotePowerShellArgument(commandPath)}${argumentList} -WindowStyle Hidden`; return [ "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShellCommand(command), ];} function makeLinuxDefaultPlan(input: OpenExternalInput): LaunchPlan { const wait = input.wait ?? false; return makeLaunchPlan("xdg-open", [input.target], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, detached: !wait, ...(wait ? {} : { stdio: "ignore" as const }), shell: false, });} function makeWindowsExplorerPlan(input: OpenExternalInput): LaunchPlan { return makeLaunchPlan("explorer", [input.target], { wait: false, allowNonzeroExitCode: false, detached: true, stdio: "ignore", shell: false, });} function makeDirectApplicationPlan( input: OpenExternalInput, app: OpenApplicationCandidate,): LaunchPlan { const wait = input.wait ?? false; return makeLaunchPlan(app.name, [...app.arguments, input.target], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, detached: !wait, ...(wait ? {} : { stdio: "ignore" as const }), shell: false, });} function resolveExternalPlans( input: OpenExternalInput, runtime: LaunchRuntime,): ReadonlyArray<LaunchPlan> { const appCandidates = normalizeAppCandidates(input.app); const plans: LaunchPlan[] = []; const preferWindowsOpenerOnWsl = shouldPreferWindowsOpenerOnWsl(input, runtime); for (const app of appCandidates) { if (app) { if (runtime.platform === "darwin") { plans.push(makeDarwinApplicationPlan(input, app)); } else { plans.push(makeDirectApplicationPlan(input, app)); } continue; } if (runtime.platform === "darwin") { plans.push(makeDarwinDefaultPlan(input)); continue; } if (runtime.platform === "win32" || preferWindowsOpenerOnWsl) { for (const powerShellCommand of runtime.powerShellCandidates) { plans.push(makePowerShellPlan(input, powerShellCommand)); } } if (runtime.platform === "win32") { if (!(input.wait ?? false)) { plans.push(makeWindowsExplorerPlan(input)); } continue; } plans.push(makeLinuxDefaultPlan(input)); } return plans;} function resolveSpawnInput( runtime: LaunchRuntime, plan: LaunchPlan, resolvedCommand: ResolvedCommand, startProcessLauncher: string | undefined,): DetachedSpawnInput { if (plan.detached && resolvedCommand.usesCmdWrapper && startProcessLauncher !== undefined) { return { command: startProcessLauncher, args: makePowerShellStartProcessArgs(resolvedCommand.path, plan.args), ...(plan.detached !== undefined ? { detached: plan.detached } : {}), ...(plan.shell !== undefined ? { shell: plan.shell } : {}), ...(runtime.platform === "win32" ? { windowsVerbatimArguments: true, windowsHide: true } : {}), ...(plan.stdio === "ignore" ? { stdin: "ignore" as const, stdout: "ignore" as const, stderr: "ignore" as const, } : {}), }; } const windowsHide = runtime.platform === "win32" && plan.detached ? true : undefined; return { command: resolvedCommand.usesCmdWrapper ? resolveWindowsCommandShell(runtime.env) : resolvedCommand.path, args: resolvedCommand.usesCmdWrapper ? makeWindowsCmdSpawnArguments(resolvedCommand.path, plan.args) : [...plan.args], ...(plan.detached !== undefined ? { detached: plan.detached } : {}), ...(plan.shell !== undefined ? { shell: plan.shell } : {}), ...(windowsHide ? { windowsHide } : {}), ...(resolvedCommand.usesCmdWrapper ? { windowsVerbatimArguments: true } : {}), ...(plan.stdio === "ignore" ? { stdin: "ignore" as const, stdout: "ignore" as const, stderr: "ignore" as const, } : {}), };} function makeCommandNotFoundError( context: LaunchContext, command: string,): DesktopLauncherCommandNotFoundError { return new DesktopLauncherCommandNotFoundError({ operation: context.operation, command, ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), });} function makeSpawnError( context: LaunchContext, command: string, args: ReadonlyArray<string>, cause?: unknown,): DesktopLauncherSpawnError { return new DesktopLauncherSpawnError({ operation: context.operation, command, args: [...args], ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), ...(cause !== undefined ? { cause } : {}), });} function makeNonZeroExitError( context: LaunchContext, command: string, args: ReadonlyArray<string>, exitCode: number,): DesktopLauncherNonZeroExitError { return new DesktopLauncherNonZeroExitError({ operation: context.operation, command, args: [...args], exitCode, ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), });} function toLaunchAttemptFailure(error: LaunchAttemptError): LaunchAttemptFailure { switch (error._tag) { case "DesktopLauncherCommandNotFoundError": return { command: error.command, args: [], reason: "commandNotFound",Lines 1–500 of 959. Load more
/** * Open - Browser/editor launch service interface. * * Owns process launch helpers for opening URLs in a browser, workspace paths in * a configured editor, and generic external targets through the platform's * default opener. * * @module Open */ import OS from "node:os"; import { spawn as spawnNodeChildProcess } from "node:child_process"; import { EDITORS, type EditorId } from "@t3tools/contracts"; import { Array, Effect, FileSystem, Layer, Option, Path, Scope } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { LINE_COLUMN_SUFFIX_PATTERN, makeWindowsEditorProtocolTarget } from "../../editorProtocol"; import { isWindowsBatchShim, makeWindowsCmdSpawnArguments, resolveWindowsCommandShell, } from "../windowsCommand"; import { DesktopLauncherCommandNotFoundError, DesktopLauncherDiscoveryError, DesktopLauncherLaunchAttemptsExhaustedError, DesktopLauncherNonZeroExitError, DesktopLauncherSpawnError, DesktopLauncherUnknownEditorError, DesktopLauncherValidationError, DesktopLauncher, type DesktopLauncherShape, type OpenApplicationInput, type OpenExternalInput, } from "../Services/DesktopLauncher"; export interface DetachedSpawnInput { readonly command: string; readonly args: ReadonlyArray<string>; readonly detached?: boolean; readonly shell?: boolean; readonly windowsVerbatimArguments?: boolean; readonly windowsHide?: boolean; readonly stdin?: "ignore"; readonly stdout?: "ignore"; readonly stderr?: "ignore"; } type DesktopLauncherOperation = | "getAvailableEditors" | "openBrowser" | "openExternal" | "openInEditor"; interface LaunchContext { readonly operation: DesktopLauncherOperation; readonly target?: string; readonly editor?: EditorId; } export interface LaunchRuntimeOptions { readonly platform?: NodeJS.Platform; readonly env?: NodeJS.ProcessEnv; readonly isWsl?: boolean; readonly isInsideContainer?: boolean; readonly powerShellCommand?: string; readonly spawnDetached?: ( input: DetachedSpawnInput, context: LaunchContext, ) => Effect.Effect<void, DesktopLauncherSpawnError>; } interface LaunchRuntime { readonly platform: NodeJS.Platform; readonly env: NodeJS.ProcessEnv; readonly isWsl: boolean; readonly isInsideContainer: boolean; readonly powerShellCandidates: ReadonlyArray<string>; readonly windowsPathExtensions: ReadonlyArray<string>; } interface LaunchPlan { readonly command: string; readonly args: ReadonlyArray<string>; readonly wait: boolean; readonly allowNonzeroExitCode: boolean; readonly detached?: boolean; readonly shell?: boolean; readonly stdio?: "ignore"; } interface ResolvedCommand { readonly path: string; readonly usesCmdWrapper: boolean; } interface OpenApplicationCandidate { readonly name: string; readonly arguments: ReadonlyArray<string>; } interface LaunchAttemptFailure { readonly command: string; readonly args: ReadonlyArray<string>; readonly reason: "commandNotFound" | "spawnFailed" | "nonZeroExit"; readonly detail: string; readonly exitCode?: number; } type LaunchAttemptError = | DesktopLauncherCommandNotFoundError | DesktopLauncherNonZeroExitError | DesktopLauncherSpawnError; type LaunchPlanError = LaunchAttemptError | DesktopLauncherLaunchAttemptsExhaustedError; const WINDOWS_POWERSHELL_CANDIDATES = ["powershell.exe", "powershell", "pwsh.exe", "pwsh"] as const; const WSL_POWERSHELL_CANDIDATES = [ "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", "/mnt/c/Program Files/PowerShell/7/pwsh.exe", "powershell.exe", "pwsh.exe", ] as const; function shouldUseGotoFlag(editor: (typeof EDITORS)[number], target: string): boolean { return editor.supportsGoto && LINE_COLUMN_SUFFIX_PATTERN.test(target); } function stripWrappingQuotes(value: string): string { return value.replace(/^"+|"+$/g, ""); } function resolvePathEnvironmentVariable(env: NodeJS.ProcessEnv): string { return env.PATH ?? env.Path ?? env.path ?? ""; } function resolveWindowsPathExtensions(env: NodeJS.ProcessEnv): ReadonlyArray<string> { const rawValue = env.PATHEXT; const fallback = [".COM", ".EXE", ".BAT", ".CMD"]; if (!rawValue) return fallback; const parsed = rawValue .split(";") .map((entry) => entry.trim()) .filter((entry) => entry.length > 0) .map((entry) => (entry.startsWith(".") ? entry.toUpperCase() : `.${entry.toUpperCase()}`)); return parsed.length > 0 ? Array.dedupe(parsed) : fallback; } function resolveCommandCandidates( command: string, runtime: LaunchRuntime, pathService: Path.Path, ): ReadonlyArray<string> { if (runtime.platform !== "win32") return [command]; const extension = pathService.extname(command); const normalizedExtension = extension.toUpperCase(); if (extension.length > 0 && runtime.windowsPathExtensions.includes(normalizedExtension)) { const commandWithoutExtension = command.slice(0, -extension.length); return Array.dedupe([ command, `${commandWithoutExtension}${normalizedExtension}`, `${commandWithoutExtension}${normalizedExtension.toLowerCase()}`, ]); } const candidates: string[] = []; for (const extensionName of runtime.windowsPathExtensions) { candidates.push(`${command}${extensionName}`); candidates.push(`${command}${extensionName.toLowerCase()}`); } return Array.dedupe(candidates); } function resolvePathDelimiter(platform: NodeJS.Platform): string { return platform === "win32" ? ";" : ":"; } function detectWsl(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): boolean { if (platform !== "linux") return false; if (typeof env.WSL_DISTRO_NAME === "string" || typeof env.WSL_INTEROP === "string") { return true; } return OS.release().toLowerCase().includes("microsoft"); } function quotePowerShellValue(value: string): string { return `'${value.replaceAll("'", "''")}'`; } function quotePowerShellArgument(value: string): string { return `"\`"${value.replaceAll("`", "``").replaceAll('"', '`"')}\`""`; } function encodePowerShellCommand(command: string): string { return Buffer.from(command, "utf16le").toString("base64"); } function normalizeAppCandidates( app: OpenApplicationInput | ReadonlyArray<OpenApplicationInput> | undefined, ): ReadonlyArray<OpenApplicationCandidate | undefined> { if (!app) return [undefined]; const apps = Array.ensure(app); const candidates: Array<OpenApplicationCandidate> = []; for (const appDef of apps) { const names = Array.ensure(appDef.name); for (const name of names) { candidates.push({ name, arguments: appDef.arguments ?? [] }); } } return candidates.length > 0 ? candidates : [undefined]; } function isUriLikeTarget(target: string): boolean { return /^[A-Za-z][A-Za-z\d+.-]*:/.test(target); } function shouldPreferWindowsOpenerOnWsl(input: OpenExternalInput, runtime: LaunchRuntime): boolean { return runtime.isWsl && !runtime.isInsideContainer && isUriLikeTarget(input.target); } function makeLaunchPlan( command: string, args: ReadonlyArray<string>, options: { readonly wait: boolean; readonly allowNonzeroExitCode: boolean; readonly detached?: boolean; readonly shell?: boolean; readonly stdio?: "ignore"; }, ): LaunchPlan { return { command, args, wait: options.wait, allowNonzeroExitCode: options.allowNonzeroExitCode, shell: options.shell ?? false, ...(options.detached !== undefined ? { detached: options.detached } : {}), ...(options.stdio !== undefined ? { stdio: options.stdio } : {}), }; } function makeDarwinDefaultPlan(input: OpenExternalInput): LaunchPlan { const args: string[] = []; const wait = input.wait ?? false; if (wait) args.push("--wait-apps"); if (input.background) args.push("--background"); if (input.newInstance) args.push("--new"); args.push(input.target); return makeLaunchPlan("open", args, { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, }); } function makeDarwinApplicationPlan( input: OpenExternalInput, app: OpenApplicationCandidate, ): LaunchPlan { const args: string[] = []; const wait = input.wait ?? false; if (wait) args.push("--wait-apps"); if (input.background) args.push("--background"); if (input.newInstance) args.push("--new"); args.push("-a", app.name); args.push(input.target); if (app.arguments.length > 0) { args.push("--args", ...app.arguments); } return makeLaunchPlan("open", args, { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, }); } function makePowerShellPlan(input: OpenExternalInput, powerShellCommand: string): LaunchPlan { const encodedParts = ["Start"]; const wait = input.wait ?? false; if (wait) encodedParts.push("-Wait"); encodedParts.push(quotePowerShellValue(input.target)); return makeLaunchPlan( powerShellCommand, [ "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShellCommand(encodedParts.join(" ")), ], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, shell: false, }, ); } function makePowerShellStartProcessArgs( commandPath: string, args: ReadonlyArray<string>, ): ReadonlyArray<string> { const argumentList = args.length > 0 ? ` -ArgumentList ${args.map(quotePowerShellArgument).join(",")}` : ""; const command = `Start ${quotePowerShellArgument(commandPath)}${argumentList} -WindowStyle Hidden`; return [ "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePowerShellCommand(command), ]; } function makeLinuxDefaultPlan(input: OpenExternalInput): LaunchPlan { const wait = input.wait ?? false; return makeLaunchPlan("xdg-open", [input.target], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, detached: !wait, ...(wait ? {} : { stdio: "ignore" as const }), shell: false, }); } function makeWindowsExplorerPlan(input: OpenExternalInput): LaunchPlan { return makeLaunchPlan("explorer", [input.target], { wait: false, allowNonzeroExitCode: false, detached: true, stdio: "ignore", shell: false, }); } function makeDirectApplicationPlan( input: OpenExternalInput, app: OpenApplicationCandidate, ): LaunchPlan { const wait = input.wait ?? false; return makeLaunchPlan(app.name, [...app.arguments, input.target], { wait, allowNonzeroExitCode: input.allowNonzeroExitCode ?? false, detached: !wait, ...(wait ? {} : { stdio: "ignore" as const }), shell: false, }); } function resolveExternalPlans( input: OpenExternalInput, runtime: LaunchRuntime, ): ReadonlyArray<LaunchPlan> { const appCandidates = normalizeAppCandidates(input.app); const plans: LaunchPlan[] = []; const preferWindowsOpenerOnWsl = shouldPreferWindowsOpenerOnWsl(input, runtime); for (const app of appCandidates) { if (app) { if (runtime.platform === "darwin") { plans.push(makeDarwinApplicationPlan(input, app)); } else { plans.push(makeDirectApplicationPlan(input, app)); } continue; } if (runtime.platform === "darwin") { plans.push(makeDarwinDefaultPlan(input)); continue; } if (runtime.platform === "win32" || preferWindowsOpenerOnWsl) { for (const powerShellCommand of runtime.powerShellCandidates) { plans.push(makePowerShellPlan(input, powerShellCommand)); } } if (runtime.platform === "win32") { if (!(input.wait ?? false)) { plans.push(makeWindowsExplorerPlan(input)); } continue; } plans.push(makeLinuxDefaultPlan(input)); } return plans; } function resolveSpawnInput( runtime: LaunchRuntime, plan: LaunchPlan, resolvedCommand: ResolvedCommand, startProcessLauncher: string | undefined, ): DetachedSpawnInput { if (plan.detached && resolvedCommand.usesCmdWrapper && startProcessLauncher !== undefined) { return { command: startProcessLauncher, args: makePowerShellStartProcessArgs(resolvedCommand.path, plan.args), ...(plan.detached !== undefined ? { detached: plan.detached } : {}), ...(plan.shell !== undefined ? { shell: plan.shell } : {}), ...(runtime.platform === "win32" ? { windowsVerbatimArguments: true, windowsHide: true } : {}), ...(plan.stdio === "ignore" ? { stdin: "ignore" as const, stdout: "ignore" as const, stderr: "ignore" as const, } : {}), }; } const windowsHide = runtime.platform === "win32" && plan.detached ? true : undefined; return { command: resolvedCommand.usesCmdWrapper ? resolveWindowsCommandShell(runtime.env) : resolvedCommand.path, args: resolvedCommand.usesCmdWrapper ? makeWindowsCmdSpawnArguments(resolvedCommand.path, plan.args) : [...plan.args], ...(plan.detached !== undefined ? { detached: plan.detached } : {}), ...(plan.shell !== undefined ? { shell: plan.shell } : {}), ...(windowsHide ? { windowsHide } : {}), ...(resolvedCommand.usesCmdWrapper ? { windowsVerbatimArguments: true } : {}), ...(plan.stdio === "ignore" ? { stdin: "ignore" as const, stdout: "ignore" as const, stderr: "ignore" as const, } : {}), }; } function makeCommandNotFoundError( context: LaunchContext, command: string, ): DesktopLauncherCommandNotFoundError { return new DesktopLauncherCommandNotFoundError({ operation: context.operation, command, ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), }); } function makeSpawnError( context: LaunchContext, command: string, args: ReadonlyArray<string>, cause?: unknown, ): DesktopLauncherSpawnError { return new DesktopLauncherSpawnError({ operation: context.operation, command, args: [...args], ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), ...(cause !== undefined ? { cause } : {}), }); } function makeNonZeroExitError( context: LaunchContext, command: string, args: ReadonlyArray<string>, exitCode: number, ): DesktopLauncherNonZeroExitError { return new DesktopLauncherNonZeroExitError({ operation: context.operation, command, args: [...args], exitCode, ...(context.target !== undefined ? { target: context.target } : {}), ...(context.editor !== undefined ? { editor: context.editor } : {}), }); } function toLaunchAttemptFailure(error: LaunchAttemptError): LaunchAttemptFailure { switch (error._tag) { case "DesktopLauncherCommandNotFoundError": return { command: error.command, args: [], reason: "commandNotFound",