From f495f85bdb81ba888a92666a8971df7b4f01a572 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 5 Feb 2026 23:13:43 +0000 Subject: [PATCH] feat(update): enhance package manager detection, version reporting, and add verbose option --- changelog.md | 9 +++ ts/00_commitinfo_data.ts | 2 +- ts/mod_update/classes.packagemanager.ts | 96 +++++++++++++++++++++++-- ts/mod_update/index.ts | 54 +++++++++++--- ts/tools.cli.ts | 8 ++- 5 files changed, 152 insertions(+), 17 deletions(-) diff --git a/changelog.md b/changelog.md index a6fa7e5..909618c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-02-05 - 3.2.0 - feat(update) +enhance package manager detection, version reporting, and add verbose option + +- Add IPackageManagerInfo interface and detectPackageManager() to robustly detect npm/yarn/pnpm via 'which' and '--version' fallbacks +- Make isAvailable() delegate to detectPackageManager() and return structured detection info +- Add getPackageManagerVersion() to obtain current and latest versions (parses local --version and queries npm registry) +- Update run() to support a verbose flag, show a package-manager status table, and collect detectedPMs with version/update status +- Update CLI help and command handling to accept --verbose/-v and pass it through to mod_update.run() + ## 2026-02-03 - 3.1.3 - fix(mod_update) try private registry (verdaccio.lossless.digital) first when fetching package versions; fall back to public npm; handle unknown latest versions gracefully in output diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index b4c6a31..a5edffd 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tools', - version: '3.1.3', + version: '3.2.0', description: 'A CLI tool placeholder for development utilities.' } diff --git a/ts/mod_update/classes.packagemanager.ts b/ts/mod_update/classes.packagemanager.ts index 5754014..f5cf6b4 100644 --- a/ts/mod_update/classes.packagemanager.ts +++ b/ts/mod_update/classes.packagemanager.ts @@ -16,6 +16,16 @@ export interface IPackageUpdateInfo { needsUpdate: boolean; } +export interface IPackageManagerInfo { + name: TPackageManager; + available: boolean; + detectionMethod?: 'which' | 'version-command'; + path?: string; + currentVersion?: string; + latestVersion?: string | null; + needsUpdate?: boolean; +} + export class PackageManagerUtil { private shell = new plugins.smartshell.Smartshell({ executor: 'bash', @@ -23,14 +33,92 @@ export class PackageManagerUtil { /** * Check if a package manager is available on the system + * Uses multiple detection methods for robustness across different shell contexts */ - public async isAvailable(pm: TPackageManager): Promise { + public async isAvailable(pm: TPackageManager, verbose = false): Promise { + const info = await this.detectPackageManager(pm, verbose); + return info.available; + } + + /** + * Detect a package manager and return detailed info + */ + public async detectPackageManager(pm: TPackageManager, verbose = false): Promise { + const info: IPackageManagerInfo = { name: pm, available: false }; + + // Primary method: try 'which' command try { - const result = await this.shell.execSilent(`which ${pm} >/dev/null 2>&1 && echo "found"`); - return result.exitCode === 0 && result.stdout.includes('found'); + const whichResult = await this.shell.execSilent(`which ${pm} 2>/dev/null`); + if (whichResult.exitCode === 0 && whichResult.stdout.trim()) { + info.available = true; + info.detectionMethod = 'which'; + info.path = whichResult.stdout.trim(); + if (verbose) { + console.log(` Checking ${pm}... found via 'which' at ${info.path}`); + } + return info; + } } catch { - return false; + // Continue to fallback } + + // Fallback method: try running pm --version directly + // This can find PMs that are available but not in PATH for 'which' + try { + const versionResult = await this.shell.execSilent(`${pm} --version 2>/dev/null`); + if (versionResult.exitCode === 0 && versionResult.stdout.trim()) { + info.available = true; + info.detectionMethod = 'version-command'; + if (verbose) { + console.log(` Checking ${pm}... found via '--version' (which failed)`); + } + return info; + } + } catch { + // Not available + } + + if (verbose) { + console.log(` Checking ${pm}... not found`); + } + return info; + } + + /** + * Get the current and latest version of a package manager + */ + public async getPackageManagerVersion(pm: TPackageManager): Promise<{ current: string; latest: string | null }> { + let current = 'unknown'; + let latest: string | null = null; + + // Get current version + try { + const result = await this.shell.execSilent(`${pm} --version 2>/dev/null`); + if (result.exitCode === 0 && result.stdout.trim()) { + // Parse version from output - handle different formats + const output = result.stdout.trim(); + // npm: "10.2.0", pnpm: "8.15.0", yarn: "1.22.19" + // Some may include prefix like "v1.22.19" + const versionMatch = output.match(/(\d+\.\d+\.\d+)/); + if (versionMatch) { + current = versionMatch[1]; + } + } + } catch { + // Keep as unknown + } + + // Get latest version from npm registry + try { + const result = await this.shell.execSilent(`npm view ${pm} version 2>/dev/null`); + if (result.exitCode === 0 && result.stdout.trim()) { + latest = result.stdout.trim(); + } + } catch { + // Keep as null + } + + return { current, latest }; } /** diff --git a/ts/mod_update/index.ts b/ts/mod_update/index.ts index 6c4fbdc..8e98d3d 100644 --- a/ts/mod_update/index.ts +++ b/ts/mod_update/index.ts @@ -1,5 +1,5 @@ import * as plugins from './mod.plugins.js'; -import { PackageManagerUtil, type TPackageManager, type IPackageUpdateInfo } from './classes.packagemanager.js'; +import { PackageManagerUtil, type TPackageManager, type IPackageUpdateInfo, type IPackageManagerInfo } from './classes.packagemanager.js'; const GITZONE_PACKAGES = [ '@git.zone/cli', @@ -15,33 +15,69 @@ const GITZONE_PACKAGES = [ export interface IUpdateOptions { yes?: boolean; + verbose?: boolean; } export const run = async (options: IUpdateOptions = {}): Promise => { const pmUtil = new PackageManagerUtil(); + const verbose = options.verbose === true; console.log('Scanning for installed @git.zone packages...\n'); // Check which package managers are available - const availablePMs: TPackageManager[] = []; + if (verbose) { + console.log('Detecting package managers:'); + } + + const detectedPMs: IPackageManagerInfo[] = []; for (const pm of ['npm', 'yarn', 'pnpm'] as TPackageManager[]) { - if (await pmUtil.isAvailable(pm)) { - availablePMs.push(pm); + const info = await pmUtil.detectPackageManager(pm, verbose); + if (info.available) { + detectedPMs.push(info); } } - if (availablePMs.length === 0) { + if (verbose) { + console.log(''); + } + + if (detectedPMs.length === 0) { console.log('No package managers found (npm, yarn, pnpm).'); + console.log('Tried detection via \'which\' command and direct version check.'); return; } - console.log(`Found package managers: ${availablePMs.join(', ')}\n`); + // Get version info for each PM and display status table + console.log('Package managers:\n'); + console.log(' Name Current Latest Status'); + console.log(' ──────────────────────────────────────────────'); + + for (const pmInfo of detectedPMs) { + const versionInfo = await pmUtil.getPackageManagerVersion(pmInfo.name); + pmInfo.currentVersion = versionInfo.current; + pmInfo.latestVersion = versionInfo.latest; + pmInfo.needsUpdate = versionInfo.latest + ? pmUtil.isNewerVersion(versionInfo.current, versionInfo.latest) + : false; + + const name = pmInfo.name.padEnd(9); + const current = versionInfo.current.padEnd(12); + const latest = (versionInfo.latest || 'unknown').padEnd(12); + const status = versionInfo.latest === null + ? '? Version unknown' + : pmInfo.needsUpdate + ? '⬆️ Update available' + : '✓ Up to date'; + console.log(` ${name}${current}${latest}${status}`); + } + + console.log(''); // Collect all installed @git.zone packages from all package managers const allPackages: IPackageUpdateInfo[] = []; - for (const pm of availablePMs) { - const installed = await pmUtil.getInstalledPackages(pm); + for (const pmInfo of detectedPMs) { + const installed = await pmUtil.getInstalledPackages(pmInfo.name); for (const pkg of installed) { // Only include packages from our predefined list if (GITZONE_PACKAGES.includes(pkg.name)) { @@ -50,7 +86,7 @@ export const run = async (options: IUpdateOptions = {}): Promise => { name: pkg.name, currentVersion: pkg.version, latestVersion: latestVersion || 'unknown', - packageManager: pm, + packageManager: pmInfo.name, needsUpdate: latestVersion ? pmUtil.isNewerVersion(pkg.version, latestVersion) : false, }); } diff --git a/ts/tools.cli.ts b/ts/tools.cli.ts index c296ec5..4964147 100644 --- a/ts/tools.cli.ts +++ b/ts/tools.cli.ts @@ -7,15 +7,17 @@ export const run = async () => { toolsCli.standardCommand().subscribe(async (argvArg) => { console.log('@git.zone/tools - CLI utility for managing @git.zone packages\n'); console.log('Commands:'); - console.log(' update Check and update globally installed @git.zone packages'); - console.log(' update -y Update without confirmation prompt'); + console.log(' update Check and update globally installed @git.zone packages'); + console.log(' update -y Update without confirmation prompt'); + console.log(' update --verbose Show detection diagnostics'); console.log(''); console.log('Use "gtools --help" for more information about a command.'); }); toolsCli.addCommand('update').subscribe(async (argvArg) => { const yesFlag = argvArg.y === true || argvArg.yes === true; - await modUpdate.run({ yes: yesFlag }); + const verboseFlag = argvArg.v === true || argvArg.verbose === true; + await modUpdate.run({ yes: yesFlag, verbose: verboseFlag }); }); toolsCli.addVersion('3.0.0');