Compare commits
1 Commits
fix/model-
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ba1d675b9 |
47
src/cli/config-manager/bun-install.test.ts
Normal file
47
src/cli/config-manager/bun-install.test.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test"
|
||||||
|
import { initConfigContext, resetConfigContext } from "./config-context"
|
||||||
|
import { runBunInstallWithDetails } from "./bun-install"
|
||||||
|
|
||||||
|
describe("bun-install", () => {
|
||||||
|
let originalPlatform: NodeJS.Platform
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalPlatform = process.platform
|
||||||
|
resetConfigContext()
|
||||||
|
initConfigContext("opencode", null)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(process, "platform", { value: originalPlatform })
|
||||||
|
resetConfigContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows with bun.exe on PATH #when runBunInstallWithDetails is called #then uses bun.exe", async () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
|
||||||
|
const whichSpy = spyOn(Bun, "which")
|
||||||
|
.mockImplementation((binary: string) => {
|
||||||
|
if (binary === "bun.exe") {
|
||||||
|
return "C:\\Tools\\bun.exe"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const spawnSpy = spyOn(Bun, "spawn").mockReturnValue({
|
||||||
|
exited: Promise.resolve(0),
|
||||||
|
exitCode: 0,
|
||||||
|
kill: () => {},
|
||||||
|
} as unknown as ReturnType<typeof Bun.spawn>)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runBunInstallWithDetails()
|
||||||
|
|
||||||
|
expect(result.success).toBe(true)
|
||||||
|
expect(spawnSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(spawnSpy.mock.calls[0]?.[0]).toEqual(["C:\\Tools\\bun.exe", "install"])
|
||||||
|
} finally {
|
||||||
|
spawnSpy.mockRestore()
|
||||||
|
whichSpy.mockRestore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -9,6 +9,14 @@ export interface BunInstallResult {
|
|||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveBunCommand(): string {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return Bun.which("bun.exe") ?? Bun.which("bun") ?? "bun.exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bun.which("bun") ?? "bun"
|
||||||
|
}
|
||||||
|
|
||||||
export async function runBunInstall(): Promise<boolean> {
|
export async function runBunInstall(): Promise<boolean> {
|
||||||
const result = await runBunInstallWithDetails()
|
const result = await runBunInstallWithDetails()
|
||||||
return result.success
|
return result.success
|
||||||
@@ -16,7 +24,8 @@ export async function runBunInstall(): Promise<boolean> {
|
|||||||
|
|
||||||
export async function runBunInstallWithDetails(): Promise<BunInstallResult> {
|
export async function runBunInstallWithDetails(): Promise<BunInstallResult> {
|
||||||
try {
|
try {
|
||||||
const proc = Bun.spawn(["bun", "install"], {
|
const bunCommand = resolveBunCommand()
|
||||||
|
const proc = Bun.spawn([bunCommand, "install"], {
|
||||||
cwd: getConfigDir(),
|
cwd: getConfigDir(),
|
||||||
stdout: "inherit",
|
stdout: "inherit",
|
||||||
stderr: "inherit",
|
stderr: "inherit",
|
||||||
@@ -39,7 +48,7 @@ export async function runBunInstallWithDetails(): Promise<BunInstallResult> {
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
timedOut: true,
|
timedOut: true,
|
||||||
error: `bun install timed out after ${BUN_INSTALL_TIMEOUT_SECONDS} seconds. Try running manually: cd ${getConfigDir()} && bun i`,
|
error: `bun install timed out after ${BUN_INSTALL_TIMEOUT_SECONDS} seconds. Try running manually in ${getConfigDir()}: bun install`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +64,7 @@ export async function runBunInstallWithDetails(): Promise<BunInstallResult> {
|
|||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `bun install failed: ${message}. Is bun installed? Try: curl -fsSL https://bun.sh/install | bash`,
|
error: `bun install failed: ${message}. Ensure Bun is installed and available in PATH: https://bun.sh/docs/installation`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/shared/command-executor/shell-path.test.ts
Normal file
39
src/shared/command-executor/shell-path.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||||
|
import { findBashPath } from "./shell-path"
|
||||||
|
|
||||||
|
describe("shell-path", () => {
|
||||||
|
let originalPlatform: NodeJS.Platform
|
||||||
|
let originalComspec: string | undefined
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalPlatform = process.platform
|
||||||
|
originalComspec = process.env.COMSPEC
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(process, "platform", { value: originalPlatform })
|
||||||
|
if (originalComspec !== undefined) {
|
||||||
|
process.env.COMSPEC = originalComspec
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete process.env.COMSPEC
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows platform with COMSPEC #when findBashPath is called #then returns COMSPEC path", () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
process.env.COMSPEC = "C:\\Windows\\System32\\cmd.exe"
|
||||||
|
|
||||||
|
const result = findBashPath()
|
||||||
|
|
||||||
|
expect(result).toBe("C:\\Windows\\System32\\cmd.exe")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows platform without COMSPEC #when findBashPath is called #then returns default cmd path", () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
delete process.env.COMSPEC
|
||||||
|
|
||||||
|
const result = findBashPath()
|
||||||
|
|
||||||
|
expect(result).toBe("C:\\Windows\\System32\\cmd.exe")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs"
|
|||||||
|
|
||||||
const DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"]
|
const DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"]
|
||||||
const DEFAULT_BASH_PATHS = ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"]
|
const DEFAULT_BASH_PATHS = ["/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash"]
|
||||||
|
const DEFAULT_WINDOWS_CMD_PATH = "C:\\Windows\\System32\\cmd.exe"
|
||||||
|
|
||||||
function findShellPath(
|
function findShellPath(
|
||||||
defaultPaths: string[],
|
defaultPaths: string[],
|
||||||
@@ -19,9 +20,17 @@ function findShellPath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function findZshPath(customZshPath?: string): string | null {
|
export function findZshPath(customZshPath?: string): string | null {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return process.env.COMSPEC?.trim() || DEFAULT_WINDOWS_CMD_PATH
|
||||||
|
}
|
||||||
|
|
||||||
return findShellPath(DEFAULT_ZSH_PATHS, customZshPath)
|
return findShellPath(DEFAULT_ZSH_PATHS, customZshPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findBashPath(): string | null {
|
export function findBashPath(): string | null {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return process.env.COMSPEC?.trim() || DEFAULT_WINDOWS_CMD_PATH
|
||||||
|
}
|
||||||
|
|
||||||
return findShellPath(DEFAULT_BASH_PATHS)
|
return findShellPath(DEFAULT_BASH_PATHS)
|
||||||
}
|
}
|
||||||
|
|||||||
57
src/shared/data-path.test.ts
Normal file
57
src/shared/data-path.test.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||||
|
import { homedir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import { getCacheDir, getDataDir } from "./data-path"
|
||||||
|
|
||||||
|
describe("data-path", () => {
|
||||||
|
let originalPlatform: NodeJS.Platform
|
||||||
|
let originalEnv: Record<string, string | undefined>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalPlatform = process.platform
|
||||||
|
originalEnv = {
|
||||||
|
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
||||||
|
APPDATA: process.env.APPDATA,
|
||||||
|
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
|
||||||
|
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.defineProperty(process, "platform", { value: originalPlatform })
|
||||||
|
for (const [key, value] of Object.entries(originalEnv)) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
process.env[key] = value
|
||||||
|
} else {
|
||||||
|
delete process.env[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows with LOCALAPPDATA #when getDataDir is called #then returns LOCALAPPDATA", () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
process.env.LOCALAPPDATA = "C:\\Users\\TestUser\\AppData\\Local"
|
||||||
|
|
||||||
|
const result = getDataDir()
|
||||||
|
|
||||||
|
expect(result).toBe("C:\\Users\\TestUser\\AppData\\Local")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows without LOCALAPPDATA #when getDataDir is called #then falls back to AppData Local", () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
delete process.env.LOCALAPPDATA
|
||||||
|
|
||||||
|
const result = getDataDir()
|
||||||
|
|
||||||
|
expect(result).toBe(join(homedir(), "AppData", "Local"))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows with LOCALAPPDATA #when getCacheDir is called #then returns Local cache path", () => {
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
process.env.LOCALAPPDATA = "C:\\Users\\TestUser\\AppData\\Local"
|
||||||
|
|
||||||
|
const result = getCacheDir()
|
||||||
|
|
||||||
|
expect(result).toBe(join("C:\\Users\\TestUser\\AppData\\Local", "cache"))
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -10,6 +10,10 @@ import * as os from "node:os"
|
|||||||
* including Windows, so we match that behavior exactly.
|
* including Windows, so we match that behavior exactly.
|
||||||
*/
|
*/
|
||||||
export function getDataDir(): string {
|
export function getDataDir(): string {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local")
|
||||||
|
}
|
||||||
|
|
||||||
return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share")
|
return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +31,11 @@ export function getOpenCodeStorageDir(): string {
|
|||||||
* - All platforms: XDG_CACHE_HOME or ~/.cache
|
* - All platforms: XDG_CACHE_HOME or ~/.cache
|
||||||
*/
|
*/
|
||||||
export function getCacheDir(): string {
|
export function getCacheDir(): string {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local")
|
||||||
|
return path.join(localAppData, "cache")
|
||||||
|
}
|
||||||
|
|
||||||
return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache")
|
return process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ describe("opencode-config-dir", () => {
|
|||||||
expect(result).toBe(join(homedir(), ".config", "opencode"))
|
expect(result).toBe(join(homedir(), ".config", "opencode"))
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns ~/.config/opencode on Windows by default", () => {
|
test("returns %APPDATA%/opencode on Windows by default", () => {
|
||||||
// given opencode CLI binary detected, platform is Windows
|
// given opencode CLI binary detected, platform is Windows
|
||||||
Object.defineProperty(process, "platform", { value: "win32" })
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
delete process.env.APPDATA
|
delete process.env.APPDATA
|
||||||
@@ -188,8 +188,8 @@ describe("opencode-config-dir", () => {
|
|||||||
// when getOpenCodeConfigDir is called with binary="opencode"
|
// when getOpenCodeConfigDir is called with binary="opencode"
|
||||||
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200", checkExisting: false })
|
const result = getOpenCodeConfigDir({ binary: "opencode", version: "1.0.200", checkExisting: false })
|
||||||
|
|
||||||
// then returns ~/.config/opencode (cross-platform default)
|
// then returns %APPDATA%/opencode
|
||||||
expect(result).toBe(join(homedir(), ".config", "opencode"))
|
expect(result).toBe(join(homedir(), "AppData", "Roaming", "opencode"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -42,29 +42,32 @@ function getTauriConfigDir(identifier: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCliConfigDir(): string {
|
function getCliConfigDir(checkExisting = true): string {
|
||||||
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim()
|
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim()
|
||||||
if (envConfigDir) {
|
if (envConfigDir) {
|
||||||
return resolve(envConfigDir)
|
return resolve(envConfigDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
const crossPlatformDir = join(homedir(), ".config", "opencode")
|
|
||||||
const crossPlatformConfig = join(crossPlatformDir, "opencode.json")
|
|
||||||
|
|
||||||
if (existsSync(crossPlatformConfig)) {
|
|
||||||
return crossPlatformDir
|
|
||||||
}
|
|
||||||
|
|
||||||
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming")
|
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming")
|
||||||
const appdataDir = join(appData, "opencode")
|
const appdataDir = join(appData, "opencode")
|
||||||
|
const crossPlatformDir = join(homedir(), ".config", "opencode")
|
||||||
|
if (!checkExisting) {
|
||||||
|
return appdataDir
|
||||||
|
}
|
||||||
|
|
||||||
const appdataConfig = join(appdataDir, "opencode.json")
|
const appdataConfig = join(appdataDir, "opencode.json")
|
||||||
|
const crossPlatformConfig = join(crossPlatformDir, "opencode.json")
|
||||||
|
|
||||||
if (existsSync(appdataConfig)) {
|
if (existsSync(appdataConfig)) {
|
||||||
return appdataDir
|
return appdataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
return crossPlatformDir
|
if (existsSync(crossPlatformConfig)) {
|
||||||
|
return crossPlatformDir
|
||||||
|
}
|
||||||
|
|
||||||
|
return appdataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
|
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
|
||||||
@@ -75,14 +78,14 @@ export function getOpenCodeConfigDir(options: OpenCodeConfigDirOptions): string
|
|||||||
const { binary, version, checkExisting = true } = options
|
const { binary, version, checkExisting = true } = options
|
||||||
|
|
||||||
if (binary === "opencode") {
|
if (binary === "opencode") {
|
||||||
return getCliConfigDir()
|
return getCliConfigDir(checkExisting)
|
||||||
}
|
}
|
||||||
|
|
||||||
const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER
|
const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER
|
||||||
const tauriDir = getTauriConfigDir(identifier)
|
const tauriDir = getTauriConfigDir(identifier)
|
||||||
|
|
||||||
if (checkExisting) {
|
if (checkExisting) {
|
||||||
const legacyDir = getCliConfigDir()
|
const legacyDir = getCliConfigDir(true)
|
||||||
const legacyConfig = join(legacyDir, "opencode.json")
|
const legacyConfig = join(legacyDir, "opencode.json")
|
||||||
const legacyConfigC = join(legacyDir, "opencode.jsonc")
|
const legacyConfigC = join(legacyDir, "opencode.jsonc")
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe("shell-env", () => {
|
|||||||
originalEnv = {
|
originalEnv = {
|
||||||
SHELL: process.env.SHELL,
|
SHELL: process.env.SHELL,
|
||||||
PSModulePath: process.env.PSModulePath,
|
PSModulePath: process.env.PSModulePath,
|
||||||
|
COMSPEC: process.env.COMSPEC,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ describe("shell-env", () => {
|
|||||||
test("#given Windows platform without PSModulePath #when detectShellType is called #then returns cmd", () => {
|
test("#given Windows platform without PSModulePath #when detectShellType is called #then returns cmd", () => {
|
||||||
delete process.env.PSModulePath
|
delete process.env.PSModulePath
|
||||||
delete process.env.SHELL
|
delete process.env.SHELL
|
||||||
|
delete process.env.COMSPEC
|
||||||
Object.defineProperty(process, "platform", { value: "win32" })
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
|
||||||
const result = detectShellType()
|
const result = detectShellType()
|
||||||
@@ -77,12 +79,35 @@ describe("shell-env", () => {
|
|||||||
test("#given PSModulePath takes priority over SHELL #when both are set #then returns powershell", () => {
|
test("#given PSModulePath takes priority over SHELL #when both are set #then returns powershell", () => {
|
||||||
process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules"
|
process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules"
|
||||||
process.env.SHELL = "/bin/bash"
|
process.env.SHELL = "/bin/bash"
|
||||||
|
process.env.COMSPEC = "C:\\Windows\\System32\\cmd.exe"
|
||||||
Object.defineProperty(process, "platform", { value: "win32" })
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
|
||||||
const result = detectShellType()
|
const result = detectShellType()
|
||||||
|
|
||||||
expect(result).toBe("powershell")
|
expect(result).toBe("powershell")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("#given Windows COMSPEC points to powershell #when detectShellType is called #then returns powershell", () => {
|
||||||
|
delete process.env.PSModulePath
|
||||||
|
delete process.env.SHELL
|
||||||
|
process.env.COMSPEC = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
|
||||||
|
const result = detectShellType()
|
||||||
|
|
||||||
|
expect(result).toBe("powershell")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("#given Windows COMSPEC points to bash executable #when detectShellType is called #then returns unix", () => {
|
||||||
|
delete process.env.PSModulePath
|
||||||
|
delete process.env.SHELL
|
||||||
|
process.env.COMSPEC = "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||||
|
Object.defineProperty(process, "platform", { value: "win32" })
|
||||||
|
|
||||||
|
const result = detectShellType()
|
||||||
|
|
||||||
|
expect(result).toBe("unix")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("shellEscape", () => {
|
describe("shellEscape", () => {
|
||||||
|
|||||||
@@ -13,11 +13,29 @@ export function detectShellType(): ShellType {
|
|||||||
return "powershell"
|
return "powershell"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
const comspec = process.env.COMSPEC ?? process.env.ComSpec
|
||||||
|
const normalizedComspec = comspec?.toLowerCase()
|
||||||
|
if (normalizedComspec?.includes("powershell") || normalizedComspec?.includes("pwsh")) {
|
||||||
|
return "powershell"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedComspec?.includes("bash") || normalizedComspec?.includes("zsh")) {
|
||||||
|
return "unix"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.SHELL) {
|
||||||
|
return "unix"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.SHELL) {
|
if (process.env.SHELL) {
|
||||||
return "unix"
|
return "unix"
|
||||||
}
|
}
|
||||||
|
|
||||||
return process.platform === "win32" ? "cmd" : "unix"
|
return "unix"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user