fix: rename all packages from @mosaic/* to @mosaicstack/*
- Updated all package.json name fields and dependency references - Updated all TypeScript/JavaScript imports - Updated .woodpecker/publish.yml filters and registry paths - Updated tools/install.sh scope default - Updated .npmrc registry paths (worktree + host) - Enhanced update-checker.ts with checkForAllUpdates() multi-package support - Updated CLI update command to show table of all packages - Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand - Marked checkForUpdate() with @deprecated JSDoc Closes #391
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Mosaic update checker — compares the installed @mosaic/mosaic package
|
||||
* Mosaic update checker — compares the installed @mosaicstack/mosaic package
|
||||
* against the Gitea npm registry and reports when an upgrade is available.
|
||||
*
|
||||
* Used by:
|
||||
@@ -39,8 +39,8 @@ export interface UpdateCheckResult {
|
||||
|
||||
// ─── Constants ──────────────────────────────────────────────────────────────
|
||||
|
||||
const REGISTRY = 'https://git.mosaicstack.dev/api/packages/mosaic/npm/';
|
||||
const PKG = '@mosaic/mosaic';
|
||||
const REGISTRY = 'https://git.mosaicstack.dev/api/packages/mosaicstack/npm/';
|
||||
const PKG = '@mosaicstack/mosaic';
|
||||
const CACHE_DIR = join(homedir(), '.cache', 'mosaic');
|
||||
const CACHE_FILE = join(CACHE_DIR, 'update-check.json');
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
||||
@@ -124,20 +124,42 @@ export function semverLt(a: string, b: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── Known packages for checkForAllUpdates() ──────────────────────────────
|
||||
|
||||
const KNOWN_PACKAGES = [
|
||||
'@mosaicstack/mosaic',
|
||||
'@mosaicstack/cli',
|
||||
'@mosaicstack/gateway',
|
||||
'@mosaicstack/quality-rails',
|
||||
];
|
||||
|
||||
// ─── Multi-package types ──────────────────────────────────────────────────
|
||||
|
||||
export interface PackageUpdateResult {
|
||||
/** Package name */
|
||||
package: string;
|
||||
/** Currently installed version (empty if not installed) */
|
||||
current: string;
|
||||
/** Latest published version (empty if check failed) */
|
||||
latest: string;
|
||||
/** True when a newer version is available */
|
||||
updateAvailable: boolean;
|
||||
}
|
||||
|
||||
// ─── Cache ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Cache stores only the latest registry version (the expensive network call).
|
||||
/** Cache stores the latest registry versions for all checked packages.
|
||||
* The installed version is always checked fresh — it's a local `npm ls`. */
|
||||
interface RegistryCache {
|
||||
latest: string;
|
||||
interface AllPackagesCache {
|
||||
packages: Record<string, { latest: string }>;
|
||||
checkedAt: string;
|
||||
registry: string;
|
||||
}
|
||||
|
||||
function readCache(): RegistryCache | null {
|
||||
function readAllCache(): AllPackagesCache | null {
|
||||
try {
|
||||
if (!existsSync(CACHE_FILE)) return null;
|
||||
const raw = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) as RegistryCache;
|
||||
const raw = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) as AllPackagesCache;
|
||||
const age = Date.now() - new Date(raw.checkedAt).getTime();
|
||||
if (age > CACHE_TTL_MS) return null;
|
||||
return raw;
|
||||
@@ -146,7 +168,7 @@ function readCache(): RegistryCache | null {
|
||||
}
|
||||
}
|
||||
|
||||
function writeCache(entry: RegistryCache): void {
|
||||
function writeAllCache(entry: AllPackagesCache): void {
|
||||
try {
|
||||
mkdirSync(CACHE_DIR, { recursive: true });
|
||||
writeFileSync(CACHE_FILE, JSON.stringify(entry, null, 2) + '\n', 'utf-8');
|
||||
@@ -155,10 +177,28 @@ function writeCache(entry: RegistryCache): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy single-package cache (backward compat)
|
||||
type RegistryCache = { latest: string; checkedAt: string; registry: string };
|
||||
|
||||
function readCache(): RegistryCache | null {
|
||||
const c = readAllCache();
|
||||
if (!c) return null;
|
||||
const mosaicLatest = c.packages[PKG]?.latest;
|
||||
if (!mosaicLatest) return null;
|
||||
return { latest: mosaicLatest, checkedAt: c.checkedAt, registry: c.registry };
|
||||
}
|
||||
|
||||
function writeCache(entry: RegistryCache): void {
|
||||
const existing = readAllCache();
|
||||
const packages = { ...(existing?.packages ?? {}) };
|
||||
packages[PKG] = { latest: entry.latest };
|
||||
writeAllCache({ packages, checkedAt: entry.checkedAt, registry: entry.registry });
|
||||
}
|
||||
|
||||
// ─── Public API ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get the currently installed @mosaic/mosaic version.
|
||||
* Get the currently installed @mosaicstack/mosaic version.
|
||||
*/
|
||||
export function getInstalledVersion(): { name: string; version: string } {
|
||||
try {
|
||||
@@ -179,7 +219,7 @@ export function getInstalledVersion(): { name: string; version: string } {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the latest published @mosaic/mosaic version from the Gitea npm registry.
|
||||
* Fetch the latest published @mosaicstack/mosaic version from the Gitea npm registry.
|
||||
* Returns empty string on failure.
|
||||
*/
|
||||
export function getLatestVersion(): { name: string; version: string } {
|
||||
@@ -199,6 +239,9 @@ export function getInstallCommand(result: Pick<UpdateCheckResult, 'targetPackage
|
||||
* installed version fresh (local npm ls is cheap, caching it causes stale
|
||||
* "update available" banners after an upgrade).
|
||||
* Never throws.
|
||||
*
|
||||
* @deprecated Use checkForAllUpdates() for multi-package checking.
|
||||
* This function is kept for backward compatibility.
|
||||
*/
|
||||
export function checkForUpdate(options?: { skipCache?: boolean }): UpdateCheckResult {
|
||||
const currentInfo = getInstalledVersion();
|
||||
@@ -284,3 +327,146 @@ export function backgroundUpdateCheck(): void {
|
||||
// Silently ignore — never block the user
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Multi-package update check ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Get the currently installed versions of all globally-installed @mosaicstack/* packages.
|
||||
*/
|
||||
function getInstalledVersions(): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
try {
|
||||
const raw = npmExec('ls -g --depth=0 --json 2>/dev/null', 3000);
|
||||
if (raw) {
|
||||
const data = JSON.parse(raw) as {
|
||||
dependencies?: Record<string, { version?: string }>;
|
||||
};
|
||||
for (const [name, info] of Object.entries(data?.dependencies ?? {})) {
|
||||
if (name.startsWith('@mosaicstack/') && info.version) {
|
||||
result[name] = info.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the latest published version of a single package from the registry.
|
||||
*/
|
||||
function getLatestVersionFor(pkgName: string): string {
|
||||
return npmExec(`view ${pkgName} version --registry=${REGISTRY}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all known @mosaicstack/* packages for updates.
|
||||
* Returns an array of per-package results, sorted by package name.
|
||||
* Never throws.
|
||||
*/
|
||||
export function checkForAllUpdates(options?: { skipCache?: boolean }): PackageUpdateResult[] {
|
||||
const installed = getInstalledVersions();
|
||||
const checkedAt = new Date().toISOString();
|
||||
|
||||
// Resolve latest versions (from cache or network)
|
||||
let latestVersions: Record<string, string>;
|
||||
|
||||
if (!options?.skipCache) {
|
||||
const cached = readAllCache();
|
||||
if (cached) {
|
||||
latestVersions = {};
|
||||
for (const pkg of KNOWN_PACKAGES) {
|
||||
const cachedLatest = cached.packages[pkg]?.latest;
|
||||
if (cachedLatest) {
|
||||
latestVersions[pkg] = cachedLatest;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
latestVersions = {};
|
||||
for (const pkg of KNOWN_PACKAGES) {
|
||||
const v = getLatestVersionFor(pkg);
|
||||
if (v) latestVersions[pkg] = v;
|
||||
}
|
||||
writeAllCache({
|
||||
packages: Object.fromEntries(
|
||||
Object.entries(latestVersions).map(([k, v]) => [k, { latest: v }]),
|
||||
),
|
||||
checkedAt,
|
||||
registry: REGISTRY,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
latestVersions = {};
|
||||
for (const pkg of KNOWN_PACKAGES) {
|
||||
const v = getLatestVersionFor(pkg);
|
||||
if (v) latestVersions[pkg] = v;
|
||||
}
|
||||
writeAllCache({
|
||||
packages: Object.fromEntries(
|
||||
Object.entries(latestVersions).map(([k, v]) => [k, { latest: v }]),
|
||||
),
|
||||
checkedAt,
|
||||
registry: REGISTRY,
|
||||
});
|
||||
}
|
||||
|
||||
const results: PackageUpdateResult[] = [];
|
||||
for (const pkg of KNOWN_PACKAGES) {
|
||||
const current = installed[pkg] ?? '';
|
||||
const latest = latestVersions[pkg] ?? '';
|
||||
results.push({
|
||||
package: pkg,
|
||||
current,
|
||||
latest,
|
||||
updateAvailable: !!(current && latest && semverLt(current, latest)),
|
||||
});
|
||||
}
|
||||
|
||||
return results.sort((a, b) => a.package.localeCompare(b.package));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the install command for all outdated packages.
|
||||
*/
|
||||
export function getInstallAllCommand(outdated: PackageUpdateResult[]): string {
|
||||
const pkgs = outdated.filter((r) => r.updateAvailable).map((r) => `${r.package}@latest`);
|
||||
if (pkgs.length === 0) return '';
|
||||
return `npm i -g ${pkgs.join(' ')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a table showing all packages with their current/latest versions.
|
||||
*/
|
||||
export function formatAllPackagesTable(results: PackageUpdateResult[]): string {
|
||||
if (results.length === 0) return 'No @mosaicstack/* packages found.';
|
||||
|
||||
const hasUpdate = results.some((r) => r.updateAvailable);
|
||||
const nameWidth = Math.max(...results.map((r) => r.package.length), 10);
|
||||
const verWidth = 10;
|
||||
|
||||
const header =
|
||||
' ' +
|
||||
'Package'.padEnd(nameWidth + 2) +
|
||||
'Current'.padEnd(verWidth + 2) +
|
||||
'Latest'.padEnd(verWidth + 2) +
|
||||
'Status';
|
||||
const sep = ' ' + '-'.repeat(header.length - 2);
|
||||
|
||||
const rows = results.map((r) => {
|
||||
const status = !r.current
|
||||
? 'not installed'
|
||||
: r.updateAvailable
|
||||
? '⬆ update available'
|
||||
: '✔ up to date';
|
||||
return (
|
||||
' ' +
|
||||
r.package.padEnd(nameWidth + 2) +
|
||||
(r.current || '-').padEnd(verWidth + 2) +
|
||||
(r.latest || '-').padEnd(verWidth + 2) +
|
||||
status
|
||||
);
|
||||
});
|
||||
|
||||
return [header, sep, ...rows].join('\n');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user