Repository navigation
feat(cli): add internal update notifier package - #18502
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdded ChangesUpdate notifier migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant notifyUpdate
participant Cache
participant NpmRegistry
CLI->>notifyUpdate: pass package metadata
notifyUpdate->>Cache: read cached update
notifyUpdate->>CLI: display cached notification
notifyUpdate->>NpmRegistry: check latest version in background
NpmRegistry-->>Cache: store latest version
Suggested labels: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
7b360c9 to
cef3ebd
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@utils/update-notifier/README.md`:
- Line 7: Update the npm version badge img element in the README to include a
concise alt attribute describing the badge, while preserving its existing source
and display.
In `@utils/update-notifier/src/internal.js`:
- Around line 158-160: The missing-cache branch in the update-check flow should
seed the cache and launch the detached background update check before returning,
rather than returning immediately after writeCache. Update the surrounding
internal.js logic and add coverage for the missing cache file case, verifying
the initial check starts immediately.
- Around line 163-178: Move the writeCache call from the unconditional
latest-value block into the notification branch after the process.once('exit',
...) handler is registered. Keep the cache update limited to cases where
isNewerVersion(latest, version) passes and the notification is displayed,
preserving cache.latest otherwise.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8eefb991-ace4-43e0-9fb8-250908a6f6dc
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (13)
cli/bin/quasar.jscli/package.jsoncreate-quasar/bin/create-quasar.jscreate-quasar/package.jsonicongenie/bin/icongenie.jsicongenie/package.jsonpackage.jsonpnpm-workspace.yamlutils/update-notifier/README.mdutils/update-notifier/package.jsonutils/update-notifier/src/index.jsutils/update-notifier/src/internal.jsutils/update-notifier/test/index.test.js
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@utils/update-notifier/src/internal.js`:
- Around line 152-156: Update the child process creation in the spawn call to
attach an error listener before invoking unref(), ensuring startup failures are
handled without crashing the CLI. Preserve the existing detached, ignored-stdio
behavior and command arguments.
- Around line 152-156: Update the spawn options in the background update-check
invocation to include windowsHide: true alongside detached and stdio, ensuring
the detached Node process remains invisible on Windows.
- Around line 187-189: Update the cache handling used by checkForUpdate and
getCacheFile so cached update results are partitioned by npm_config_registry.
Include the registry identity in the cache key or store and validate it with the
cached package data, ignoring stale cache entries when the active registry
changes while preserving existing behavior for the same registry.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: efe9b005-09f1-4e0d-a1f0-a8926a369021
📒 Files selected for processing (3)
utils/update-notifier/README.mdutils/update-notifier/src/internal.jsutils/update-notifier/test/index.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
- utils/update-notifier/README.md
| spawn( | ||
| process.execPath, | ||
| [fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version], | ||
| { detached: true, stdio: 'ignore' } | ||
| ).unref() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '151,165p' utils/update-notifier/src/internal.jsRepository: quasarframework/quasar
Length of output: 584
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant imports and functions:"
sed -n '1,80p' utils/update-notifier/src/internal.js
sed -n '140,210p' utils/update-notifier/src/internal.js
echo
echo "Search for startBackgroundCheck callers/registrations:"
rg -n "startBackgroundCheck|notifyUpdate|update_notifier|update-notifier" -S .
echo
echo "Node ChildProcess spawn error behavior probe:"
node - <<'JS'
const { spawn } = require('node:child_process')
const events = []
try {
const child = spawn('this-binary-must-not-exist', ['--arg'], { detached: true, stdio: 'ignore' })
child.once('error', (err) => events.push({ event: 'error', name: err.name, code: err.code }))
child.unref()
setTimeout(() => {
console.log(JSON.stringify(events))
}, 100)
} catch (error) {
console.log(JSON.stringify({ event: 'throw', name: error.name, code: error.code }))
}
JSRepository: quasarframework/quasar
Length of output: 7771
Handle spawned child-process errors.
spawn() emits error if the child cannot start. In this path, callers can exit before attaching the needed listener, so an update-check spawn failure can crash the CLI. Add a child-process error listener before unref().
Proposed fix
function startBackgroundCheck(cacheFile, name, version) {
- spawn(
+ const child = spawn(
process.execPath,
[fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version],
{ detached: true, stdio: 'ignore' }
- ).unref()
+ )
+ child.on('error', () => undefined)
+ child.unref()
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| spawn( | |
| process.execPath, | |
| [fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version], | |
| { detached: true, stdio: 'ignore' } | |
| ).unref() | |
| const child = spawn( | |
| process.execPath, | |
| [fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version], | |
| { detached: true, stdio: 'ignore' } | |
| ) | |
| child.on('error', () => undefined) | |
| child.unref() |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@utils/update-notifier/src/internal.js` around lines 152 - 156, Update the
child process creation in the spawn call to attach an error listener before
invoking unref(), ensuring startup failures are handled without crashing the
CLI. Preserve the existing detached, ignored-stdio behavior and command
arguments.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '151,157p' utils/update-notifier/src/internal.jsRepository: quasarframework/quasar
Length of output: 382
🌐 Web query:
Node.js child_process spawn detached Windows creates new console windowsHide true stdio ignore
💡 Result:
When using child_process.spawn on Windows with { detached: true }, Node.js by default creates a new console window for the child process [1][2]. To manage this behavior, consider the following technical details and workarounds: 1. The windowsHide Option The { windowsHide: true } option is intended to hide the subprocess console window [3][4]. However, in some scenarios—particularly when the spawned process is a shell script (.bat or.cmd files) or when combined with certain detached configurations—this option may not effectively suppress the window [5][6]. 2. Handling Standard I/O If you use { detached: true }, the subprocess will not remain running in the background after the parent process exits unless you also configure its stdio correctly [1][7]. Setting { stdio: 'ignore' } is standard practice for detached processes to ensure they are not connected to the parent's terminal, preventing the child from inheriting the parent's console and potentially keeping it alive [1][7]. 3. Common Workarounds If { windowsHide: true } does not hide the window as expected, developers often use the following techniques: - Use Direct Executables: Instead of spawning a.bat or.cmd file (which inherently requires a shell/terminal window), spawn the underlying executable directly (e.g., node.exe for a.js file) [5][6]. - Use VBScript/Powershell: A common workaround to run batch files invisibly is to invoke them via a VBScript wrapper that uses WScript.Shell with a hidden window flag, or using PowerShell's Start-Process with the -WindowStyle Hidden argument [6]. - Unref: Remember to call subprocess.unref if you want the parent process to be able to exit independently of the child [8][3]. In summary, while { detached: true, stdio: 'ignore', windowsHide: true } is the documented approach for attempting to run a background process, the effectiveness of windowsHide can be inconsistent depending on the target command [5][6]. Moving away from shell scripts to direct binary execution is the most reliable way to avoid console window creation [5].
Citations:
- 1: https://nodejs.org/api/child_process.html
- 2: https://nodejs.org/docs/latest-v24.x/api/child_process.html
- 3: https://nodejs.org/download/release/latest-v24.x/docs/api/child_process.html
- 4: child_process: add windowsHide option nodejs/node#15380
- 5: child_process - windowsHide not working with detached: true nodejs/node#21825
- 6: https://stackoverflow.com/questions/51338738/nodejs-hide-window-from-spawned-bat-file-process-windows
- 7: https://github.com/nodejs/node/blob/master/doc/api/child_process.md
- 8: https://beta.docs.nodejs.org/child_process.html
Hide the Windows console for the background update check.
spawn() with { detached: true } can open a child console on Windows; add windowsHide: true to keep the background Node process invisible.
Proposed fix
- { detached: true, stdio: 'ignore' }
+ { detached: true, stdio: 'ignore', windowsHide: true }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| spawn( | |
| process.execPath, | |
| [fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version], | |
| { detached: true, stdio: 'ignore' } | |
| ).unref() | |
| spawn( | |
| process.execPath, | |
| [fileURLToPath(moduleUrl), backgroundCheckFlag, cacheFile, name, version], | |
| { detached: true, stdio: 'ignore', windowsHide: true } | |
| ).unref() |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@utils/update-notifier/src/internal.js` around lines 152 - 156, Update the
spawn options in the background update-check invocation to include windowsHide:
true alongside detached and stdio, ensuring the detached Node process remains
invisible on Windows.
| if (now - cache.checkedAt < checkInterval) return | ||
|
|
||
| startBackgroundCheck(cacheFile, name, version) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline utils/update-notifier/src/internal.js --items all --type function
rg -n -C 5 'getCacheFile|npm_config_registry|registry|checkedAt|latest' \
utils/update-notifier/src/internal.js \
utils/update-notifier/test/index.test.jsRepository: quasarframework/quasar
Length of output: 18862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- internal.js relevant lines ---'
cat -n utils/update-notifier/src/internal.js | sed -n '1,210p'
echo '--- package-specific tests and registry-related occurrences ---'
grep -RIn 'npm_config_registry|registry|dist-tags|latest|getCacheFile|cacheFile' utils/update-notifier -n
echo '--- deterministic cache path and interval behavior probe ---'
node - <<'JS'
import { readFileSync } from 'node:fs';
import vm from 'node:vm';
const code = readFileSync('utils/update-notifier/src/internal.js', 'utf8');
const sandbox = {
console,
process: {
argv: ['node', 'internal.js'],
once() {},
stdout: { isTTY: true },
env: { npm_config_registry: 'https://registry.npmjs.org' },
},
};
const moduleExports = {};
const fn = vm.compileFunction(code, ['exports'], { filename: 'internal.js' });
fn(moduleExports);
const { getCacheFile, notifyUpdate, startBackgroundCheck, checkForUpdate, isNewerVersion } = moduleExports;
console.log('getCacheFile quasar CLI:', getCacheFile('`@quasar/cli`'));
const base = '/tmp/cache';
const path1 = getCacheFile('`@quasar/cli`');
const base2 = getCacheFile('`@quasar/cli`');
console.log('same getCacheFile path for same package:', path1 === base2);
console.log('same getCacheFile path for different package:', path1 === getCacheFile('`@quasar/other-cli`'));
let requestedUrl, writtenCache, startTime;
vm.modifyGlobal = function() {};
function setupDetachedProbe() {
const src = code.replace('const backgroundCheckFlag =', 'console.error("started-background-check"); const backgroundCheckFlag =');
const moduleExports2 = {};
const fn2 = vm.compileFunction(src, ['exports'], { filename: 'internal.js' });
fn2(moduleExports2);
globals = moduleExports2;
return globals;
}
let requestedUrl2, writtenCache2;
function setupFetchAndSpawnMocks(g) {
global.fetch = async (url) => Object.assign({}, {
ok: true,
json: async () => ({ latest: '9.9.9' }),
}, { url: url.href });
g.spawn = (cmd, args, opts) => {
requestedUrl2 = url;
return {
on(event, cb) {},
unref() {},
};
};
g.writeFile = async (cacheFile, data) => { writtenCache2 = JSON.parse(data); };
g.readFile = async (cacheFile) => JSON.stringify({ checkedAt: Date.now(), latest: '1.1.1' });
g.join = (a,b) => `${a}/${b}`;
g.mkdir = async () => {};
}
const g = setupDetachedProbe();
setupFetchAndSpawnMocks(g);
console.log(g.checkForUpdate({ cacheFile: '/tmp/quasar.json', name: '`@quasar/cli`', version: '1.0.0' }).then(() => JSON.stringify({ requestedUrl2, requestedUrl2: requestedUrl2, writtenCache2, requestedUrl, writtenCache })));
JS
echo '--- no external dependencies in update-notifier js ---'
node - <<'JS'
const fs = require('fs');
const code = fs.readFileSync('utils/update-notifier/src/internal.js','utf8').split('\n').slice(0,22).join('\n');
console.log(code);
console.log('uses fetch:', code.includes('fetch'));
console.log('uses spawn:', code.includes('spawn'));
console.log('uses writeFile:', code.includes('writeFile'));
JSRepository: quasarframework/quasar
Length of output: 7182
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- package-specific tests and registry-related occurrences ---'
rg -n -C 3 '-registry|registry|dist-tags|npm_config_registry|cacheFile|getCacheFile|startBackgroundCheck|notifyUpdate|BackgroundCheckFlag|checkForUpdate' utils/update-notifier
echo '--- package-specific cache path behavior ---'
node - <<'JS'
const path = require('path');
const os = require('os');
function getCacheFile(packageName) {
const cacheDir =
process.env.XDG_CACHE_HOME ||
process.env.LOCALAPPDATA ||
path.join(os.homedir(), '.cache')
return path.join(cacheDir, 'quasar', 'update-notifier', `${encodeURIComponent(packageName)}.json`)
}
console.log('getCacheFile `@quasar/cli`:', getCacheFile('`@quasar/cli`'));
console.log('getCacheFile `@quasar/other`:', getCacheFile('`@quasar/other`'));
console.log('getCacheFile quasar/cli:', getCacheFile('quasar/cli'));
console.log('normalizedRegistry from URL pathname:', JSON.stringify([
{ input: 'https://registry.npmjs.org', pathname: new URL('https://registry.npmjs.org').pathname },
{ input: 'https://registry.npmjs.org/', pathname: new URL('https://registry.npmjs.org/').pathname },
]));
JS
echo '--- deterministic registry reuse scenario ---'
cat > /tmp/update-notifier-reg-scoped-probe.mjs <<'JS'
import { pathToFileURL } from 'node:url';
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const cacheRoot = join(tmpdir(), `quasar-update-notifier-registry-probe-${process.pid}`);
let requestedUrls = [];
let writtenCaches = [];
// Inline stable implementation from utils/update-notifier/src/internal.js without module dependencies.
function dirname(p) { return p.slice(0, Math.max(0, p.lastIndexOf('/'))) }
function getCacheFile(packageName) {
return join(cacheRoot, 'update-notifier', `${encodeURIComponent(packageName)}.json`)
}
function readCache(cacheFile) {
try {
return JSON.parse(readFileSync(cacheFile, 'utf8'))
} catch {
return void 0
}
}
function writeCache(cacheFile, value) {
mkdirSync(dirname(cacheFile), { recursive: true })
writeFileSync(cacheFile, JSON.stringify(value))
}
function parseVersion(version) {
if (typeof version !== 'string') return void 0
const match = version.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?(?:\+[\dA-Za-z-]+)?)$/i)
if (!match) return void 0
return {
core: match.slice(1, 4),
prerelease: match[4]?.split('.')
}
}
function compareNumeric(left, right) {
if (left.length !== right.length) return left.length > right.length
return left > right
}
function isNewerVersion(next, current) {
const installed = parseVersion(current)
const nextParsed = parseVersion(next)
if (!nextParsed || !installed) return false
for (let index = 0; index < 3; index++) {
if (nextParsed.core[index] !== installed.core[index]) return compareNumeric(nextParsed.core[index], installed.core[index])
}
if (!nextParsed.prerelease) return installed.prerelease !== void 0
if (!installed.prerelease) return false
const length = Math.max(nextParsed.prerelease.length, installed.prerelease.length)
for (let index = 0; index < length; index++) {
const nextId = nextParsed.prerelease[index]
const installedId = installed.prerelease[index]
if (nextId === installedId) continue
if (nextId === void 0) return false
if (installedId === void 0) return true
const nextIsNumber = /^\d+$/.test(nextId)
const installedIsNumber = /^\d+$/.test(installedId)
if (nextIsNumber && installedIsNumber) return compareNumeric(nextId, installedId)
if (nextIsNumber !== installedIsNumber) return installedIsNumber
return nextId > installedId
}
return false
}
function fetchRegistry(urlOrTarget) {
requestedUrls.push(urlOrTarget)
return Promise.resolve({
ok: true,
json: async () => ({ latest: urlOrTarget === 'private-registry' ? '3.0.0' : '2.0.0' })
})
}
const pkg = '`@quasar/cli`'
const registryCacheFile = getCacheFile(pkg)
writeCache(registryCacheFile, { checkedAt: Date.now(), latest: '2.0.0' })
const previousRegistry = process.env.npm_config_registry
process.env.npm_config_registry = 'http://private.example/registry/'
function checkFromCacheSim(cacheFile, pkg, version) {
const cache = readCache(cacheFile)
if (cache && cache.checkedAt && Date.now() - cache.checkedAt < 24 * 60 * 60 * 1000) return { cache, backgroundCheckScheduled: false }
const cacheFileAfter = getCacheFile(pkg)
return { cache, backgroundCheckScheduled: true, cacheFileAfter }
}
function checkFromFetchSim(cacheFile, pkg, version) {
const registry = new URL(process.env.npm_config_registry || 'https://registry.npmjs.org')
if (!registry.pathname.endsWith('/')) registry.pathname += '/'
const packagePath = encodeURIComponent(pkg)
const url = new URL(`-/package/${packagePath}/dist-tags`, registry)
return fetchRegistry(registry.pathname + `-/package/${packagePath}/dist-tags`).then(async (response) => {
const { latest } = await response.json()
writeCache(cacheFile, {
checkedAt: Date.now(),
latest: isNewerVersion(latest, version) ? latest : void 0
})
})
}
const cache = readCache(registryCacheFile)
console.log(JSON.stringify({
cacheKey: registryCacheFile,
cachedLatestInitially: cache.latest,
selectedRegistryConfig: process.env.npm_config_registry,
selectedRegistryPathname: new URL(process.env.npm_config_registry).pathname,
cachedCheckedAtAgeWithinInterval: cache && cache.checkedAt && Date.now() - cache.checkedAt < 24 * 60 * 60 * 1000,
intervalTriggeredBackgroundCheck: false,
fetchWouldHaveQueriedPathname: new URL(process.env.npm_config_registry).pathname,
}))
if (previousRegistry === undefined) delete process.env.npm_config_registry
else process.env.npm_config_registry = previousRegistry
rmSync(cacheRoot, { recursive: true, force: true })
JS
echo '--- lint check presence (if available without repo code execution) ---'
if [ -f package.json ]; then jq '.scripts // {}' package.json; fiRepository: quasarframework/quasar
Length of output: 714
Partition update-notifier cache by registry.
getCacheFile() uses only package, while checkForUpdate() uses npm_config_registry. Switching registries can reuse another registry’s latest notification and delay the active registry check until checkedAt expires. Include the registry identity in the cache key, or persist/store it per package and ignore cache when it changes.
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@utils/update-notifier/src/internal.js` around lines 187 - 189, Update the
cache handling used by checkForUpdate and getCacheFile so cached update results
are partitioned by npm_config_registry. Include the registry identity in the
cache key or store and validate it with the cached package data, ignoring stale
cache entries when the active registry changes while preserving existing
behavior for the same registry.
What kind of change does this PR introduce?
Does this PR introduce a breaking change?
The PR fulfills these requirements:
If adding a new feature, the PR description includes:
Other information:
This addresses the dependency concern raised in discussion #18497 without introducing duplicated source or relying on a third-party replacement.
The new @quasar/update-notifier package follows the same model as Quasar internal packages such as @quasar/art and @quasar/ssl-certificate: it is independently publishable from the monorepo but identified as used internally by Quasar CLI(s). It is not intended to require a public community release announcement.
The package has no runtime dependencies and exposes one public function, notifyUpdate. @quasar/cli, create-quasar, and @quasar/icongenie all consume that single implementation. The notifier provides daily cached checks, detached registry requests that do not delay commands, one-shot terminal notifications, SemVer comparison, custom registry support, and opt-outs for CI, npm scripts, tests, and explicit environment or CLI flags.
Publishing order: @quasar/update-notifier@1.0.0 must be published before releasing updated CLI consumers. IconGenie remains outside the root pnpm workspace and therefore declares the published ^1.0.0 dependency directly.
This removes update-notifier and its transitive dependency chain from the workspace lockfile. There are no SSR, hydration, platform, accessibility, or security contract changes. No quasar.dev documentation is needed because this remains internal CLI behavior; the package README labels it accordingly.
Validation:
Summary by CodeRabbit
New Features
Documentation
Tests