packages/ros-cdk-cli/lib/version.ts (55 lines of code) (raw):
import { exec as _exec } from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as semver from 'semver';
import { promisify } from 'util';
import { debug } from './logging';
import { cdkCacheDir } from './util/directories';
const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
const exec = promisify(_exec);
export const DISPLAY_VERSION = `${versionNumber()}`;
export function versionNumber(): string {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('../package.json').version.replace(/\+[0-9a-f]+$/, '');
}
export class VersionCheckTTL {
public static timestampFilePath(): string {
// Using the same path from account-cache.ts
return path.join(cdkCacheDir(), 'repo-version-ttl');
}
private readonly file: string;
// File modify times are accurate only to the second
private readonly ttlSecs: number;
constructor(file?: string, ttlSecs?: number) {
this.file = file || VersionCheckTTL.timestampFilePath();
try {
fs.mkdirsSync(path.dirname(this.file));
fs.accessSync(path.dirname(this.file), fs.constants.W_OK);
} catch {
throw new Error(`Directory (${path.dirname(this.file)}) is not writable.`);
}
this.ttlSecs = ttlSecs || ONE_DAY_IN_SECONDS;
}
public async hasExpired(): Promise<boolean> {
try {
const lastCheckTime = (await fs.stat(this.file)).mtimeMs;
const today = new Date().getTime();
if ((today - lastCheckTime) / 1000 > this.ttlSecs) {
// convert ms to sec
return true;
}
return false;
} catch (err) {
if (err.code === 'ENOENT') {
return true;
} else {
throw err;
}
}
}
public async update(latestVersion?: string): Promise<void> {
if (!latestVersion) {
latestVersion = '';
}
await fs.writeFile(this.file, latestVersion);
}
}
// Export for unit testing only.
// Don't use directly, use displayVersionMessage() instead.
export async function latestVersionIfHigher(
currentVersion: string,
cacheFile: VersionCheckTTL,
): Promise<string | null> {
if (!(await cacheFile.hasExpired())) {
return null;
}
const { stdout, stderr } = await exec('npm view ros-cdk version');
if (stderr && stderr.trim().length > 0) {
debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`);
}
const latestVersion = stdout.trim();
if (!semver.valid(latestVersion)) {
throw new Error(`npm returned an invalid semver ${latestVersion}`);
}
const isNewer = semver.gt(latestVersion, currentVersion);
await cacheFile.update(latestVersion);
if (isNewer) {
return latestVersion;
} else {
return null;
}
}