@controlplane/cli
Version:
Control Plane Corporation CLI
245 lines • 9.93 kB
JavaScript
;
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