feat(mod_update): add self-update flow, package name parser, dynamic CLI version, and tests

This commit is contained in:
2026-02-06 00:19:17 +00:00
parent 57e4d1c043
commit 65906f7e5f
7 changed files with 172 additions and 9 deletions

View File

@@ -191,12 +191,11 @@ export class PackageManagerUtil {
const data = JSON.parse(line);
if (data.type === 'tree' && data.data && data.data.trees) {
for (const tree of data.data.trees) {
const name = tree.name?.split('@')[0] || '';
if (name.startsWith('@git.zone/')) {
const version = tree.name?.split('@').pop() || 'unknown';
const parsed = PackageManagerUtil.parseYarnPackageName(tree.name || '');
if (parsed.name.startsWith('@git.zone/')) {
packages.push({
name,
version,
name: parsed.name,
version: parsed.version,
packageManager: pm,
});
}
@@ -277,6 +276,26 @@ export class PackageManagerUtil {
}
}
/**
* Parse a yarn package name string like "@git.zone/cli@1.0.0" into name and version.
* Handles scoped packages correctly by splitting on the last '@' (version separator).
*/
public static parseYarnPackageName(fullName: string): { name: string; version: string } {
if (!fullName) {
return { name: '', version: 'unknown' };
}
const lastAtIndex = fullName.lastIndexOf('@');
// If lastAtIndex is 0, the string is just "@something" with no version
// If lastAtIndex is -1, there's no '@' at all
if (lastAtIndex <= 0) {
return { name: fullName, version: 'unknown' };
}
return {
name: fullName.substring(0, lastAtIndex),
version: fullName.substring(lastAtIndex + 1) || 'unknown',
};
}
/**
* Compare two semver versions
* Returns true if latest > current

View File

@@ -1,6 +1,10 @@
import * as plugins from './mod.plugins.js';
import { PackageManagerUtil, type TPackageManager, type IPackageUpdateInfo, type IPackageManagerInfo } from './classes.packagemanager.js';
import { commitinfo } from '../00_commitinfo_data.js';
// Curated list of known @git.zone CLI tools to track for updates.
// This list is intentionally hardcoded to only track official tools.
// Add new entries here when new @git.zone packages are published.
const GITZONE_PACKAGES = [
'@git.zone/cli',
'@git.zone/tsdoc',
@@ -73,6 +77,54 @@ export const run = async (options: IUpdateOptions = {}): Promise<void> => {
console.log('');
// === Self-update check ===
console.log('Checking for gtools self-update...\n');
const selfVersion = commitinfo.version;
const selfLatest = await pmUtil.getLatestVersion('@git.zone/tools');
if (selfLatest && pmUtil.isNewerVersion(selfVersion, selfLatest)) {
console.log(` @git.zone/tools ${selfVersion}${selfLatest} ⬆️ Update available\n`);
// Find which PM has it installed globally
let selfPm: TPackageManager | null = null;
for (const pmInfo of detectedPMs) {
const installed = await pmUtil.getInstalledPackages(pmInfo.name);
if (installed.some(p => p.name === '@git.zone/tools')) {
selfPm = pmInfo.name;
break;
}
}
if (!selfPm) {
// Fallback: use first available PM
selfPm = detectedPMs[0].name;
}
let shouldSelfUpdate = options.yes === true;
if (!shouldSelfUpdate) {
const smartinteractInstance = new plugins.smartinteract.SmartInteract();
const answer = await smartinteractInstance.askQuestion({
type: 'confirm',
name: 'confirmSelfUpdate',
message: 'Do you want to update gtools itself first?',
default: true,
});
shouldSelfUpdate = answer.value === true;
}
if (shouldSelfUpdate) {
const success = await pmUtil.executeUpdate(selfPm, '@git.zone/tools');
if (success) {
console.log('\ngtools has been updated. Please re-run "gtools update" to check remaining packages.');
process.exit(0);
} else {
console.log('\ngtools self-update failed. Continuing with current version...\n');
}
}
} else {
console.log(` @git.zone/tools ${selfVersion} ✓ Up to date\n`);
}
// Collect all installed @git.zone packages from all package managers
const allPackages: IPackageUpdateInfo[] = [];