From 22056e7c6b02b4c82afd8506bdcda648f37e8ff7 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 12 May 2025 15:31:10 -0500 Subject: [PATCH 1/7] feat: output update-notifier message to stdout --- src/hooks/npm-update-notifier.ts | 56 ++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index b5d40e81..8685b4a5 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -1,28 +1,70 @@ import type { Hook } from '@oclif/core'; import updateNotifier, { type UpdateInfo } from 'update-notifier'; import pkg from '../../package.json' with { type: 'json' }; +import { debugLogger } from '../service/log.svc.ts'; +import { ux } from '@oclif/core'; const updateNotifierHook: Hook.Init = async (options) => { + debugLogger('pkg.version', pkg.version); + + const distTag = getDistTag(pkg.version); + debugLogger('distTag', distTag); + + let updateCheckInterval = 1000 * 60 * 60 * 24; // Check once per day for latest versions + + // If we're not on the latest dist-tag, check for updates every time + if (distTag !== 'latest') { + updateCheckInterval = 0; // Check every time for non-latest versions + } + + debugLogger('updateCheckInterval', updateCheckInterval); + const notifier = updateNotifier({ pkg, - updateCheckInterval: 1000 * 60 * 60 * 24, // Check once per day + distTag, + updateCheckInterval, }); + debugLogger('updateNotifierHook', { notifier }); + if (notifier.update) { const notification = handleUpdate(notifier.update, pkg.version); + debugLogger('notification', notification); + if (notification) { - notifier.notify(notification); + console.log(ux.colorize('yellow', notification.message)) } } }; export default updateNotifierHook; +type DistTag = 'latest' | 'beta' | 'prev1' | 'alpha' | 'next'; + +function getDistTag(version: string): DistTag { + const isBeta = version.includes('-beta'); + const isAlpha = version.includes('-alpha'); + const isNext = version.includes('-next'); + + if (isBeta) { + return 'beta'; + } + + if (isAlpha) { + return 'alpha'; + } + + if (isNext) { + return 'next'; + } + + return 'latest'; +} + export function handleUpdate(update: UpdateInfo, currentVersion: string) { - const isPreV1 = currentVersion.startsWith('0.'); - const isBeta = currentVersion.includes('-beta') || update.latest.includes('-beta'); - const isAlpha = currentVersion.includes('-alpha') || update.latest.includes('-alpha'); - const isNext = currentVersion.includes('-next') || update.latest.includes('-next'); + const isPreV1 = currentVersion.startsWith('0.') || update.latest.startsWith('0.'); + const currentDistTag = getDistTag(currentVersion); + const updateDistTag = getDistTag(update.latest); let message = `Update available! v${currentVersion} → v${update.latest}`; @@ -35,7 +77,7 @@ export function handleUpdate(update: UpdateInfo, currentVersion: string) { * [1]https://semver.org/#spec-item-4 * [2]https://antfu.me/posts/epoch-semver#leading-zero-major-versioning */ - if (isPreV1 || isBeta || isAlpha || isNext || update.type === 'major') { + if (isPreV1 || currentDistTag !== 'latest' || updateDistTag !== 'latest' || update.type === 'major') { message += '\nThis update may contain breaking changes.'; } From 51b8994a2ed04091f2aa0fb9878af90ee212c5ca Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 12 May 2025 15:34:11 -0500 Subject: [PATCH 2/7] chore: add more specs for npm update hook --- src/hooks/npm-update-notifier.ts | 8 +-- test/hooks/npm-update-notifier.test.ts | 92 +++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index 8685b4a5..a11aae7a 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -1,8 +1,8 @@ import type { Hook } from '@oclif/core'; +import { ux } from '@oclif/core'; import updateNotifier, { type UpdateInfo } from 'update-notifier'; import pkg from '../../package.json' with { type: 'json' }; import { debugLogger } from '../service/log.svc.ts'; -import { ux } from '@oclif/core'; const updateNotifierHook: Hook.Init = async (options) => { debugLogger('pkg.version', pkg.version); @@ -22,7 +22,7 @@ const updateNotifierHook: Hook.Init = async (options) => { const notifier = updateNotifier({ pkg, distTag, - updateCheckInterval, + updateCheckInterval, }); debugLogger('updateNotifierHook', { notifier }); @@ -32,7 +32,7 @@ const updateNotifierHook: Hook.Init = async (options) => { debugLogger('notification', notification); if (notification) { - console.log(ux.colorize('yellow', notification.message)) + console.log(ux.colorize('yellow', notification.message)); } } }; @@ -41,7 +41,7 @@ export default updateNotifierHook; type DistTag = 'latest' | 'beta' | 'prev1' | 'alpha' | 'next'; -function getDistTag(version: string): DistTag { +export function getDistTag(version: string): DistTag { const isBeta = version.includes('-beta'); const isAlpha = version.includes('-alpha'); const isNext = version.includes('-next'); diff --git a/test/hooks/npm-update-notifier.test.ts b/test/hooks/npm-update-notifier.test.ts index 8cd93316..82acd0cf 100644 --- a/test/hooks/npm-update-notifier.test.ts +++ b/test/hooks/npm-update-notifier.test.ts @@ -1,6 +1,28 @@ import assert from 'node:assert'; import { describe, it } from 'node:test'; -import { handleUpdate } from '../../src/hooks/npm-update-notifier.ts'; +import { getDistTag, handleUpdate } from '../../src/hooks/npm-update-notifier.ts'; + +describe('getDistTag', () => { + it('should return beta for beta versions', () => { + assert.strictEqual(getDistTag('1.0.0-beta.1'), 'beta'); + assert.strictEqual(getDistTag('2.3.4-beta.42'), 'beta'); + }); + + it('should return alpha for alpha versions', () => { + assert.strictEqual(getDistTag('1.0.0-alpha.1'), 'alpha'); + assert.strictEqual(getDistTag('2.3.4-alpha.42'), 'alpha'); + }); + + it('should return next for next versions', () => { + assert.strictEqual(getDistTag('1.0.0-next.1'), 'next'); + assert.strictEqual(getDistTag('2.3.4-next.42'), 'next'); + }); + + it('should return latest for stable versions', () => { + assert.strictEqual(getDistTag('1.0.0'), 'latest'); + assert.strictEqual(getDistTag('2.3.4'), 'latest'); + }); +}); describe('handleUpdate', () => { describe('updates that may contain breaking changes', () => { @@ -88,6 +110,57 @@ describe('handleUpdate', () => { defer: false, }); }); + + it('should warn when updating from stable to beta', () => { + const result = handleUpdate( + { + type: 'minor', + latest: '1.4.0-beta.1', + current: '1.3.0', + name: '@herodevs/cli', + }, + '1.3.0', + ); + + assert.deepStrictEqual(result, { + message: 'Update available! v1.3.0 → v1.4.0-beta.1\nThis update may contain breaking changes.', + defer: false, + }); + }); + + it('should warn when updating from beta to stable', () => { + const result = handleUpdate( + { + type: 'minor', + latest: '1.4.0', + current: '1.4.0-beta.1', + name: '@herodevs/cli', + }, + '1.4.0-beta.1', + ); + + assert.deepStrictEqual(result, { + message: 'Update available! v1.4.0-beta.1 → v1.4.0\nThis update may contain breaking changes.', + defer: false, + }); + }); + + it('should warn about minor updates between beta versions', () => { + const result = handleUpdate( + { + type: 'minor', + latest: '1.4.0-beta.2', + current: '1.3.0-beta.1', + name: '@herodevs/cli', + }, + '1.3.0-beta.1', + ); + + assert.deepStrictEqual(result, { + message: 'Update available! v1.3.0-beta.1 → v1.4.0-beta.2\nThis update may contain breaking changes.', + defer: false, + }); + }); }); describe('updates that should not contain breaking changes', () => { @@ -124,5 +197,22 @@ describe('handleUpdate', () => { defer: false, }); }); + + it('should not warn about minor updates between stable versions', () => { + const result = handleUpdate( + { + type: 'minor', + latest: '1.4.0', + current: '1.3.0', + name: '@herodevs/cli', + }, + '1.3.0', + ); + + assert.deepStrictEqual(result, { + message: 'Update available! v1.3.0 → v1.4.0', + defer: false, + }); + }); }); }); From 48762ff829a20f94d266c58e8a403dff308f5d82 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 12 May 2025 15:39:55 -0500 Subject: [PATCH 3/7] chore: delete unnecessary comment --- src/hooks/npm-update-notifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index a11aae7a..fa64ee24 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -14,7 +14,7 @@ const updateNotifierHook: Hook.Init = async (options) => { // If we're not on the latest dist-tag, check for updates every time if (distTag !== 'latest') { - updateCheckInterval = 0; // Check every time for non-latest versions + updateCheckInterval = 0; } debugLogger('updateCheckInterval', updateCheckInterval); From 033aa0ca8ee4efd2b20316736cf155c292bc656a Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Mon, 12 May 2025 17:29:14 -0500 Subject: [PATCH 4/7] chore: set should notify in npm script to true Update src/hooks/npm-update-notifier.ts Co-authored-by: Marco Ippolito Update src/hooks/npm-update-notifier.ts Co-authored-by: Marco Ippolito Update src/hooks/npm-update-notifier.ts Co-authored-by: Marco Ippolito --- src/hooks/npm-update-notifier.ts | 34 +++++++++----------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index fa64ee24..f1fc81e4 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -4,18 +4,14 @@ import updateNotifier, { type UpdateInfo } from 'update-notifier'; import pkg from '../../package.json' with { type: 'json' }; import { debugLogger } from '../service/log.svc.ts'; -const updateNotifierHook: Hook.Init = async (options) => { +const updateNotifierHook: Hook.Init = (options) => { debugLogger('pkg.version', pkg.version); const distTag = getDistTag(pkg.version); debugLogger('distTag', distTag); - - let updateCheckInterval = 1000 * 60 * 60 * 24; // Check once per day for latest versions - - // If we're not on the latest dist-tag, check for updates every time - if (distTag !== 'latest') { - updateCheckInterval = 0; - } + const ONE_DAY_MS = 1000 * 60 * 60 * 24; + // If we're on the latest dist-tag, check for updates every time + const updateCheckInterval = distTag === 'latest' ? 0 : ONE_DAY_MS; debugLogger('updateCheckInterval', updateCheckInterval); @@ -23,6 +19,7 @@ const updateNotifierHook: Hook.Init = async (options) => { pkg, distTag, updateCheckInterval, + shouldNotifyInNpmScript: true, }); debugLogger('updateNotifierHook', { notifier }); @@ -32,7 +29,7 @@ const updateNotifierHook: Hook.Init = async (options) => { debugLogger('notification', notification); if (notification) { - console.log(ux.colorize('yellow', notification.message)); + notifier.notify(notification); } } }; @@ -42,22 +39,9 @@ export default updateNotifierHook; type DistTag = 'latest' | 'beta' | 'prev1' | 'alpha' | 'next'; export function getDistTag(version: string): DistTag { - const isBeta = version.includes('-beta'); - const isAlpha = version.includes('-alpha'); - const isNext = version.includes('-next'); - - if (isBeta) { - return 'beta'; - } - - if (isAlpha) { - return 'alpha'; - } - - if (isNext) { - return 'next'; - } - + if (version.includes('-beta')) return 'beta'; + if (version.includes('-alpha')) return 'alpha'; + if (version.includes('-next')) return 'next'; return 'latest'; } From e59a16ba576e5ffb7c171303609471596832762e Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 13 May 2025 10:46:24 -0500 Subject: [PATCH 5/7] chore: run linting --- src/hooks/npm-update-notifier.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index f1fc81e4..9cbe72b8 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -4,14 +4,14 @@ import updateNotifier, { type UpdateInfo } from 'update-notifier'; import pkg from '../../package.json' with { type: 'json' }; import { debugLogger } from '../service/log.svc.ts'; -const updateNotifierHook: Hook.Init = (options) => { +const updateNotifierHook: Hook.Init = async (options) => { debugLogger('pkg.version', pkg.version); const distTag = getDistTag(pkg.version); debugLogger('distTag', distTag); - const ONE_DAY_MS = 1000 * 60 * 60 * 24; - // If we're on the latest dist-tag, check for updates every time - const updateCheckInterval = distTag === 'latest' ? 0 : ONE_DAY_MS; + const ONE_DAY_MS = 1000 * 60 * 60 * 24; + // If we're on the latest dist-tag, check for updates every time + const updateCheckInterval = distTag === 'latest' ? 0 : ONE_DAY_MS; debugLogger('updateCheckInterval', updateCheckInterval); From 7e9f27577655efd050bf42b66b0ccf81648940d1 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 13 May 2025 10:47:37 -0500 Subject: [PATCH 6/7] chore: remove prev1 from dist tag type --- src/hooks/npm-update-notifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index 9cbe72b8..1504c1dc 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -36,7 +36,7 @@ const updateNotifierHook: Hook.Init = async (options) => { export default updateNotifierHook; -type DistTag = 'latest' | 'beta' | 'prev1' | 'alpha' | 'next'; +type DistTag = 'latest' | 'beta' | 'alpha' | 'next'; export function getDistTag(version: string): DistTag { if (version.includes('-beta')) return 'beta'; From c22b1150d8cdc120e85b668627e0eb279a395b27 Mon Sep 17 00:00:00 2001 From: Edward Ezekiel Date: Tue, 13 May 2025 10:48:04 -0500 Subject: [PATCH 7/7] chore: remove unused import --- src/hooks/npm-update-notifier.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/npm-update-notifier.ts b/src/hooks/npm-update-notifier.ts index 1504c1dc..a57cce08 100644 --- a/src/hooks/npm-update-notifier.ts +++ b/src/hooks/npm-update-notifier.ts @@ -1,5 +1,4 @@ import type { Hook } from '@oclif/core'; -import { ux } from '@oclif/core'; import updateNotifier, { type UpdateInfo } from 'update-notifier'; import pkg from '../../package.json' with { type: 'json' }; import { debugLogger } from '../service/log.svc.ts';