Skip to content

Commit abe5af5

Browse files
committed
a faster but overly verbose cli upgrade check
1 parent e6c0b56 commit abe5af5

File tree

2 files changed

+194
-4
lines changed

2 files changed

+194
-4
lines changed

src/utils/versionCache.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { homedir } from 'os';
2+
import { join } from 'path';
3+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4+
import { Logger } from './logger.js';
5+
import semver from 'semver';
6+
7+
const logger = new Logger({ name: 'version-cache' });
8+
9+
/**
10+
* Interface for the version cache data structure
11+
*/
12+
export interface VersionCacheData {
13+
/** The cached version string */
14+
version: string;
15+
}
16+
17+
/**
18+
* Class to manage version cache in the user's home directory
19+
*/
20+
export class VersionCache {
21+
private readonly cacheDir: string;
22+
private readonly cacheFile: string;
23+
24+
constructor() {
25+
this.cacheDir = join(homedir(), '.mycoder');
26+
this.cacheFile = join(this.cacheDir, 'version-cache.json');
27+
28+
// Ensure cache directory exists
29+
this.ensureCacheDir();
30+
}
31+
32+
/**
33+
* Ensures the cache directory exists
34+
*/
35+
private ensureCacheDir(): void {
36+
try {
37+
if (!existsSync(this.cacheDir)) {
38+
mkdirSync(this.cacheDir, { recursive: true });
39+
}
40+
} catch (error) {
41+
logger.warn(
42+
'Failed to create cache directory ~/.mycoder:',
43+
error instanceof Error ? error.message : String(error)
44+
);
45+
}
46+
}
47+
48+
/**
49+
* Reads the cache data from disk
50+
* @returns The cached data or null if not found/invalid
51+
*/
52+
read(): VersionCacheData | null {
53+
try {
54+
if (!existsSync(this.cacheFile)) {
55+
return null;
56+
}
57+
58+
const data = JSON.parse(
59+
readFileSync(this.cacheFile, 'utf-8')
60+
) as VersionCacheData;
61+
62+
// Validate required fields
63+
if (!data || typeof data.version !== 'string') {
64+
return null;
65+
}
66+
67+
return data;
68+
} catch (error) {
69+
logger.warn(
70+
'Error reading version cache:',
71+
error instanceof Error ? error.message : String(error)
72+
);
73+
return null;
74+
}
75+
}
76+
77+
/**
78+
* Writes data to the cache file
79+
* @param data The version cache data to write
80+
*/
81+
write(data: VersionCacheData): void {
82+
try {
83+
writeFileSync(this.cacheFile, JSON.stringify(data, null, 2));
84+
} catch (error) {
85+
logger.warn(
86+
'Error writing version cache:',
87+
error instanceof Error ? error.message : String(error)
88+
);
89+
}
90+
}
91+
92+
/**
93+
* Checks if the cached version is greater than the current version
94+
* @param currentVersion The current package version
95+
* @returns true if cached version > current version, false otherwise
96+
*/
97+
shouldCheckServer(currentVersion: string): boolean {
98+
try {
99+
const data = this.read();
100+
if (!data) {
101+
return true; // No cache, should check server
102+
}
103+
104+
// If cached version is greater than current, we should check server
105+
// as an upgrade is likely available
106+
return semver.gt(data.version, currentVersion);
107+
} catch (error) {
108+
logger.warn(
109+
'Error comparing versions:',
110+
error instanceof Error ? error.message : String(error)
111+
);
112+
return true; // On error, check server to be safe
113+
}
114+
}
115+
116+
/**
117+
* Clears the cache file
118+
*/
119+
clear(): void {
120+
try {
121+
if (existsSync(this.cacheFile)) {
122+
writeFileSync(this.cacheFile, '');
123+
}
124+
} catch (error) {
125+
logger.warn(
126+
'Error clearing version cache:',
127+
error instanceof Error ? error.message : String(error)
128+
);
129+
}
130+
}
131+
}

src/utils/versionCheck.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { Logger } from "./logger.js";
22
import chalk from "chalk";
33
import { createRequire } from "module";
44
import type { PackageJson } from "type-fest";
5+
import { VersionCache } from './versionCache.js';
6+
import semver from 'semver';
57

68
const require = createRequire(import.meta.url);
79
const logger = new Logger({ name: "version-check" });
10+
const versionCache = new VersionCache();
811

912
/**
1013
* Gets the current package info from package.json
@@ -60,16 +63,53 @@ export function generateUpgradeMessage(
6063
latestVersion: string,
6164
packageName: string,
6265
): string | null {
63-
return currentVersion !== latestVersion
66+
return semver.gt(latestVersion, currentVersion)
6467
? chalk.green(
6568
` Update available: ${currentVersion}${latestVersion}\n Run 'npm install -g ${packageName}' to update`,
6669
)
6770
: null;
6871
}
6972

73+
/**
74+
* Updates the version cache
75+
*/
76+
async function updateVersionCache(
77+
packageName: string,
78+
version: string,
79+
): Promise<void> {
80+
try {
81+
versionCache.write({ version });
82+
} catch (error) {
83+
logger.warn(
84+
"Error updating version cache:",
85+
error instanceof Error ? error.message : String(error),
86+
);
87+
}
88+
}
89+
90+
/**
91+
* Performs a background version check and updates the cache
92+
*/
93+
async function backgroundVersionCheck(
94+
packageName: string,
95+
): Promise<void> {
96+
try {
97+
const latestVersion = await fetchLatestVersion(packageName);
98+
if (latestVersion) {
99+
await updateVersionCache(packageName, latestVersion);
100+
}
101+
} catch (error) {
102+
logger.warn(
103+
"Background version check failed:",
104+
error instanceof Error ? error.message : String(error),
105+
);
106+
}
107+
}
108+
70109
/**
71110
* Checks if a newer version of the package is available on npm.
72-
* Only runs check when package is installed globally.
111+
* - Immediately returns cached version check result if available
112+
* - Always performs a background check to update cache for next time
73113
*
74114
* @returns Upgrade message string if update available, null otherwise
75115
*/
@@ -82,13 +122,32 @@ export async function checkForUpdates(): Promise<string | null> {
82122
return null;
83123
}
84124

85-
const latestVersion = await fetchLatestVersion(packageName);
125+
// Always trigger background version check for next time
126+
setImmediate(() => {
127+
backgroundVersionCheck(packageName).catch(error => {
128+
logger.warn(
129+
"Background version check failed:",
130+
error instanceof Error ? error.message : String(error),
131+
);
132+
});
133+
});
134+
135+
// Use cached data for immediate response if available
136+
const cachedData = versionCache.read();
137+
if (cachedData) {
138+
return generateUpgradeMessage(currentVersion, cachedData.version, packageName);
139+
}
86140

141+
// No cache available, need to check server
142+
const latestVersion = await fetchLatestVersion(packageName);
87143
if (!latestVersion) {
88144
logger.warn("Unable to determine latest published version");
89145
return null;
90146
}
91147

148+
// Update cache with result
149+
await updateVersionCache(packageName, latestVersion);
150+
92151
return generateUpgradeMessage(currentVersion, latestVersion, packageName);
93152
} catch (error) {
94153
// Log error but don't throw to handle gracefully
@@ -98,4 +157,4 @@ export async function checkForUpdates(): Promise<string | null> {
98157
);
99158
return null;
100159
}
101-
}
160+
}

0 commit comments

Comments
 (0)