Skip to content

Commit 9512418

Browse files
committed
fix: Record current version in yargs
1 parent d86930c commit 9512418

File tree

5 files changed

+141
-118
lines changed

5 files changed

+141
-118
lines changed

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/nevermore-cli-helpers/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"@types/node": "^18.11.4",
3131
"@types/semver": "^7.5.0",
3232
"prettier": "2.7.1",
33-
"typescript": "^5.9.3"
33+
"typescript": "^5.9.3",
34+
"typescript-memoize": "^1.1.1"
3435
},
3536
"scripts": {
3637
"build": "tsc --build",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { checkForUpdatesAsync } from './version-checker.js';
1+
export { VersionChecker } from './version-checker.js';

tools/nevermore-cli-helpers/src/version-checker.ts

Lines changed: 132 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as path from 'path';
77
import * as semver from 'semver';
88
import { OutputHelper } from '@quenty/cli-output-helpers';
99
import { readFile, writeFile } from 'fs/promises';
10+
import { Memoize } from 'typescript-memoize';
1011

1112
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
1213

@@ -32,137 +33,155 @@ interface VersionCheckerOptions {
3233
verbose?: boolean;
3334
}
3435

35-
export async function checkForUpdatesAsync(
36-
options: VersionCheckerOptions
37-
): Promise<void> {
38-
try {
39-
await checkForUpdatesInternalAsync(options);
40-
} catch (error) {
41-
const name = options.humanReadableName || options.packageName;
42-
OutputHelper.box(`Failed to check for updates for ${name} due to ${error}`);
36+
export class VersionChecker {
37+
public static async checkForUpdatesAsync(
38+
options: VersionCheckerOptions
39+
): Promise<UpdateCheckResult | undefined> {
40+
try {
41+
return await VersionChecker._checkForUpdatesInternalAsync(options);
42+
} catch (error) {
43+
const name = options.humanReadableName || options.packageName;
44+
OutputHelper.box(
45+
`Failed to check for updates for ${name} due to ${error}`
46+
);
47+
}
48+
49+
return undefined;
4350
}
44-
}
4551

46-
async function checkForUpdatesInternalAsync(
47-
options: VersionCheckerOptions
48-
): Promise<void> {
49-
const {
50-
packageName,
51-
registryUrl,
52-
currentVersion,
53-
packageJsonPath,
54-
updateCommand = `npm install -g ${packageName}@latest`,
55-
} = options;
56-
57-
const version = await queryOurVersionAsync(currentVersion, packageJsonPath);
58-
if (!version) {
52+
private static async _checkForUpdatesInternalAsync(
53+
options: VersionCheckerOptions
54+
): Promise<UpdateCheckResult | undefined> {
55+
const {
56+
packageName,
57+
registryUrl,
58+
currentVersion,
59+
packageJsonPath,
60+
updateCommand = `npm install -g ${packageName}@latest`,
61+
} = options;
62+
63+
const version = await VersionChecker._queryOurVersionAsync(
64+
currentVersion,
65+
packageJsonPath
66+
);
67+
if (!version) {
68+
if (options.verbose) {
69+
OutputHelper.error(
70+
`Could not determine current version for ${packageName}, skipping update check.`
71+
);
72+
}
73+
return undefined;
74+
}
75+
76+
const result = await VersionChecker._queryUpdateStateAsync(
77+
packageName,
78+
version,
79+
registryUrl
80+
);
81+
5982
if (options.verbose) {
60-
OutputHelper.error(
61-
`Could not determine current version for ${packageName}, skipping update check.`
83+
OutputHelper.info(
84+
`Checked for updates for ${packageName}. Current version: ${result.currentVersion}, Latest version: ${result.latestVersion}, and update available: ${result.updateAvailable}`
6285
);
6386
}
64-
return;
65-
}
6687

67-
const result = await queryUpdateStateAsync(packageName, version, registryUrl);
88+
if (result.updateAvailable) {
89+
const name = options.humanReadableName || packageName;
90+
const text = [
91+
`${name} update available: ${result.currentVersion}${result.latestVersion}`,
92+
'',
93+
OutputHelper.formatHint(`Run '${updateCommand}' to update`),
94+
].join('\n');
6895

69-
if (options.verbose) {
70-
OutputHelper.info(
71-
`Checked for updates for ${packageName}. Current version: ${result.currentVersion}, Latest version: ${result.latestVersion}, and update available: ${result.updateAvailable}`
72-
);
96+
OutputHelper.box(text, { centered: true });
97+
}
98+
99+
return result;
73100
}
74101

75-
if (result.updateAvailable) {
76-
const name = options.humanReadableName || packageName;
77-
const text = [
78-
`${name} update available: ${result.currentVersion}${result.latestVersion}`,
79-
'',
80-
OutputHelper.formatHint(`Run '${updateCommand}' to update`),
81-
].join('\n');
102+
@Memoize()
103+
private static async _queryOurVersionAsync(
104+
currentVersion: string | undefined,
105+
packageJsonPath: string | undefined
106+
): Promise<string | null> {
107+
if (currentVersion) {
108+
return currentVersion;
109+
}
82110

83-
OutputHelper.box(text, { centered: true });
84-
}
85-
}
111+
if (!packageJsonPath) {
112+
throw new Error(
113+
'Either currentVersion or packageJsonPath must be provided to determine the current version.'
114+
);
115+
}
86116

87-
async function queryOurVersionAsync(
88-
currentVersion: string | undefined,
89-
packageJsonPath: string | undefined
90-
): Promise<string | null> {
91-
if (currentVersion) {
92-
return currentVersion;
117+
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8'));
118+
return pkg.version || null;
93119
}
94120

95-
if (!packageJsonPath) {
96-
throw new Error(
97-
'Either currentVersion or packageJsonPath must be provided to determine the current version.'
98-
);
99-
}
121+
private static async _queryUpdateStateAsync(
122+
packageName: string,
123+
currentVersion: string,
124+
registryUrl: string
125+
): Promise<UpdateCheckResult> {
126+
// Use a simple cache file in the user's home directory
127+
const cacheKey = `${packageName
128+
.replace('/', '-')
129+
.replace('@', '')}-version`;
130+
const cacheFile = path.join(os.homedir(), '.nevermore-version-cache');
131+
132+
// Try to read cached data
133+
let cachedData: VersionCache | undefined;
134+
let loadedCacheData;
135+
try {
136+
const cacheContent = await readFile(cacheFile, 'utf-8');
137+
loadedCacheData = JSON.parse(cacheContent);
138+
cachedData = loadedCacheData[cacheKey] as VersionCache | undefined;
139+
} catch (error) {
140+
// Cache file doesn't exist or is invalid, will check for updates
141+
}
100142

101-
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8'));
102-
return pkg.version || null;
103-
}
143+
// If we checked recently, skip
144+
const now = Date.now();
145+
if (
146+
cachedData &&
147+
(now - cachedData.lastCheck < CHECK_INTERVAL_MS ||
148+
cachedData.currentVersion !== currentVersion)
149+
) {
150+
return {
151+
updateAvailable: semver.gt(cachedData.latestVersion, currentVersion),
152+
currentVersion: currentVersion,
153+
latestVersion: cachedData.latestVersion,
154+
};
155+
}
104156

105-
async function queryUpdateStateAsync(
106-
packageName: string,
107-
currentVersion: string,
108-
registryUrl: string
109-
): Promise<UpdateCheckResult> {
110-
// Use a simple cache file in the user's home directory
111-
const cacheKey = `${packageName.replace('/', '-').replace('@', '')}-version`;
112-
const cacheFile = path.join(os.homedir(), '.nevermore-version-cache');
113-
114-
// Try to read cached data
115-
let cachedData: VersionCache | undefined;
116-
let loadedCacheData;
117-
try {
118-
const cacheContent = await readFile(cacheFile, 'utf-8');
119-
loadedCacheData = JSON.parse(cacheContent);
120-
cachedData = loadedCacheData[cacheKey] as VersionCache | undefined;
121-
} catch (error) {
122-
// Cache file doesn't exist or is invalid, will check for updates
123-
}
157+
const { default: latestVersion } = await import('latest-version');
124158

125-
// If we checked recently, skip
126-
const now = Date.now();
127-
if (
128-
cachedData &&
129-
(now - cachedData.lastCheck < CHECK_INTERVAL_MS ||
130-
cachedData.currentVersion !== currentVersion)
131-
) {
132-
return {
133-
updateAvailable: semver.gt(cachedData.latestVersion, currentVersion),
159+
// Check for new version
160+
const latestVersionString = await latestVersion(packageName, {
161+
registryUrl: registryUrl,
162+
});
163+
164+
// Save to cache
165+
const newCache: VersionCache = {
166+
lastCheck: now,
167+
latestVersion: latestVersionString,
134168
currentVersion: currentVersion,
135-
latestVersion: cachedData.latestVersion,
136169
};
137-
}
170+
const newResults = loadedCacheData || {};
171+
newResults[cacheKey] = newCache;
172+
173+
try {
174+
await writeFile(cacheFile, JSON.stringify(newResults, null, 2), 'utf-8');
175+
} catch (error) {
176+
// Ignore cache write errors, update check still worked
177+
OutputHelper.warn(`Failed to write cache file: ${error}`);
178+
}
138179

139-
const { default: latestVersion } = await import('latest-version');
140-
141-
// Check for new version
142-
const latestVersionString = await latestVersion(packageName, {
143-
registryUrl: registryUrl,
144-
});
145-
146-
// Save to cache
147-
const newCache: VersionCache = {
148-
lastCheck: now,
149-
latestVersion: latestVersionString,
150-
currentVersion: currentVersion,
151-
};
152-
const newResults = loadedCacheData || {};
153-
newResults[cacheKey] = newCache;
154-
155-
try {
156-
await writeFile(cacheFile, JSON.stringify(newResults, null, 2), 'utf-8');
157-
} catch (error) {
158-
// Ignore cache write errors, update check still worked
159-
OutputHelper.warn(`Failed to write cache file: ${error}`);
180+
// Return whether update is available
181+
return {
182+
updateAvailable: semver.gt(latestVersionString, currentVersion),
183+
currentVersion: currentVersion,
184+
latestVersion: latestVersionString,
185+
};
160186
}
161-
162-
// Return whether update is available
163-
return {
164-
updateAvailable: semver.gt(latestVersionString, currentVersion),
165-
currentVersion: currentVersion,
166-
latestVersion: latestVersionString,
167-
};
168187
}

tools/nevermore-cli/src/nevermore.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { hideBin } from 'yargs/helpers';
99
import { fileURLToPath } from 'url';
1010
import { dirname, join } from 'path';
1111
import { OutputHelper } from '@quenty/cli-output-helpers';
12-
import { checkForUpdatesAsync } from '@quenty/nevermore-cli-helpers';
12+
import { VersionChecker } from '@quenty/nevermore-cli-helpers';
1313

1414
import { InitGameCommand } from './commands/init-game-command.js';
1515
import { InitPackageCommand } from './commands/init-package-command.js';
@@ -19,7 +19,7 @@ import { InstallPackageCommand } from './commands/install-package-command.js';
1919
import { TestProjectCommand } from './commands/test-project-command.js';
2020
import { DownloadRobloxTypes } from './commands/download-roblox-types.js';
2121

22-
await checkForUpdatesAsync({
22+
const versionData = await VersionChecker.checkForUpdatesAsync({
2323
humanReadableName: 'Nevermore CLI',
2424
packageName: '@quenty/nevermore-cli',
2525
registryUrl: 'https://registry.npmjs.org/',
@@ -31,7 +31,7 @@ await checkForUpdatesAsync({
3131

3232
yargs(hideBin(process.argv))
3333
.scriptName('nevermore')
34-
.version()
34+
.version(versionData?.currentVersion as any)
3535
.option('yes', {
3636
description: 'True if this run should not prompt the user in any way',
3737
default: false,

0 commit comments

Comments
 (0)