"use strict";
const fs = require("fs");
const os = require("os");
const path = require("path");
const crypto = require("crypto");
const { spawnSync } = require("child_process");
const METADATA_FILE = ".gitcode-install.json";
function normalizePath(value, isWin = process.platform === "win32") {
const pathAPI = isWin ? path.win32 : path.posix;
let normalized = pathAPI.resolve(value || "").replace(/[\\/]+$/, "");
if (isWin) normalized = normalized.toLowerCase();
return normalized;
}
function pathEntries(env = process.env, isWin = process.platform === "win32") {
const delimiter = isWin ? ";" : ":";
return (env.PATH || env.Path || "")
.split(delimiter)
.map((entry) => entry.trim().replace(/^"|"$/g, ""))
.filter(Boolean);
}
function commandCandidates(name, env = process.env, isWin = process.platform === "win32") {
const extensions = isWin ? [".exe", ".com", ".bat", ".cmd", ".ps1", ""] : [""];
const candidates = [];
const seen = new Set();
for (const dir of pathEntries(env, isWin)) {
for (const extension of extensions) {
const candidate = path.join(dir, `${name}${extension}`);
const key = normalizePath(candidate, isWin);
if (seen.has(key)) continue;
seen.add(key);
try {
if (fs.statSync(candidate).isFile()) candidates.push(candidate);
} catch {
}
}
}
return candidates;
}
function expectedGlobalBin(prefix, isWin = process.platform === "win32") {
return isWin ? prefix : path.join(prefix, "bin");
}
function pathConflict(prefix, env = process.env, isWin = process.platform === "win32") {
const expectedDir = normalizePath(expectedGlobalBin(prefix, isWin), isWin);
const candidates = commandCandidates("gitcode", env, isWin);
const selected = candidates[0] || "";
const pathAPI = isWin ? path.win32 : path.posix;
const selectedDir = selected ? normalizePath(pathAPI.dirname(selected), isWin) : "";
return {
expectedDir: expectedGlobalBin(prefix, isWin),
candidates,
selected,
shadowed: Boolean(selected && selectedDir !== expectedDir),
};
}
function writeInstallMetadata(packageRoot, values) {
const target = path.join(packageRoot, METADATA_FILE);
const temp = `${target}.tmp-${process.pid}-${crypto.randomBytes(8).toString("hex")}`;
const data = { schema: 1, ...values };
try {
const stat = fs.lstatSync(target);
if (!stat.isFile() || stat.isSymbolicLink()) {
throw new Error(`refusing non-regular install metadata target: ${target}`);
}
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
fs.writeFileSync(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600, flag: "wx" });
try {
fs.renameSync(temp, target);
} catch (error) {
if (!["EEXIST", "EPERM"].includes(error.code)) throw error;
fs.unlinkSync(target);
fs.renameSync(temp, target);
}
return target;
}
function readInstallMetadata(packageRoot) {
try {
return JSON.parse(fs.readFileSync(path.join(packageRoot, METADATA_FILE), "utf8"));
} catch {
return null;
}
}
function npmInvocation(execPath = process.execPath, env = process.env, platform = process.platform) {
const candidates = [
env.npm_execpath,
path.join(path.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"),
path.resolve(path.dirname(execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
].filter(Boolean);
for (const candidate of candidates) {
if (fs.existsSync(candidate) && /\.m?js$/i.test(candidate)) {
return { command: execPath, prefix: [candidate], metadataPath: candidate };
}
}
const command = platform === "win32" ? "npm.cmd" : "npm";
return { command, prefix: [], metadataPath: command, shell: platform === "win32" };
}
function discoverGlobalInstall(packageRoot, options = {}) {
const invoke = options.npm || npmInvocation(options.execPath, options.env, options.platform);
const runner = options.runner || spawnSync;
const run = (args) =>
runner(invoke.command, [...invoke.prefix, ...args], {
encoding: "utf8",
timeout: 5000,
windowsHide: true,
shell: Boolean(invoke.shell),
});
const root = run(["root", "-g"]);
const prefix = run(["prefix", "-g"]);
if (root.status !== 0 || prefix.status !== 0) return null;
const isWin = options.platform === "win32";
const pathAPI = isWin ? path.win32 : path.posix;
const expectedRoot = normalizePath(root.stdout.trim(), isWin);
const actualRoot = normalizePath(pathAPI.resolve(packageRoot, "..", ".."), isWin);
return {
schema: 1,
distribution: expectedRoot === actualRoot ? "npm" : "npm-local",
global: expectedRoot === actualRoot,
version: options.version || "",
prefix: prefix.stdout.trim(),
npm: invoke.metadataPath,
};
}
function ensureInstallMetadata(packageRoot, version, options = {}) {
const existing = readInstallMetadata(packageRoot);
if (existing) return existing;
const discovered = discoverGlobalInstall(packageRoot, { ...options, version });
if (!discovered) return null;
try {
writeInstallMetadata(packageRoot, discovered);
} catch {
}
return discovered;
}
function stateDir(env = process.env, platform = process.platform) {
if (env.GC_STATE_DIR) return env.GC_STATE_DIR;
if (platform === "win32") {
return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "gitcode-cli");
}
return path.join(env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state"), "gitcode-cli");
}
module.exports = {
METADATA_FILE,
commandCandidates,
discoverGlobalInstall,
ensureInstallMetadata,
expectedGlobalBin,
normalizePath,
npmInvocation,
pathConflict,
pathEntries,
readInstallMetadata,
stateDir,
writeInstallMetadata,
};