Skip to content

Commit 98a24d2

Browse files
committed
feat: Nevermore CLI also warns if you're in a local development mode, and checks for updates quicker
1 parent 177c820 commit 98a24d2

File tree

2 files changed

+104
-28
lines changed

2 files changed

+104
-28
lines changed

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

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,44 @@ import { OutputHelper } from '@quenty/cli-output-helpers';
99
import { readFile, writeFile } from 'fs/promises';
1010
import { Memoize } from 'typescript-memoize';
1111

12-
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
12+
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
1313

1414
interface VersionCache {
1515
lastCheck: number;
1616
latestVersion: string;
1717
currentVersion: string;
18+
isLocalDev: boolean;
1819
}
1920

2021
interface UpdateCheckResult {
2122
updateAvailable: boolean;
2223
currentVersion: string;
2324
latestVersion: string;
25+
isLocalDev: boolean;
2426
}
2527

2628
interface VersionCheckerOptions {
2729
packageName: string;
2830
humanReadableName?: string;
2931
registryUrl: string;
30-
currentVersion?: string;
3132
packageJsonPath?: string;
3233
updateCommand?: string;
3334
verbose?: boolean;
3435
}
3536

37+
interface OurVersionData {
38+
isLocalDev: boolean;
39+
version: string;
40+
}
41+
3642
export class VersionChecker {
3743
public static async checkForUpdatesAsync(
3844
options: VersionCheckerOptions
3945
): Promise<UpdateCheckResult | undefined> {
4046
try {
4147
return await VersionChecker._checkForUpdatesInternalAsync(options);
4248
} catch (error) {
43-
const name = options.humanReadableName || options.packageName;
49+
const name = VersionChecker._getDisplayName(options);
4450
OutputHelper.box(
4551
`Failed to check for updates for ${name} due to ${error}`
4652
);
@@ -55,40 +61,57 @@ export class VersionChecker {
5561
const {
5662
packageName,
5763
registryUrl,
58-
currentVersion,
5964
packageJsonPath,
6065
updateCommand = `npm install -g ${packageName}@latest`,
6166
} = options;
6267

63-
const version = await VersionChecker._queryOurVersionAsync(
64-
currentVersion,
68+
const versionData = await VersionChecker._queryOurVersionAsync(
6569
packageJsonPath
6670
);
67-
if (!version) {
71+
if (!versionData) {
6872
if (options.verbose) {
73+
const name = VersionChecker._getDisplayName(options);
6974
OutputHelper.error(
70-
`Could not determine current version for ${packageName}, skipping update check.`
75+
`Could not determine current version for ${name}, skipping update check.`
7176
);
7277
}
7378
return undefined;
7479
}
7580

7681
const result = await VersionChecker._queryUpdateStateAsync(
7782
packageName,
78-
version,
83+
versionData,
7984
registryUrl
8085
);
8186

8287
if (options.verbose) {
88+
const currentyVersionDisplayName =
89+
VersionChecker._getLocalVersionDisplayName(versionData);
90+
8391
OutputHelper.info(
84-
`Checked for updates for ${packageName}. Current version: ${result.currentVersion}, Latest version: ${result.latestVersion}, and update available: ${result.updateAvailable}`
92+
`Checked for updates for ${packageName}. Current version: ${currentyVersionDisplayName}, Latest version: ${result.latestVersion}, and update available: ${result.updateAvailable}`
8593
);
8694
}
8795

88-
if (result.updateAvailable) {
89-
const name = options.humanReadableName || packageName;
96+
if (result.isLocalDev) {
97+
const name = VersionChecker._getDisplayName(options);
98+
const text = [
99+
`${name} is running in local development mode`,
100+
'',
101+
OutputHelper.formatHint(
102+
`Run '${updateCommand}' to switch to production copy`
103+
),
104+
'',
105+
'This will result in less errors.',
106+
].join('\n');
107+
108+
OutputHelper.box(text, { centered: true });
109+
} else if (result.updateAvailable) {
110+
const name = VersionChecker._getDisplayName(options);
111+
const currentyVersionDisplayName =
112+
VersionChecker._getLocalVersionDisplayName(versionData);
90113
const text = [
91-
`${name} update available: ${result.currentVersion}${result.latestVersion}`,
114+
`${name} update available: ${currentyVersionDisplayName}${result.latestVersion}`,
92115
'',
93116
OutputHelper.formatHint(`Run '${updateCommand}' to update`),
94117
].join('\n');
@@ -99,28 +122,70 @@ export class VersionChecker {
99122
return result;
100123
}
101124

125+
private static _getDisplayName(options: VersionCheckerOptions): string {
126+
return options.humanReadableName || options.packageName;
127+
}
128+
129+
/**
130+
* Helper method to get version display name from UpdateCheckResult
131+
*/
132+
public static getVersionDisplayName(versionData: UpdateCheckResult): string {
133+
return VersionChecker._getLocalVersionDisplayName({
134+
isLocalDev: versionData.isLocalDev,
135+
version: versionData.currentVersion,
136+
});
137+
}
138+
139+
private static _getLocalVersionDisplayName(
140+
versionData: OurVersionData
141+
): string {
142+
return versionData.isLocalDev
143+
? `${versionData.version}-local-copy`
144+
: versionData.version;
145+
}
146+
102147
@Memoize()
103148
private static async _queryOurVersionAsync(
104-
currentVersion: string | undefined,
105149
packageJsonPath: string | undefined
106-
): Promise<string | null> {
107-
if (currentVersion) {
108-
return currentVersion;
109-
}
110-
150+
): Promise<OurVersionData | null> {
111151
if (!packageJsonPath) {
112152
throw new Error(
113153
'Either currentVersion or packageJsonPath must be provided to determine the current version.'
114154
);
115155
}
116156

117157
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf8'));
118-
return pkg.version || null;
158+
159+
// Check dependencies and see if they're workspace* or link:* or file:* instead of a version
160+
function isLinkedDependencies(
161+
deps: Record<string, string> | undefined
162+
): boolean {
163+
if (!deps) {
164+
return false;
165+
}
166+
167+
return Object.values(deps).some(
168+
(dep) =>
169+
dep.startsWith('workspace:') ||
170+
dep.startsWith('link:') ||
171+
dep.startsWith('file:')
172+
);
173+
}
174+
175+
const isLocalDev =
176+
isLinkedDependencies(pkg.dependencies) ||
177+
isLinkedDependencies(pkg.devDependencies) ||
178+
isLinkedDependencies(pkg.peerDependencies);
179+
180+
return {
181+
isLocalDev: isLocalDev,
182+
version: pkg.version || null,
183+
};
119184
}
120185

121186
private static async _queryUpdateStateAsync(
122187
packageName: string,
123-
currentVersion: string,
188+
versionData: OurVersionData,
124189
registryUrl: string
125190
): Promise<UpdateCheckResult> {
126191
// Use a simple cache file in the user's home directory
@@ -145,12 +210,17 @@ export class VersionChecker {
145210
if (
146211
cachedData &&
147212
(now - cachedData.lastCheck < CHECK_INTERVAL_MS ||
148-
cachedData.currentVersion !== currentVersion)
213+
cachedData.currentVersion !== versionData.version ||
214+
cachedData.isLocalDev !== versionData.isLocalDev)
149215
) {
150216
return {
151-
updateAvailable: semver.gt(cachedData.latestVersion, currentVersion),
152-
currentVersion: currentVersion,
217+
updateAvailable: semver.gt(
218+
cachedData.latestVersion,
219+
versionData.version
220+
),
221+
currentVersion: versionData.version,
153222
latestVersion: cachedData.latestVersion,
223+
isLocalDev: versionData.isLocalDev,
154224
};
155225
}
156226

@@ -165,7 +235,8 @@ export class VersionChecker {
165235
const newCache: VersionCache = {
166236
lastCheck: now,
167237
latestVersion: latestVersionString,
168-
currentVersion: currentVersion,
238+
currentVersion: versionData.version,
239+
isLocalDev: versionData.isLocalDev,
169240
};
170241
const newResults = loadedCacheData || {};
171242
newResults[cacheKey] = newCache;
@@ -179,9 +250,10 @@ export class VersionChecker {
179250

180251
// Return whether update is available
181252
return {
182-
updateAvailable: semver.gt(latestVersionString, currentVersion),
183-
currentVersion: currentVersion,
253+
updateAvailable: semver.gt(latestVersionString, versionData.version),
254+
currentVersion: versionData.version,
184255
latestVersion: latestVersionString,
256+
isLocalDev: versionData.isLocalDev,
185257
};
186258
}
187259
}

tools/nevermore-cli/src/nevermore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ const versionData = await VersionChecker.checkForUpdatesAsync({
3131

3232
yargs(hideBin(process.argv))
3333
.scriptName('nevermore')
34-
.version(versionData?.currentVersion as any)
34+
.version(
35+
(versionData
36+
? VersionChecker.getVersionDisplayName(versionData)
37+
: undefined) as any
38+
)
3539
.option('yes', {
3640
description: 'True if this run should not prompt the user in any way',
3741
default: false,

0 commit comments

Comments
 (0)