@oclif/plugin-warn-if-update-available
Advanced tools
Comparing version 2.1.1 to 2.1.2-qa.0
@@ -1,8 +0,12 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const promises_1 = require("fs/promises"); | ||
const http_call_1 = require("http-call"); | ||
const path_1 = require("path"); | ||
// eslint-disable-next-line max-params | ||
async function run(name, file, version, registry, authorization) { | ||
import makeDebug from 'debug'; | ||
import { HTTP } from 'http-call'; | ||
import { mkdir, writeFile } from 'node:fs/promises'; | ||
import { dirname } from 'node:path'; | ||
async function run([name, file, version, registry, authorization]) { | ||
const debug = makeDebug('get-version'); | ||
debug('name:', name); | ||
debug('file:', file); | ||
debug('version:', version); | ||
debug('registry:', registry); | ||
debug('authorization:', authorization); | ||
const url = [ | ||
@@ -13,9 +17,11 @@ registry.replace(/\/+$/, ''), | ||
const headers = authorization ? { authorization } : {}; | ||
await (0, promises_1.mkdir)((0, path_1.dirname)(file), { recursive: true }); | ||
await (0, promises_1.writeFile)(file, JSON.stringify({ current: version, headers })); // touch file with current version to prevent multiple updates | ||
const { body } = await http_call_1.default.get(url, { headers, timeout: 5000 }); | ||
await (0, promises_1.writeFile)(file, JSON.stringify(Object.assign(Object.assign({}, body['dist-tags']), { current: version, authorization }))); | ||
await mkdir(dirname(file), { recursive: true }); | ||
await writeFile(file, JSON.stringify({ current: version, headers })); // touch file with current version to prevent multiple updates | ||
const { body } = await HTTP.get(url, { headers, timeout: 5000 }); | ||
await writeFile(file, JSON.stringify({ ...body['dist-tags'], authorization, current: version })); | ||
process.exit(0); // eslint-disable-line unicorn/no-process-exit, no-process-exit | ||
} | ||
run(process.argv[2], process.argv[3], process.argv[4], process.argv[5], process.argv[6]) | ||
.catch(require('@oclif/core/handle')); | ||
await run(process.argv.slice(2)).catch(async (error) => { | ||
const { handle } = await import('@oclif/core'); | ||
await handle(error); | ||
}); |
@@ -1,3 +0,23 @@ | ||
import { Hook } from '@oclif/core'; | ||
import { Hook, Interfaces } from '@oclif/core'; | ||
export declare function hasNotBeenMsSinceDate(ms: number, now: Date, date: Date): boolean; | ||
export declare function convertToMs(frequency: number, unit: 'days' | 'hours' | 'milliseconds' | 'minutes' | 'seconds'): number; | ||
export declare function getEnvVarNumber(envVar: string, defaultValue?: number): number | undefined; | ||
export declare function getEnvVarEnum<T extends string>(envVar: string, allowed: T[], defaultValue: T): T; | ||
export declare function getEnvVarEnum<T extends string>(envVar: string, allowed: T[], defaultValue?: T): T | undefined; | ||
export declare function semverGreaterThan(a: string, b: string): boolean; | ||
/** | ||
* Returns the newest version of the CLI from the cache if it is newer than the current version. | ||
* | ||
* Returns undefined early if: | ||
* - `update` command is being run | ||
* - `<CLI>_SKIP_NEW_VERSION_CHECK` is set to true | ||
* - the current version is a prerelease | ||
* - the warning was last shown to the user within the frequency and frequencyUnit | ||
*/ | ||
export declare function getNewerVersion({ argv, config, file, }: { | ||
argv: string[]; | ||
config: Interfaces.Config; | ||
file: string; | ||
}): Promise<string | undefined>; | ||
declare const hook: Hook<'init'>; | ||
export default hook; |
@@ -1,44 +0,103 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const child_process_1 = require("child_process"); | ||
const promises_1 = require("fs/promises"); | ||
const path_1 = require("path"); | ||
import chalk from 'chalk'; | ||
import makeDebug from 'debug'; | ||
import { spawn } from 'node:child_process'; | ||
import { stat as fsStat, readFile, utimes } from 'node:fs/promises'; | ||
import { dirname, join, resolve } from 'node:path'; | ||
import { fileURLToPath } from 'node:url'; | ||
async function readJSON(file) { | ||
const [contents, stat] = await Promise.all([readFile(file, 'utf8'), fsStat(file)]); | ||
return { contents: JSON.parse(contents), stat }; | ||
} | ||
export function hasNotBeenMsSinceDate(ms, now, date) { | ||
const diff = now.getTime() - date.getTime(); | ||
return diff < ms; | ||
} | ||
export function convertToMs(frequency, unit) { | ||
switch (unit) { | ||
case 'days': { | ||
return frequency * 24 * 60 * 60 * 1000; | ||
} | ||
case 'hours': { | ||
return frequency * 60 * 60 * 1000; | ||
} | ||
case 'milliseconds': { | ||
return frequency; | ||
} | ||
case 'minutes': { | ||
return frequency * 60 * 1000; | ||
} | ||
case 'seconds': { | ||
return frequency * 1000; | ||
} | ||
default: { | ||
// default to minutes | ||
return frequency * 60 * 1000; | ||
} | ||
} | ||
} | ||
export function getEnvVarNumber(envVar, defaultValue) { | ||
const envVarRaw = process.env[envVar]; | ||
if (!envVarRaw) | ||
return defaultValue; | ||
const parsed = Number.parseInt(envVarRaw, 10); | ||
if (Number.isNaN(parsed)) | ||
return defaultValue; | ||
return parsed; | ||
} | ||
export function getEnvVarEnum(envVar, allowed, defaultValue) { | ||
const envVarRaw = process.env[envVar]; | ||
if (!envVarRaw) | ||
return defaultValue; | ||
if (!allowed.includes(envVarRaw)) | ||
return defaultValue; | ||
return envVarRaw; | ||
} | ||
export function semverGreaterThan(a, b) { | ||
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }) > 0; | ||
} | ||
/** | ||
* Returns the newest version of the CLI from the cache if it is newer than the current version. | ||
* | ||
* Returns undefined early if: | ||
* - `update` command is being run | ||
* - `<CLI>_SKIP_NEW_VERSION_CHECK` is set to true | ||
* - the current version is a prerelease | ||
* - the warning was last shown to the user within the frequency and frequencyUnit | ||
*/ | ||
export async function getNewerVersion({ argv, config, file, }) { | ||
// do not show warning if running `update` command of <CLI>_SKIP_NEW_VERSION_CHECK=true | ||
if (argv[2] === 'update' || config.scopedEnvVarTrue('SKIP_NEW_VERSION_CHECK')) | ||
return; | ||
// TODO: handle prerelease channels | ||
if (config.version.includes('-')) | ||
return; | ||
const { frequency, frequencyUnit } = config.pjson.oclif['warn-if-update-available'] ?? {}; | ||
const warningFrequency = getEnvVarNumber(config.scopedEnvVarKey('NEW_VERSION_CHECK_FREQ'), frequency); | ||
const warningFrequencyUnit = getEnvVarEnum(config.scopedEnvVarKey('NEW_VERSION_CHECK_FREQ_UNIT'), ['days', 'hours', 'minutes', 'seconds', 'milliseconds'], frequencyUnit ?? 'minutes'); | ||
const tag = config.scopedEnvVar('NEW_VERSION_CHECK_TAG') ?? 'latest'; | ||
const { contents: distTags, stat } = await readJSON(file); | ||
// If the file was modified before the timeout, don't show the warning | ||
if (warningFrequency && | ||
warningFrequencyUnit && | ||
hasNotBeenMsSinceDate(convertToMs(warningFrequency, warningFrequencyUnit), new Date(), stat.mtime)) | ||
return; | ||
if (distTags[tag] && semverGreaterThan(distTags[tag].split('-')[0], config.version.split('-')[0])) | ||
return distTags[tag]; | ||
} | ||
const hook = async function ({ config }) { | ||
const file = (0, path_1.join)(config.cacheDir, 'version'); | ||
const debug = makeDebug('update-check'); | ||
const file = join(config.cacheDir, 'version'); | ||
// Destructure package.json configuration with defaults | ||
const { timeoutInDays = 60, message = '<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>.', registry = 'https://registry.npmjs.org', authorization = '', } = config.pjson.oclif['warn-if-update-available'] || {}; | ||
const checkVersion = async () => { | ||
try { | ||
// do not show warning if updating | ||
if (process.argv[2] === 'update') | ||
return; | ||
const distTags = JSON.parse(await (0, promises_1.readFile)(file, 'utf8')); | ||
if (config.version.includes('-')) { | ||
// to-do: handle channels | ||
return; | ||
} | ||
const semverGt = await Promise.resolve().then(() => require('semver/functions/gt')); | ||
if (distTags && distTags.latest && semverGt(distTags.latest.split('-')[0], config.version.split('-')[0])) { | ||
const chalk = require('chalk'); | ||
// Default message if the user doesn't provide one | ||
const template = require('lodash.template'); | ||
this.warn(template(message)(Object.assign({ chalk, | ||
config }, distTags))); | ||
} | ||
} | ||
catch (error) { | ||
if (error.code !== 'ENOENT') | ||
throw error; | ||
} | ||
}; | ||
const { authorization = '', message = '<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>.', registry = 'https://registry.npmjs.org', timeoutInDays = 60, } = config.pjson.oclif['warn-if-update-available'] ?? {}; | ||
const refreshNeeded = async () => { | ||
if (this.config.scopedEnvVarTrue('FORCE_VERSION_CACHE_UPDATE')) | ||
return true; | ||
if (this.config.scopedEnvVarTrue('SKIP_NEW_VERSION_CHECK')) | ||
return false; | ||
try { | ||
const { mtime } = await (0, promises_1.stat)(file); | ||
const staleAt = new Date(mtime.valueOf() + (1000 * 60 * 60 * 24 * timeoutInDays)); | ||
const { mtime } = await fsStat(file); | ||
const staleAt = new Date(mtime.valueOf() + 1000 * 60 * 60 * 24 * timeoutInDays); | ||
return staleAt < new Date(); | ||
} | ||
catch (error) { | ||
const debug = require('debug')('update-check'); | ||
debug(error); | ||
@@ -49,5 +108,6 @@ return true; | ||
const spawnRefresh = async () => { | ||
const debug = require('debug')('update-check'); | ||
const versionScript = resolve(dirname(fileURLToPath(import.meta.url)), '../../../lib/get-version'); | ||
debug('spawning version refresh'); | ||
(0, child_process_1.spawn)(process.execPath, [(0, path_1.join)(__dirname, '../../../lib/get-version'), config.name, file, config.version, registry, authorization], { | ||
debug(process.execPath, versionScript, config.name, file, config.version, registry, authorization); | ||
spawn(process.execPath, [versionScript, config.name, file, config.version, registry, authorization], { | ||
detached: !config.windows, | ||
@@ -57,6 +117,27 @@ stdio: 'ignore', | ||
}; | ||
await checkVersion(); | ||
try { | ||
const newerVersion = await getNewerVersion({ argv: process.argv, config, file }); | ||
if (newerVersion) { | ||
// Default message if the user doesn't provide one | ||
const [template] = await Promise.all([ | ||
import('lodash.template'), | ||
// Update the modified time (mtime) of the version file so that we can track the last time we | ||
// showed the warning. This makes it possible to respect the frequency and frequencyUnit options. | ||
utimes(file, new Date(), new Date()), | ||
]); | ||
this.warn(template.default(message)({ | ||
chalk, | ||
config, | ||
latest: newerVersion, | ||
})); | ||
} | ||
} | ||
catch (error) { | ||
const { code } = error; | ||
if (code !== 'ENOENT') | ||
throw error; | ||
} | ||
if (await refreshNeeded()) | ||
await spawnRefresh(); | ||
}; | ||
exports.default = hook; | ||
export default hook; |
@@ -1,3 +0,1 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = {}; | ||
export default {}; |
{ | ||
"version": "2.1.1", | ||
"commands": {} | ||
"commands": {}, | ||
"version": "2.1.2-qa.0" | ||
} |
{ | ||
"name": "@oclif/plugin-warn-if-update-available", | ||
"description": "warns if there is a newer version of CLI released", | ||
"version": "2.1.1", | ||
"version": "2.1.2-qa.0", | ||
"author": "Salesforce", | ||
"bugs": "https://github.com/oclif/plugin-warn-if-update-available/issues", | ||
"dependencies": { | ||
"@oclif/core": "^2.15.0", | ||
"chalk": "^4.1.0", | ||
"@oclif/core": "^3.3.1", | ||
"chalk": "^5.3.0", | ||
"debug": "^4.1.0", | ||
"http-call": "^5.2.2", | ||
"lodash.template": "^4.5.0", | ||
"semver": "^7.5.4" | ||
"lodash.template": "^4.5.0" | ||
}, | ||
"devDependencies": { | ||
"@oclif/test": "^1.2.8", | ||
"@types/chai": "^4.3.6", | ||
"@commitlint/config-conventional": "^17.7.0", | ||
"@oclif/prettier-config": "^0.2.1", | ||
"@oclif/test": "^3.0.2", | ||
"@types/chai": "^4.3.8", | ||
"@types/debug": "^4.1.9", | ||
"@types/lodash.template": "^4.5.1", | ||
"@types/mocha": "^8.0.0", | ||
"@types/node": "^14.18.62", | ||
"@types/semver": "^7.5.2", | ||
"chai": "^4.3.8", | ||
"eslint": "^7.3.1", | ||
"eslint-config-oclif": "^3.1.0", | ||
"eslint-config-oclif-typescript": "^0.2.0", | ||
"@types/mocha": "^10.0.2", | ||
"@types/node": "^18", | ||
"@types/semver": "^7.5.3", | ||
"chai": "^4.3.10", | ||
"commitlint": "^17.7.2", | ||
"eslint": "^8.51.0", | ||
"eslint-config-oclif": "^5.0.0", | ||
"eslint-config-oclif-typescript": "^3.0.5", | ||
"eslint-config-prettier": "^9.0.0", | ||
"globby": "^11.0.1", | ||
"mocha": "^8.0.1", | ||
"oclif": "^3.17.1", | ||
"ts-node": "^9.1.1", | ||
"tslib": "^2.6.2", | ||
"typescript": "4.4.3" | ||
"husky": "^8.0.3", | ||
"lint-staged": "^14.0.1", | ||
"mocha": "^10.2.0", | ||
"oclif": "^4.0.2", | ||
"prettier": "^3.0.3", | ||
"sinon": "^16.1.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.2.2" | ||
}, | ||
"engines": { | ||
"node": ">=12.0.0" | ||
"node": ">=18.0.0" | ||
}, | ||
"exports": "./lib/index.js", | ||
"files": [ | ||
"oclif.manifest.json", | ||
"/lib" | ||
"/lib", | ||
"/oclif.lock" | ||
], | ||
@@ -51,3 +60,4 @@ "homepage": "https://github.com/oclif/plugin-warn-if-update-available", | ||
] | ||
} | ||
}, | ||
"topicSeparator": " " | ||
}, | ||
@@ -57,13 +67,14 @@ "repository": "oclif/plugin-warn-if-update-available", | ||
"build": "rm -rf lib && tsc", | ||
"clean": "rm -f oclif.manifest.json", | ||
"lint": "eslint . --ext .ts --config .eslintrc", | ||
"clean": "rm -f oclif.manifest.json oclif.lock", | ||
"lint": "eslint . --ext .ts", | ||
"postpublish": "yarn run clean", | ||
"posttest": "yarn lint", | ||
"prepare": "husky install", | ||
"prepublishOnly": "yarn run build && oclif lock && oclif manifest . && oclif readme", | ||
"pretest": "yarn build --noEmit && tsc -p test --noEmit", | ||
"preversion": "yarn run clean", | ||
"test": "mocha --forbid-only \"test/**/*.test.ts\"", | ||
"posttest": "yarn lint", | ||
"prepublishOnly": "yarn run build && oclif manifest . && oclif readme", | ||
"postpublish": "yarn run clean", | ||
"preversion": "yarn run clean", | ||
"version": "oclif readme && git add README.md" | ||
}, | ||
"main": "lib/index.js" | ||
} | ||
"type": "module" | ||
} |
@@ -1,3 +0,2 @@ | ||
@oclif/plugin-warn-if-update-available | ||
====================================== | ||
# @oclif/plugin-warn-if-update-available | ||
@@ -7,4 +6,2 @@ warns if there is a newer version of CLI released | ||
[![Version](https://img.shields.io/npm/v/@oclif/plugin-warn-if-update-available.svg)](https://npmjs.org/package/@oclif/plugin-warn-if-update-available) | ||
[![CircleCI](https://circleci.com/gh/oclif/plugin-warn-if-update-available/tree/main.svg?style=shield)](https://circleci.com/gh/oclif/plugin-warn-if-update-available/tree/main) | ||
[![Appveyor CI](https://ci.appveyor.com/api/projects/status/github/oclif/plugin-warn-if-update-available?branch=main&svg=true)](https://ci.appveyor.com/project/oclif/plugin-warn-if-update-available/branch/main) | ||
[![Downloads/week](https://img.shields.io/npm/dw/@oclif/plugin-warn-if-update-available.svg)](https://npmjs.org/package/@oclif/plugin-warn-if-update-available) | ||
@@ -14,2 +11,3 @@ [![License](https://img.shields.io/npm/l/@oclif/plugin-warn-if-update-available.svg)](https://github.com/oclif/plugin-warn-if-update-available/blob/main/package.json) | ||
<!-- toc --> | ||
* [@oclif/plugin-warn-if-update-available](#oclifplugin-warn-if-update-available) | ||
* [What is this?](#what-is-this) | ||
@@ -19,2 +17,3 @@ * [How it works](#how-it-works) | ||
* [Configuration](#configuration) | ||
* [Environment Variables](#environment-variables) | ||
<!-- tocstop --> | ||
@@ -30,3 +29,3 @@ | ||
This checks the version against the npm registry asynchronously in a forked process, at most once per 7 days. It then saves a version file to the cache directory that will enable the warning. The upside of this method is that it won't block a user while they're using your CLI—the downside is that it will only display _after_ running a command that fetches the new version. | ||
This checks the version against the npm registry asynchronously in a forked process once every 60 days by default (see [Configuration](#configuration) for how to configure this). It then saves a version file to the cache directory that will enable the warning. The upside of this method is that it won't block a user while they're using your CLI—the downside is that it will only display _after_ running a command that fetches the new version. | ||
@@ -57,2 +56,4 @@ # Installation | ||
- `authorization` - Authorization header value for registries that require auth. | ||
- `frequency` - The frequency that the new version warning should be shown. | ||
- `frequencyUnit` - The unit of time that should be used to calculate the frequency (`days`, `hours`, `minutes`, `seconds`, `milliseconds`). Defaults to `minutes`. | ||
@@ -64,5 +65,3 @@ ## Example configuration | ||
"oclif": { | ||
"plugins": [ | ||
"@oclif/plugin-warn-if-update-available" | ||
], | ||
"plugins": ["@oclif/plugin-warn-if-update-available"], | ||
"warn-if-update-available": { | ||
@@ -77,1 +76,65 @@ "timeoutInDays": 7, | ||
``` | ||
## Notification Frequency | ||
Once a new version has been found, the default behavior is to notify the user on every command execution. You can modify this by setting the `frequency` and `frequencyUnit` options. | ||
**Examples** | ||
Once every 10 minutes. | ||
```json | ||
{ | ||
"oclif": { | ||
"warn-if-update-available": { | ||
"frequency": 10 | ||
} | ||
} | ||
} | ||
``` | ||
Once every 6 hours. | ||
```json | ||
{ | ||
"oclif": { | ||
"warn-if-update-available": { | ||
"frequency": 6, | ||
"frequencyUnit": "hours" | ||
} | ||
} | ||
} | ||
``` | ||
Once a day. | ||
```json | ||
{ | ||
"oclif": { | ||
"warn-if-update-available": { | ||
"frequency": 1, | ||
"frequencyUnit": "days" | ||
} | ||
} | ||
} | ||
``` | ||
Once every 30 seconds. | ||
```json | ||
{ | ||
"oclif": { | ||
"warn-if-update-available": { | ||
"frequency": 30, | ||
"frequencyUnit": "seconds" | ||
} | ||
} | ||
} | ||
``` | ||
# Environment Variables | ||
- `<CLI>_SKIP_NEW_VERSION_CHECK`: Skip this version check | ||
- `<CLI>_FORCE_VERSION_CACHE_UPDATE`: Force the version cache to update | ||
- `<CLI>_NEW_VERSION_CHECK_FREQ`: environment variable override for `frequency` setting | ||
- `<CLI>_NEW_VERSION_CHECK_FREQ_UNIT`: environment variable override for `frequencyUnit` setting |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
300550
5
11
197
134
Yes
134
6
9
1
41
24
362172
1
+ Added@oclif/core@3.27.0(transitive)
+ Addedchalk@5.4.1(transitive)
+ Addedcolor@4.2.3(transitive)
+ Addedcolor-string@1.9.1(transitive)
+ Addedis-arrayish@0.3.2(transitive)
+ Addedminimatch@9.0.5(transitive)
+ Addedsimple-swizzle@0.2.2(transitive)
- Removedsemver@^7.5.4
- Removed@cspotcode/source-map-support@0.8.1(transitive)
- Removed@jridgewell/resolve-uri@3.1.2(transitive)
- Removed@jridgewell/sourcemap-codec@1.5.0(transitive)
- Removed@jridgewell/trace-mapping@0.3.9(transitive)
- Removed@oclif/core@2.16.0(transitive)
- Removed@tsconfig/node10@1.0.11(transitive)
- Removed@tsconfig/node12@1.0.11(transitive)
- Removed@tsconfig/node14@1.0.3(transitive)
- Removed@tsconfig/node16@1.0.4(transitive)
- Removedacorn@8.14.0(transitive)
- Removedacorn-walk@8.3.4(transitive)
- Removedarg@4.1.3(transitive)
- Removedcreate-require@1.1.1(transitive)
- Removeddiff@4.0.2(transitive)
- Removedmake-error@1.3.6(transitive)
- Removedsemver@7.6.3(transitive)
- Removedts-node@10.9.2(transitive)
- Removedtslib@2.8.1(transitive)
- Removedtypescript@5.7.2(transitive)
- Removedv8-compile-cache-lib@3.0.1(transitive)
- Removedyn@3.1.1(transitive)
Updated@oclif/core@^3.3.1
Updatedchalk@^5.3.0