|
1 | 1 | import type { Command } from "@commander-js/extra-typings"; |
2 | 2 | import { semver } from "bun"; |
3 | 3 | import { logError } from "../helpers/log"; |
| 4 | +import { resolveCommand } from "../helpers/platform"; |
4 | 5 |
|
5 | 6 | const NPM_REGISTRY = "https://registry.npmjs.org/archgate/latest"; |
6 | 7 |
|
| 8 | +interface PackageManager { |
| 9 | + name: string; |
| 10 | + globalBinCmd: string[]; |
| 11 | + upgradeArgs: string[]; |
| 12 | +} |
| 13 | + |
| 14 | +const PACKAGE_MANAGERS: PackageManager[] = [ |
| 15 | + { |
| 16 | + name: "bun", |
| 17 | + globalBinCmd: ["bun", "pm", "-g", "bin"], |
| 18 | + upgradeArgs: ["add", "-g", "archgate@latest"], |
| 19 | + }, |
| 20 | + { |
| 21 | + name: "pnpm", |
| 22 | + globalBinCmd: ["pnpm", "bin", "-g"], |
| 23 | + upgradeArgs: ["add", "-g", "archgate@latest"], |
| 24 | + }, |
| 25 | + { |
| 26 | + name: "yarn", |
| 27 | + globalBinCmd: ["yarn", "global", "bin"], |
| 28 | + upgradeArgs: ["global", "add", "archgate@latest"], |
| 29 | + }, |
| 30 | + { |
| 31 | + name: "npm", |
| 32 | + globalBinCmd: ["npm", "bin", "-g"], |
| 33 | + upgradeArgs: ["install", "-g", "archgate@latest"], |
| 34 | + }, |
| 35 | +]; |
| 36 | + |
| 37 | +/** |
| 38 | + * Get the global bin directory for a package manager. |
| 39 | + * Returns null if the command is not available or fails. |
| 40 | + */ |
| 41 | +async function getGlobalBinDir(cmd: string[]): Promise<string | null> { |
| 42 | + try { |
| 43 | + const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" }); |
| 44 | + const stdout = await new Response(proc.stdout).text(); |
| 45 | + const exitCode = await proc.exited; |
| 46 | + if (exitCode !== 0) return null; |
| 47 | + return stdout.trim() || null; |
| 48 | + } catch { |
| 49 | + return null; |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Detect which package manager installed archgate by checking whether |
| 55 | + * the running binary lives under each manager's global bin directory. |
| 56 | + * Resolves all candidates in parallel. Falls back to npm if none match. |
| 57 | + */ |
| 58 | +async function detectPackageManager(): Promise<{ |
| 59 | + cmd: string; |
| 60 | + args: string[]; |
| 61 | + manualHint: string; |
| 62 | +}> { |
| 63 | + const binaryPath = process.execPath; |
| 64 | + |
| 65 | + // Resolve all package managers in parallel |
| 66 | + const candidates = await Promise.all( |
| 67 | + PACKAGE_MANAGERS.map(async (pm) => { |
| 68 | + const resolved = await resolveCommand(pm.name); |
| 69 | + if (!resolved) return null; |
| 70 | + const globalBinCmd = [resolved, ...pm.globalBinCmd.slice(1)]; |
| 71 | + const binDir = await getGlobalBinDir(globalBinCmd); |
| 72 | + return { pm, resolved, binDir }; |
| 73 | + }) |
| 74 | + ); |
| 75 | + |
| 76 | + // Find which PM's global bin dir contains the running binary |
| 77 | + const match = candidates.find( |
| 78 | + (c) => c?.binDir && binaryPath.startsWith(c.binDir) |
| 79 | + ); |
| 80 | + |
| 81 | + if (match) { |
| 82 | + return { |
| 83 | + cmd: match.resolved, |
| 84 | + args: match.pm.upgradeArgs, |
| 85 | + manualHint: `${match.pm.name} ${match.pm.upgradeArgs.join(" ")}`, |
| 86 | + }; |
| 87 | + } |
| 88 | + |
| 89 | + // Default to npm |
| 90 | + const npmCandidate = candidates.find((c) => c?.pm.name === "npm"); |
| 91 | + const npm = PACKAGE_MANAGERS.find((pm) => pm.name === "npm")!; |
| 92 | + return { |
| 93 | + cmd: npmCandidate?.resolved ?? "npm", |
| 94 | + args: npm.upgradeArgs, |
| 95 | + manualHint: `npm ${npm.upgradeArgs.join(" ")}`, |
| 96 | + }; |
| 97 | +} |
| 98 | + |
7 | 99 | export function registerUpgradeCommand(program: Command) { |
8 | 100 | program |
9 | 101 | .command("upgrade") |
10 | | - .description("Upgrade Archgate to the latest version via npm") |
| 102 | + .description("Upgrade Archgate to the latest version") |
11 | 103 | .action(async () => { |
12 | 104 | console.log("Checking for latest Archgate release..."); |
13 | 105 |
|
@@ -58,16 +150,18 @@ export function registerUpgradeCommand(program: Command) { |
58 | 150 |
|
59 | 151 | console.log(`Upgrading ${currentVersion} -> ${latestVersion}...`); |
60 | 152 |
|
61 | | - const proc = Bun.spawn(["npm", "install", "-g", "archgate@latest"], { |
| 153 | + const { cmd, args, manualHint } = await detectPackageManager(); |
| 154 | + |
| 155 | + const proc = Bun.spawn([cmd, ...args], { |
62 | 156 | stdout: "inherit", |
63 | 157 | stderr: "inherit", |
64 | 158 | }); |
65 | 159 | const exitCode = await proc.exited; |
66 | 160 |
|
67 | 161 | if (exitCode !== 0) { |
68 | 162 | logError( |
69 | | - "Failed to install the latest version via npm.", |
70 | | - "Try running `npm install -g archgate@latest` manually." |
| 163 | + "Failed to install the latest version.", |
| 164 | + `Try running \`${manualHint}\` manually.` |
71 | 165 | ); |
72 | 166 | process.exit(1); |
73 | 167 | } |
|
0 commit comments