UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

245 lines 9.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HOMEBREW_FORMULA = exports.PACKAGE_NAME = void 0; exports.detectInstallMethod = detectInstallMethod; exports.detectPackageManager = detectPackageManager; exports.packageManagerUpgradeCommand = packageManagerUpgradeCommand; exports.homebrewUpgradeCommand = homebrewUpgradeCommand; exports.homebrewRefreshCommand = homebrewRefreshCommand; exports.renderCommand = renderCommand; exports.upgradeInstruction = upgradeInstruction; exports.probeInstall = probeInstall; const fs = require("fs"); const io_1 = require("../util/io"); const version_1 = require("../util/version"); Object.defineProperty(exports, "PACKAGE_NAME", { enumerable: true, get: function () { return version_1.PACKAGE_NAME; } }); // ANCHOR - Constants /** Path segments that only appear when Homebrew owns the executable. */ const HOMEBREW_PATH_MARKERS = ['/Cellar/', '/Caskroom/']; /** Path segments that mark an npm-managed GLOBAL install root (npm/nvm/n/fnm/volta/asdf, unix and Windows). */ const NPM_GLOBAL_MARKERS = [ '/lib/node_modules/', '/appdata/roaming/npm/node_modules/', '/program files/nodejs/node_modules/', '/roaming/nvm/', ]; /** Path segments that mark a pnpm GLOBAL install root. */ const PNPM_GLOBAL_MARKERS = ['/pnpm/global/']; /** * Path segments that mark a yarn classic GLOBAL install root, wherever its data * directory lands (unix and Windows). Every segment names the `global` folder: * yarn berry keeps a project's own dependencies under `.yarn/`, so a bare `.yarn` * segment would misread them as a global install. */ const YARN_GLOBAL_MARKERS = ['/yarn/global/', '/.yarn/global/', '/yarn/data/global/']; /** Path segments that mark a bun GLOBAL install root: ~/.bun by default, or a BUN_INSTALL directory named bun. */ const BUN_GLOBAL_MARKERS = ['/.bun/install/global/', '/bun/install/global/']; /** Path segments that mark any package manager's GLOBAL install root. */ const GLOBAL_INSTALL_MARKERS = [...NPM_GLOBAL_MARKERS, ...PNPM_GLOBAL_MARKERS, ...YARN_GLOBAL_MARKERS, ...BUN_GLOBAL_MARKERS]; /** The Homebrew formula name for the CLI. */ exports.HOMEBREW_FORMULA = 'cpln'; // SECTION - Functions /** * Infers how the running `cpln` was installed from a set of environment inputs. * * Precedence matters: a Homebrew install is itself a packaged binary, so it must * be checked before the generic packaged-binary case; a source checkout is the * only build without a baked version. * * @param {InstallProbe} probe - The environment inputs describing the running process. * @returns {InstallMethod} The inferred install method. */ function detectInstallMethod(probe) { // A source checkout runs under node and carries no baked version if (!probe.isPackaged && probe.npmVersion === 'dev') { return 'source'; } // Homebrew ships cpln as a packaged binary inside its Cellar/Caskroom if (probe.isPackaged && isHomebrewOwned(probe.execPath, probe.env)) { return 'homebrew'; } // Any other packaged single-binary was downloaded directly if (probe.isPackaged) { return 'binary'; } // A copy in a project's node_modules or the npx cache is a project dependency; // a global upgrade would leave the copy actually in use untouched if (isProjectLocal(probe.scriptPath)) { return 'local'; } // Running under node with a real version means it was installed from a registry return 'npm'; } /** * Determines whether the executable path is owned by Homebrew. * * @param {string} execPath - The real path of the running executable. * @param {NodeJS.ProcessEnv} env - The process environment, consulted for HOMEBREW_* hints. * @returns {boolean} True when the path resolves inside a Homebrew Cellar/Caskroom or prefix. */ function isHomebrewOwned(execPath, env) { // The Cellar/Caskroom segment is the precise, prefix-independent signal if (HOMEBREW_PATH_MARKERS.some((marker) => execPath.includes(marker))) { return true; } // Honor an explicit cellar location exported by a Homebrew shell const cellar = env.HOMEBREW_CELLAR; if (cellar && execPath.startsWith(cellar)) { return true; } return false; } /** * Determines whether the running CLI is a project-local dependency rather than a * global install: in the npx cache, or in a node_modules that is not any package * manager's global root. * * @param {string} scriptPath - The resolved main module path of the running CLI. * @returns {boolean} True when the CLI in use is not a global install. */ function isProjectLocal(scriptPath) { const normalized = normalizePath(scriptPath); // npx runs packages out of its own cache directory if (normalized.includes('/_npx/')) { return true; } if (matchesAnyMarker(normalized, GLOBAL_INSTALL_MARKERS)) { return false; } // Covers yarn berry too: its zip cache and unplugged trees both nest a node_modules return normalized.includes('/node_modules/'); } /** * Determines whether a normalized path contains any of the given markers. * * @param {string} normalized - The normalized path to test. * @param {string[]} markers - The path segments to look for. * @returns {boolean} True when at least one marker is present. */ function matchesAnyMarker(normalized, markers) { return markers.some((marker) => normalized.includes(marker)); } /** * Normalizes a path for marker matching: forward slashes, lowercase. * * @param {string} filePath - The path to normalize. * @returns {string} The normalized path. */ function normalizePath(filePath) { return (0, io_1.posixPath)(filePath).toLowerCase(); } /** * Resolves which package manager owns a global npm-family install from the module path. * * @param {string} scriptPath - The resolved main module path of the running CLI. * @returns {PackageManager} The detected package manager, defaulting to npm. */ function detectPackageManager(scriptPath) { const normalized = normalizePath(scriptPath); // pnpm nests global packages under a .pnpm store or a pnpm global directory if (normalized.includes('/.pnpm/') || matchesAnyMarker(normalized, PNPM_GLOBAL_MARKERS)) { return 'pnpm'; } // yarn keeps global packages under its own data directory // (~/.config/yarn on unix, %LOCALAPPDATA%/Yarn/Data on Windows) if (matchesAnyMarker(normalized, YARN_GLOBAL_MARKERS)) { return 'yarn'; } // bun keeps global packages under its install root (~/.bun by default) if (matchesAnyMarker(normalized, BUN_GLOBAL_MARKERS)) { return 'bun'; } return 'npm'; } /** * Builds the upgrade command for a given package manager targeting the latest release. * * @param {PackageManager} manager - The package manager to build the command for. * @returns {RunnableCommand} The program and arguments that upgrade the CLI globally. */ function packageManagerUpgradeCommand(manager) { const target = `${version_1.PACKAGE_NAME}@latest`; switch (manager) { case 'pnpm': return { program: 'pnpm', args: ['add', '-g', target] }; case 'yarn': return { program: 'yarn', args: ['global', 'add', target] }; case 'bun': return { program: 'bun', args: ['add', '-g', target] }; default: return { program: 'npm', args: ['install', '-g', target] }; } } /** * Builds the Homebrew upgrade command for the CLI formula. * * @returns {RunnableCommand} The `brew upgrade cpln` command. */ function homebrewUpgradeCommand() { return { program: 'brew', args: ['upgrade', exports.HOMEBREW_FORMULA] }; } /** * Builds the Homebrew tap-refresh command. Running this before an upgrade prevents * a stale local tap from reporting "already up to date" against an old formula. * * @returns {RunnableCommand} The `brew update` command. */ function homebrewRefreshCommand() { return { program: 'brew', args: ['update'] }; } /** * Renders a runnable command back into a copy-pasteable shell string. * * @param {RunnableCommand} command - The command to render. * @returns {string} The command as a single shell-ready line. */ function renderCommand(command) { return [command.program, ...command.args].join(' '); } /** * Builds the line telling the user how to install a newer version, for the * new-version notice printed after a command. * * @param {InstallMethod} method - The detected install method. * @returns {string} The instruction to close the notice with. */ function upgradeInstruction(method) { // `cpln upgrade` refuses a project dependency, so it must not be the advice given if (method === 'local') { return `Update ${version_1.PACKAGE_NAME} in your project's package.json.`; } return `Run 'cpln upgrade' to update.`; } /** * Builds an InstallProbe from the live process, resolving symlinks so a Homebrew * bin symlink is seen as its underlying Cellar path. * * @returns {InstallProbe} The environment inputs describing the running process. */ function probeInstall() { var _a, _b; return { execPath: safeRealpath(process.execPath), scriptPath: (_b = (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : '', isPackaged: Boolean(process.pkg) || 'pkg' in process.versions, npmVersion: version_1.About.npm, env: process.env, }; } /** * Resolves a path to its canonical location, falling back to the input when it * cannot be resolved (e.g., the file no longer exists). * * @param {string} filePath - The path to resolve. * @returns {string} The real path, or the original path on failure. */ function safeRealpath(filePath) { try { return fs.realpathSync(filePath); } catch (_a) { return filePath; } } // !SECTION //# sourceMappingURL=install-method.js.map