From aae54c10c7d9458d25cb7b95c3174f41480a38ea Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Fri, 19 Dec 2025 12:15:33 +0100 Subject: [PATCH 1/6] [cli] Show info notice when a newer version is available --- .changeset/shaggy-bugs-wave.md | 5 ++ packages/cli/bin/run.js | 13 ++++- packages/cli/package.json | 1 + packages/cli/src/lib/update-check.ts | 73 ++++++++++++++++++++++++ packages/cli/src/types/update-check.d.ts | 22 +++++++ pnpm-lock.yaml | 37 +++++++++--- 6 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 .changeset/shaggy-bugs-wave.md create mode 100644 packages/cli/src/lib/update-check.ts create mode 100644 packages/cli/src/types/update-check.d.ts diff --git a/.changeset/shaggy-bugs-wave.md b/.changeset/shaggy-bugs-wave.md new file mode 100644 index 000000000..b9e59c2bf --- /dev/null +++ b/.changeset/shaggy-bugs-wave.md @@ -0,0 +1,5 @@ +--- +"@workflow/cli": patch +--- + +Show info notice when a newer version is available diff --git a/packages/cli/bin/run.js b/packages/cli/bin/run.js index 5115eb0e1..71e6d1d9d 100755 --- a/packages/cli/bin/run.js +++ b/packages/cli/bin/run.js @@ -1,5 +1,16 @@ #!/usr/bin/env node -import { execute } from '@oclif/core'; +import { execute, Config } from '@oclif/core'; +import { checkForUpdates } from '../dist/lib/update-check.js'; +// Load config to get the version +const config = await Config.load({ root: import.meta.url }); + +// Start update check in background (don't await yet) +const updateCheckPromise = checkForUpdates(config.version).catch(() => {}); + +// Execute the CLI command await execute({ type: 'esm', development: false, dir: import.meta.url }); + +// Wait for update check to complete and show warning if needed +await updateCheckPromise; diff --git a/packages/cli/package.json b/packages/cli/package.json index 05100e188..50ac2ca01 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,6 +42,7 @@ "dependencies": { "@oclif/core": "4.0.0", "@oclif/plugin-help": "6.2.31", + "update-check": "1.5.4", "@swc/core": "catalog:", "@workflow/builders": "workspace:*", "@workflow/swc-plugin": "workspace:*", diff --git a/packages/cli/src/lib/update-check.ts b/packages/cli/src/lib/update-check.ts new file mode 100644 index 000000000..88dc7bf10 --- /dev/null +++ b/packages/cli/src/lib/update-check.ts @@ -0,0 +1,73 @@ +import boxen from 'boxen'; +import chalk from 'chalk'; +import updateCheck from 'update-check'; + +const PACKAGES_TO_CHECK = ['workflow', '@workflow/cli'] as const; + +interface UpdateInfo { + packageName: string; + currentVersion: string; + latestVersion: string; +} + +/** + * Check if a newer version of the CLI is available on npm. + * This runs asynchronously and prints a warning if an update is available. + * Errors are silently ignored to avoid disrupting the CLI experience. + */ +export async function checkForUpdates(currentVersion: string): Promise { + try { + const updates: UpdateInfo[] = []; + + // Check both packages in parallel + const results = await Promise.allSettled( + PACKAGES_TO_CHECK.map(async (packageName) => { + const pkg = { name: packageName, version: currentVersion }; + const update = await updateCheck(pkg); + if (update) { + return { + packageName, + currentVersion, + latestVersion: update.latest, + }; + } + return null; + }) + ); + + for (const result of results) { + if (result.status === 'fulfilled' && result.value) { + updates.push(result.value); + } + } + + if (updates.length > 0) { + printUpdateWarning(updates[0]); + } + } catch { + // Silently ignore errors - don't disrupt the CLI experience + } +} + +function printUpdateWarning(update: UpdateInfo): void { + const updateCommand = + update.packageName === 'workflow' + ? 'npm install workflow@latest' + : 'npm install @workflow/cli@latest'; + + const message = [ + chalk.yellow( + `Update available: ${update.currentVersion} → ${chalk.green(update.latestVersion)}` + ), + '', + `Run ${chalk.cyan(updateCommand)} to update`, + ].join('\n'); + + const box = boxen(message, { + padding: 1, + borderColor: 'yellow', + textAlignment: 'center', + }); + + console.error(box); +} diff --git a/packages/cli/src/types/update-check.d.ts b/packages/cli/src/types/update-check.d.ts new file mode 100644 index 000000000..0576162b3 --- /dev/null +++ b/packages/cli/src/types/update-check.d.ts @@ -0,0 +1,22 @@ +declare module 'update-check' { + interface Package { + name: string; + version: string; + } + + interface UpdateResult { + latest: string; + } + + interface Options { + interval?: number; + distTag?: string; + } + + function updateCheck( + pkg: Package, + options?: Options + ): Promise; + + export = updateCheck; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0623f7ac1..d0304777a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -493,6 +493,9 @@ importers: tinyglobby: specifier: 0.2.14 version: 0.2.14 + update-check: + specifier: 1.5.4 + version: 1.5.4 xdg-app-paths: specifier: 5.1.0 version: 5.1.0 @@ -11542,6 +11545,13 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true + registry-auth-token@3.3.2: + resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + + registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + rehype-harden@1.1.5: resolution: {integrity: sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==} @@ -12800,6 +12810,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-check@1.5.4: + resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} + uqr@0.1.2: resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} @@ -20946,8 +20959,7 @@ snapshots: deep-eql@5.0.2: {} - deep-extend@0.6.0: - optional: true + deep-extend@0.6.0: {} deep-is@0.1.4: optional: true @@ -22437,8 +22449,7 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: - optional: true + ini@1.3.8: {} ini@4.1.1: {} @@ -25456,7 +25467,6 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - optional: true react-day-picker@9.11.1(react@19.2.0): dependencies: @@ -25764,6 +25774,15 @@ snapshots: regexp-tree@0.1.27: {} + registry-auth-token@3.3.2: + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + + registry-url@3.1.0: + dependencies: + rc: 1.2.8 + rehype-harden@1.1.5: {} rehype-katex@7.0.1: @@ -26442,8 +26461,7 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@2.0.1: - optional: true + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: optional: true @@ -27167,6 +27185,11 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-check@1.5.4: + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + uqr@0.1.2: {} uri-js@4.4.1: From 8e1dbead9a4ab2aad668b204c41ada16c08d00b2 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Fri, 19 Dec 2025 12:36:19 +0100 Subject: [PATCH 2/6] Better solution --- packages/cli/package.json | 4 +- packages/cli/src/lib/update-check.ts | 26 ++++- packages/cli/src/types/latest-version.d.ts | 12 +++ packages/cli/src/types/update-check.d.ts | 22 ----- pnpm-lock.yaml | 105 +++++++++++++++++---- 5 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 packages/cli/src/types/latest-version.d.ts delete mode 100644 packages/cli/src/types/update-check.d.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 50ac2ca01..ce70c9b67 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,12 +37,14 @@ }, "devDependencies": { "@types/node": "catalog:", + "@types/semver": "7.7.1", "@workflow/tsconfig": "workspace:*" }, "dependencies": { "@oclif/core": "4.0.0", "@oclif/plugin-help": "6.2.31", - "update-check": "1.5.4", + "latest-version": "9.0.0", + "semver": "7.7.3", "@swc/core": "catalog:", "@workflow/builders": "workspace:*", "@workflow/swc-plugin": "workspace:*", diff --git a/packages/cli/src/lib/update-check.ts b/packages/cli/src/lib/update-check.ts index 88dc7bf10..83337b437 100644 --- a/packages/cli/src/lib/update-check.ts +++ b/packages/cli/src/lib/update-check.ts @@ -1,6 +1,7 @@ import boxen from 'boxen'; import chalk from 'chalk'; -import updateCheck from 'update-check'; +import latestVersion from 'latest-version'; +import { lt, valid } from 'semver'; const PACKAGES_TO_CHECK = ['workflow', '@workflow/cli'] as const; @@ -14,6 +15,7 @@ interface UpdateInfo { * Check if a newer version of the CLI is available on npm. * This runs asynchronously and prints a warning if an update is available. * Errors are silently ignored to avoid disrupting the CLI experience. + * Works for both global and local installations. */ export async function checkForUpdates(currentVersion: string): Promise { try { @@ -22,13 +24,14 @@ export async function checkForUpdates(currentVersion: string): Promise { // Check both packages in parallel const results = await Promise.allSettled( PACKAGES_TO_CHECK.map(async (packageName) => { - const pkg = { name: packageName, version: currentVersion }; - const update = await updateCheck(pkg); - if (update) { + const latest = await latestVersion(packageName); + // Only report if current version is less than latest + // Use semver comparison to handle pre-release versions correctly + if (latest && isNewerVersion(currentVersion, latest)) { return { packageName, currentVersion, - latestVersion: update.latest, + latestVersion: latest, }; } return null; @@ -49,6 +52,19 @@ export async function checkForUpdates(currentVersion: string): Promise { } } +/** + * Check if the latest version is newer than the current version. + * Handles pre-release versions (e.g., 4.0.1-beta.33). + */ +function isNewerVersion(current: string, latest: string): boolean { + // If both are valid semver, use proper comparison + if (valid(current) && valid(latest)) { + return lt(current, latest); + } + // Fallback to string comparison if semver parsing fails + return current !== latest; +} + function printUpdateWarning(update: UpdateInfo): void { const updateCommand = update.packageName === 'workflow' diff --git a/packages/cli/src/types/latest-version.d.ts b/packages/cli/src/types/latest-version.d.ts new file mode 100644 index 000000000..c0970bfee --- /dev/null +++ b/packages/cli/src/types/latest-version.d.ts @@ -0,0 +1,12 @@ +declare module 'latest-version' { + interface Options { + version?: string; + } + + function latestVersion( + packageName: string, + options?: Options + ): Promise; + + export default latestVersion; +} diff --git a/packages/cli/src/types/update-check.d.ts b/packages/cli/src/types/update-check.d.ts deleted file mode 100644 index 0576162b3..000000000 --- a/packages/cli/src/types/update-check.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -declare module 'update-check' { - interface Package { - name: string; - version: string; - } - - interface UpdateResult { - latest: string; - } - - interface Options { - interval?: number; - distTag?: string; - } - - function updateCheck( - pkg: Package, - options?: Options - ): Promise; - - export = updateCheck; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0304777a..346e6e886 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -478,6 +478,9 @@ importers: find-up: specifier: 7.0.0 version: 7.0.0 + latest-version: + specifier: 9.0.0 + version: 9.0.0 mixpart: specifier: 0.0.4 version: 0.0.4 @@ -487,15 +490,15 @@ importers: ora: specifier: 8.2.0 version: 8.2.0 + semver: + specifier: 7.7.3 + version: 7.7.3 terminal-link: specifier: 5.0.0 version: 5.0.0 tinyglobby: specifier: 0.2.14 version: 0.2.14 - update-check: - specifier: 1.5.4 - version: 1.5.4 xdg-app-paths: specifier: 5.1.0 version: 5.1.0 @@ -506,6 +509,9 @@ importers: '@types/node': specifier: 'catalog:' version: 22.19.0 + '@types/semver': + specifier: 7.7.1 + version: 7.7.1 '@workflow/tsconfig': specifier: workspace:* version: link:../tsconfig @@ -4503,6 +4509,18 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + engines: {node: '>=12'} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -7559,6 +7577,9 @@ packages: confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -9010,6 +9031,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -9595,6 +9619,10 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + ky@1.14.1: + resolution: {integrity: sha512-hYje4L9JCmpEQBtudo+v52X5X8tgWXUYyPcxKSuxQNboqufecl9VMWjGiucAFH060AwPXHZuH+WB2rrqfkmafw==} + engines: {node: '>=18'} + lambda-local@2.2.0: resolution: {integrity: sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg==} engines: {node: '>=8'} @@ -9604,6 +9632,10 @@ packages: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} + latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + launch-editor@2.11.1: resolution: {integrity: sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==} @@ -10789,6 +10821,10 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@10.0.1: + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} + engines: {node: '>=18'} + package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} @@ -11273,6 +11309,9 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.4: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} @@ -11545,12 +11584,13 @@ packages: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} hasBin: true - registry-auth-token@3.3.2: - resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + registry-auth-token@5.1.0: + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + engines: {node: '>=14'} - registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} + registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} rehype-harden@1.1.5: resolution: {integrity: sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==} @@ -12810,9 +12850,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-check@1.5.4: - resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} - uqr@0.1.2: resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} @@ -16422,6 +16459,18 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@2.3.1': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + '@polka/url@1.0.0-next.29': {} '@poppinss/colors@4.1.5': @@ -20550,6 +20599,11 @@ snapshots: confbox@0.2.2: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + consola@3.4.2: {} content-disposition@1.0.1: {} @@ -22145,6 +22199,8 @@ snapshots: gopd@1.2.0: {} + graceful-fs@4.2.10: {} + graceful-fs@4.2.11: {} gray-matter@4.0.3: @@ -22732,6 +22788,8 @@ snapshots: kuler@2.0.0: {} + ky@1.14.1: {} + lambda-local@2.2.0: dependencies: commander: 10.0.1 @@ -22746,6 +22804,10 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 + latest-version@9.0.0: + dependencies: + package-json: 10.0.1 + launch-editor@2.11.1: dependencies: picocolors: 1.1.1 @@ -24848,6 +24910,13 @@ snapshots: package-json-from-dist@1.0.1: {} + package-json@10.0.1: + dependencies: + ky: 1.14.1 + registry-auth-token: 5.1.0 + registry-url: 6.0.1 + semver: 7.7.3 + package-manager-detector@0.2.11: dependencies: quansync: 0.2.10 @@ -25334,6 +25403,8 @@ snapshots: property-information@7.1.0: {} + proto-list@1.2.4: {} + protobufjs@7.5.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -25774,12 +25845,11 @@ snapshots: regexp-tree@0.1.27: {} - registry-auth-token@3.3.2: + registry-auth-token@5.1.0: dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 + '@pnpm/npm-conf': 2.3.1 - registry-url@3.1.0: + registry-url@6.0.1: dependencies: rc: 1.2.8 @@ -27185,11 +27255,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-check@1.5.4: - dependencies: - registry-auth-token: 3.3.2 - registry-url: 3.1.0 - uqr@0.1.2: {} uri-js@4.4.1: From 453f28529049f912df9d9a30dab77fe90dad342e Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Fri, 19 Dec 2025 12:44:16 +0100 Subject: [PATCH 3/6] Fix --- packages/cli/bin/run.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cli/bin/run.js b/packages/cli/bin/run.js index 71e6d1d9d..63bbd547c 100755 --- a/packages/cli/bin/run.js +++ b/packages/cli/bin/run.js @@ -1,13 +1,15 @@ #!/usr/bin/env node -import { execute, Config } from '@oclif/core'; +import { createRequire } from 'node:module'; +import { execute } from '@oclif/core'; import { checkForUpdates } from '../dist/lib/update-check.js'; -// Load config to get the version -const config = await Config.load({ root: import.meta.url }); +// Get version from the CLI package's own package.json +const require = createRequire(import.meta.url); +const { version } = require('../package.json'); // Start update check in background (don't await yet) -const updateCheckPromise = checkForUpdates(config.version).catch(() => {}); +const updateCheckPromise = checkForUpdates(version).catch(() => {}); // Execute the CLI command await execute({ type: 'esm', development: false, dir: import.meta.url }); From db60098d672fc82b201441900b07c254fc9e636c Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Fri, 19 Dec 2025 12:56:20 +0100 Subject: [PATCH 4/6] Try again --- packages/cli/bin/run.js | 13 --- packages/cli/package.json | 13 ++- packages/cli/src/lib/update-check.ts | 89 ------------------ packages/cli/src/types/latest-version.d.ts | 12 --- pnpm-lock.yaml | 104 ++++++++++++--------- 5 files changed, 67 insertions(+), 164 deletions(-) delete mode 100644 packages/cli/src/lib/update-check.ts delete mode 100644 packages/cli/src/types/latest-version.d.ts diff --git a/packages/cli/bin/run.js b/packages/cli/bin/run.js index 63bbd547c..5115eb0e1 100755 --- a/packages/cli/bin/run.js +++ b/packages/cli/bin/run.js @@ -1,18 +1,5 @@ #!/usr/bin/env node -import { createRequire } from 'node:module'; import { execute } from '@oclif/core'; -import { checkForUpdates } from '../dist/lib/update-check.js'; -// Get version from the CLI package's own package.json -const require = createRequire(import.meta.url); -const { version } = require('../package.json'); - -// Start update check in background (don't await yet) -const updateCheckPromise = checkForUpdates(version).catch(() => {}); - -// Execute the CLI command await execute({ type: 'esm', development: false, dir: import.meta.url }); - -// Wait for update check to complete and show warning if needed -await updateCheckPromise; diff --git a/packages/cli/package.json b/packages/cli/package.json index ce70c9b67..d9fca341a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -31,20 +31,23 @@ "dirname": "workflow", "commands": "./dist/commands", "plugins": [ - "@oclif/plugin-help" + "@oclif/plugin-help", + "@oclif/plugin-warn-if-update-available" ], - "topicSeparator": " " + "topicSeparator": " ", + "warn-if-update-available": { + "timeoutInDays": 7, + "message": "<%= config.name %> update available from <%= chalk.greenBright(config.version) %> to <%= chalk.greenBright(latest) %>. Run \"npm install <%= config.name %>@latest\" to update." + } }, "devDependencies": { "@types/node": "catalog:", - "@types/semver": "7.7.1", "@workflow/tsconfig": "workspace:*" }, "dependencies": { "@oclif/core": "4.0.0", "@oclif/plugin-help": "6.2.31", - "latest-version": "9.0.0", - "semver": "7.7.3", + "@oclif/plugin-warn-if-update-available": "3.1.53", "@swc/core": "catalog:", "@workflow/builders": "workspace:*", "@workflow/swc-plugin": "workspace:*", diff --git a/packages/cli/src/lib/update-check.ts b/packages/cli/src/lib/update-check.ts deleted file mode 100644 index 83337b437..000000000 --- a/packages/cli/src/lib/update-check.ts +++ /dev/null @@ -1,89 +0,0 @@ -import boxen from 'boxen'; -import chalk from 'chalk'; -import latestVersion from 'latest-version'; -import { lt, valid } from 'semver'; - -const PACKAGES_TO_CHECK = ['workflow', '@workflow/cli'] as const; - -interface UpdateInfo { - packageName: string; - currentVersion: string; - latestVersion: string; -} - -/** - * Check if a newer version of the CLI is available on npm. - * This runs asynchronously and prints a warning if an update is available. - * Errors are silently ignored to avoid disrupting the CLI experience. - * Works for both global and local installations. - */ -export async function checkForUpdates(currentVersion: string): Promise { - try { - const updates: UpdateInfo[] = []; - - // Check both packages in parallel - const results = await Promise.allSettled( - PACKAGES_TO_CHECK.map(async (packageName) => { - const latest = await latestVersion(packageName); - // Only report if current version is less than latest - // Use semver comparison to handle pre-release versions correctly - if (latest && isNewerVersion(currentVersion, latest)) { - return { - packageName, - currentVersion, - latestVersion: latest, - }; - } - return null; - }) - ); - - for (const result of results) { - if (result.status === 'fulfilled' && result.value) { - updates.push(result.value); - } - } - - if (updates.length > 0) { - printUpdateWarning(updates[0]); - } - } catch { - // Silently ignore errors - don't disrupt the CLI experience - } -} - -/** - * Check if the latest version is newer than the current version. - * Handles pre-release versions (e.g., 4.0.1-beta.33). - */ -function isNewerVersion(current: string, latest: string): boolean { - // If both are valid semver, use proper comparison - if (valid(current) && valid(latest)) { - return lt(current, latest); - } - // Fallback to string comparison if semver parsing fails - return current !== latest; -} - -function printUpdateWarning(update: UpdateInfo): void { - const updateCommand = - update.packageName === 'workflow' - ? 'npm install workflow@latest' - : 'npm install @workflow/cli@latest'; - - const message = [ - chalk.yellow( - `Update available: ${update.currentVersion} → ${chalk.green(update.latestVersion)}` - ), - '', - `Run ${chalk.cyan(updateCommand)} to update`, - ].join('\n'); - - const box = boxen(message, { - padding: 1, - borderColor: 'yellow', - textAlignment: 'center', - }); - - console.error(box); -} diff --git a/packages/cli/src/types/latest-version.d.ts b/packages/cli/src/types/latest-version.d.ts deleted file mode 100644 index c0970bfee..000000000 --- a/packages/cli/src/types/latest-version.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'latest-version' { - interface Options { - version?: string; - } - - function latestVersion( - packageName: string, - options?: Options - ): Promise; - - export default latestVersion; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 346e6e886..3d3ecbf78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -421,6 +421,9 @@ importers: '@oclif/plugin-help': specifier: 6.2.31 version: 6.2.31(typescript@5.9.3) + '@oclif/plugin-warn-if-update-available': + specifier: 3.1.53 + version: 3.1.53(typescript@5.9.3) '@swc/core': specifier: 'catalog:' version: 1.15.3 @@ -478,9 +481,6 @@ importers: find-up: specifier: 7.0.0 version: 7.0.0 - latest-version: - specifier: 9.0.0 - version: 9.0.0 mixpart: specifier: 0.0.4 version: 0.0.4 @@ -490,9 +490,6 @@ importers: ora: specifier: 8.2.0 version: 8.2.0 - semver: - specifier: 7.7.3 - version: 7.7.3 terminal-link: specifier: 5.0.0 version: 5.0.0 @@ -509,9 +506,6 @@ importers: '@types/node': specifier: 'catalog:' version: 22.19.0 - '@types/semver': - specifier: 7.7.1 - version: 7.7.1 '@workflow/tsconfig': specifier: workspace:* version: link:../tsconfig @@ -3479,6 +3473,10 @@ packages: resolution: {integrity: sha512-o4xR98DEFf+VqY+M9B3ZooTm2T/mlGvyBHwHcnsPJCEnvzHqEA9xUlCUK4jm7FBXHhkppziMgCC2snsueLoIpQ==} engines: {node: '>=18.0.0'} + '@oclif/plugin-warn-if-update-available@3.1.53': + resolution: {integrity: sha512-ALxKMNFFJQJV1Z2OMVTV+q7EbKHhnTAPcTgkgHeXCNdW5nFExoXuwusZLS4Zv2o83j9UoDx1R/CSX7QZVgEHTA==} + engines: {node: '>=18.0.0'} + '@octokit/app@16.1.2': resolution: {integrity: sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ==} engines: {node: '>= 20'} @@ -9160,6 +9158,10 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-call@5.3.0: + resolution: {integrity: sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==} + engines: {node: '>=8.0.0'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -9400,6 +9402,10 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + is-ssh@1.4.1: resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} @@ -9544,6 +9550,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -9619,10 +9628,6 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - ky@1.14.1: - resolution: {integrity: sha512-hYje4L9JCmpEQBtudo+v52X5X8tgWXUYyPcxKSuxQNboqufecl9VMWjGiucAFH060AwPXHZuH+WB2rrqfkmafw==} - engines: {node: '>=18'} - lambda-local@2.2.0: resolution: {integrity: sha512-bPcgpIXbHnVGfI/omZIlgucDqlf4LrsunwoKue5JdZeGybt8L6KyJz2Zu19ffuZwIwLj2NAI2ZyaqNT6/cetcg==} engines: {node: '>=8'} @@ -9632,10 +9637,6 @@ packages: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} - latest-version@9.0.0: - resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} - engines: {node: '>=18'} - launch-editor@2.11.1: resolution: {integrity: sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==} @@ -10821,10 +10822,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-json@10.0.1: - resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} - engines: {node: '>=18'} - package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} @@ -10848,6 +10845,10 @@ packages: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -11588,10 +11589,6 @@ packages: resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} engines: {node: '>=14'} - registry-url@6.0.1: - resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} - engines: {node: '>=12'} - rehype-harden@1.1.5: resolution: {integrity: sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==} @@ -15774,6 +15771,18 @@ snapshots: transitivePeerDependencies: - typescript + '@oclif/plugin-warn-if-update-available@3.1.53(typescript@5.9.3)': + dependencies: + '@oclif/core': 4.0.0(typescript@5.9.3) + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + http-call: 5.3.0 + lodash: 4.17.21 + registry-auth-token: 5.1.0 + transitivePeerDependencies: + - supports-color + - typescript + '@octokit/app@16.1.2': dependencies: '@octokit/auth-app': 8.1.2 @@ -21013,7 +21022,8 @@ snapshots: deep-eql@5.0.2: {} - deep-extend@0.6.0: {} + deep-extend@0.6.0: + optional: true deep-is@0.1.4: optional: true @@ -22420,6 +22430,17 @@ snapshots: http-cache-semantics@4.2.0: {} + http-call@5.3.0: + dependencies: + content-type: 1.0.5 + debug: 4.4.3(supports-color@8.1.1) + is-retry-allowed: 1.2.0 + is-stream: 2.0.1 + parse-json: 4.0.0 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - supports-color + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -22616,6 +22637,8 @@ snapshots: dependencies: '@types/estree': 1.0.8 + is-retry-allowed@1.2.0: {} + is-ssh@1.4.1: dependencies: protocols: 2.0.2 @@ -22729,6 +22752,8 @@ snapshots: json-buffer@3.0.1: optional: true + json-parse-better-errors@1.0.2: {} + json-parse-even-better-errors@2.3.1: {} json-schema-ref-resolver@3.0.0: @@ -22788,8 +22813,6 @@ snapshots: kuler@2.0.0: {} - ky@1.14.1: {} - lambda-local@2.2.0: dependencies: commander: 10.0.1 @@ -22804,10 +22827,6 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 - latest-version@9.0.0: - dependencies: - package-json: 10.0.1 - launch-editor@2.11.1: dependencies: picocolors: 1.1.1 @@ -24910,13 +24929,6 @@ snapshots: package-json-from-dist@1.0.1: {} - package-json@10.0.1: - dependencies: - ky: 1.14.1 - registry-auth-token: 5.1.0 - registry-url: 6.0.1 - semver: 7.7.3 - package-manager-detector@0.2.11: dependencies: quansync: 0.2.10 @@ -24945,6 +24957,11 @@ snapshots: parse-gitignore@2.0.0: {} + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -25538,6 +25555,7 @@ snapshots: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 + optional: true react-day-picker@9.11.1(react@19.2.0): dependencies: @@ -25849,10 +25867,6 @@ snapshots: dependencies: '@pnpm/npm-conf': 2.3.1 - registry-url@6.0.1: - dependencies: - rc: 1.2.8 - rehype-harden@1.1.5: {} rehype-katex@7.0.1: @@ -26531,7 +26545,8 @@ snapshots: strip-final-newline@4.0.0: {} - strip-json-comments@2.0.1: {} + strip-json-comments@2.0.1: + optional: true strip-json-comments@3.1.1: optional: true @@ -26864,7 +26879,6 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - optional: true turbo-darwin-64@2.5.4: optional: true From b9249ed08f47a8f41fa9460f59306c2f995d614f Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 21 Dec 2025 10:52:17 +0100 Subject: [PATCH 5/6] [cli] Only do interactive pagination when using the -i flag --- .changeset/violet-bats-crash.md | 5 +++++ packages/cli/src/commands/inspect.ts | 1 + packages/cli/src/lib/config/types.ts | 1 + packages/cli/src/lib/inspect/flags.ts | 8 +++++++ packages/cli/src/lib/inspect/output.ts | 4 ++++ packages/cli/src/lib/inspect/pagination.ts | 25 +++++++++++++++++----- 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 .changeset/violet-bats-crash.md diff --git a/.changeset/violet-bats-crash.md b/.changeset/violet-bats-crash.md new file mode 100644 index 000000000..40dfad6b9 --- /dev/null +++ b/.changeset/violet-bats-crash.md @@ -0,0 +1,5 @@ +--- +"@workflow/cli": patch +--- + +Add -i / --interactive flag for enabling pagination bindings, new default being off diff --git a/packages/cli/src/commands/inspect.ts b/packages/cli/src/commands/inspect.ts index 54d13ed64..159d053d9 100644 --- a/packages/cli/src/commands/inspect.ts +++ b/packages/cli/src/commands/inspect.ts @@ -239,6 +239,7 @@ function toInspectOptions(flags: any): InspectCLIOptions { workflowName: flags.workflowName, withData: flags.withData, backend: flags.backend, + interactive: flags.interactive, }; } diff --git a/packages/cli/src/lib/config/types.ts b/packages/cli/src/lib/config/types.ts index 2e818e738..83f7c0442 100644 --- a/packages/cli/src/lib/config/types.ts +++ b/packages/cli/src/lib/config/types.ts @@ -21,4 +21,5 @@ export type InspectCLIOptions = { withData?: boolean; backend?: string; disableRelativeDates?: boolean; + interactive?: boolean; }; diff --git a/packages/cli/src/lib/inspect/flags.ts b/packages/cli/src/lib/inspect/flags.ts index 0d84d28ac..1757c4fbf 100644 --- a/packages/cli/src/lib/inspect/flags.ts +++ b/packages/cli/src/lib/inspect/flags.ts @@ -127,4 +127,12 @@ export const cliFlags = { helpLabel: '--limit', helpValue: 'NUMBER', }), + interactive: Flags.boolean({ + description: 'Enable interactive pagination with keyboard controls', + required: false, + char: 'i', + default: false, + helpGroup: 'Output', + helpLabel: '-i, --interactive', + }), }; diff --git a/packages/cli/src/lib/inspect/output.ts b/packages/cli/src/lib/inspect/output.ts index e41540748..75700d15f 100644 --- a/packages/cli/src/lib/inspect/output.ts +++ b/packages/cli/src/lib/inspect/output.ts @@ -543,6 +543,7 @@ export const listRuns = async (world: World, opts: InspectCLIOptions = {}) => { await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { try { const runs = await world.runs.list({ @@ -680,6 +681,7 @@ export const listSteps = async ( await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { logger.debug(`Fetching steps for run ${runId}`); try { @@ -888,6 +890,7 @@ export const listEvents = async ( await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { logger.debug(`Fetching events for run ${filterId}`); try { @@ -957,6 +960,7 @@ export const listHooks = async (world: World, opts: InspectCLIOptions = {}) => { // Setup pagination with new mechanism await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { if (!runId) { logger.debug('Fetching all hooks'); diff --git a/packages/cli/src/lib/inspect/pagination.ts b/packages/cli/src/lib/inspect/pagination.ts index 3c2838a59..89c04299b 100644 --- a/packages/cli/src/lib/inspect/pagination.ts +++ b/packages/cli/src/lib/inspect/pagination.ts @@ -143,6 +143,11 @@ export interface ListPaginationOptions { * Optional callback for when fetching starts */ onFetchStart?: (pageIndex: number) => void; + + /** + * Enable interactive pagination with keyboard controls + */ + interactive?: boolean; } /** @@ -151,7 +156,11 @@ export interface ListPaginationOptions { export async function setupListPagination( options: ListPaginationOptions ): Promise { - const { initialCursor, fetchPage, displayPage, onFetchStart } = options; + const { initialCursor, fetchPage, displayPage, onFetchStart, interactive } = + options; + + // Interactive mode requires both TTY support and explicit --interactive flag + const enableInteractive = interactive && isInteractive(); // Pages stack - stores all fetched pages const pages: PageData[] = []; @@ -172,7 +181,7 @@ export async function setupListPagination( pageIndex = index; // Clear screen for subsequent pages in interactive mode - if (isInteractive()) { + if (enableInteractive) { console.clear(); } @@ -183,7 +192,13 @@ export async function setupListPagination( // Fetch and display first page const firstPage = await showPage(0, initialCursor); - if (!isInteractive()) { + // In non-interactive mode, show info if there are more pages + if (!enableInteractive) { + if (firstPage.hasMore) { + logger.info( + '\nMore results available. Use --interactive (-i) to paginate through results.' + ); + } return; } @@ -208,7 +223,7 @@ export async function setupListPagination( pageIndex = newPageIndex; const cachedPage = pages[newPageIndex]; - if (isInteractive()) { + if (enableInteractive) { console.clear(); } @@ -257,7 +272,7 @@ export async function setupListPagination( pageIndex--; const prevPage = pages[pageIndex]; - if (isInteractive()) { + if (enableInteractive) { console.clear(); } From 38c6fcd4eaf09ca01dc03bce77c15052272681b7 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sun, 21 Dec 2025 10:52:17 +0100 Subject: [PATCH 6/6] [cli] Only do interactive pagination when using the -i flag --- .changeset/violet-bats-crash.md | 5 +++++ packages/cli/src/commands/inspect.ts | 1 + packages/cli/src/lib/config/types.ts | 1 + packages/cli/src/lib/inspect/flags.ts | 8 +++++++ packages/cli/src/lib/inspect/output.ts | 4 ++++ packages/cli/src/lib/inspect/pagination.ts | 25 +++++++++++++++++----- 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 .changeset/violet-bats-crash.md diff --git a/.changeset/violet-bats-crash.md b/.changeset/violet-bats-crash.md new file mode 100644 index 000000000..40dfad6b9 --- /dev/null +++ b/.changeset/violet-bats-crash.md @@ -0,0 +1,5 @@ +--- +"@workflow/cli": patch +--- + +Add -i / --interactive flag for enabling pagination bindings, new default being off diff --git a/packages/cli/src/commands/inspect.ts b/packages/cli/src/commands/inspect.ts index 54d13ed64..159d053d9 100644 --- a/packages/cli/src/commands/inspect.ts +++ b/packages/cli/src/commands/inspect.ts @@ -239,6 +239,7 @@ function toInspectOptions(flags: any): InspectCLIOptions { workflowName: flags.workflowName, withData: flags.withData, backend: flags.backend, + interactive: flags.interactive, }; } diff --git a/packages/cli/src/lib/config/types.ts b/packages/cli/src/lib/config/types.ts index 2e818e738..83f7c0442 100644 --- a/packages/cli/src/lib/config/types.ts +++ b/packages/cli/src/lib/config/types.ts @@ -21,4 +21,5 @@ export type InspectCLIOptions = { withData?: boolean; backend?: string; disableRelativeDates?: boolean; + interactive?: boolean; }; diff --git a/packages/cli/src/lib/inspect/flags.ts b/packages/cli/src/lib/inspect/flags.ts index 0d84d28ac..1757c4fbf 100644 --- a/packages/cli/src/lib/inspect/flags.ts +++ b/packages/cli/src/lib/inspect/flags.ts @@ -127,4 +127,12 @@ export const cliFlags = { helpLabel: '--limit', helpValue: 'NUMBER', }), + interactive: Flags.boolean({ + description: 'Enable interactive pagination with keyboard controls', + required: false, + char: 'i', + default: false, + helpGroup: 'Output', + helpLabel: '-i, --interactive', + }), }; diff --git a/packages/cli/src/lib/inspect/output.ts b/packages/cli/src/lib/inspect/output.ts index e41540748..75700d15f 100644 --- a/packages/cli/src/lib/inspect/output.ts +++ b/packages/cli/src/lib/inspect/output.ts @@ -543,6 +543,7 @@ export const listRuns = async (world: World, opts: InspectCLIOptions = {}) => { await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { try { const runs = await world.runs.list({ @@ -680,6 +681,7 @@ export const listSteps = async ( await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { logger.debug(`Fetching steps for run ${runId}`); try { @@ -888,6 +890,7 @@ export const listEvents = async ( await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { logger.debug(`Fetching events for run ${filterId}`); try { @@ -957,6 +960,7 @@ export const listHooks = async (world: World, opts: InspectCLIOptions = {}) => { // Setup pagination with new mechanism await setupListPagination({ initialCursor: opts.cursor, + interactive: opts.interactive, fetchPage: async (cursor) => { if (!runId) { logger.debug('Fetching all hooks'); diff --git a/packages/cli/src/lib/inspect/pagination.ts b/packages/cli/src/lib/inspect/pagination.ts index 3c2838a59..89c04299b 100644 --- a/packages/cli/src/lib/inspect/pagination.ts +++ b/packages/cli/src/lib/inspect/pagination.ts @@ -143,6 +143,11 @@ export interface ListPaginationOptions { * Optional callback for when fetching starts */ onFetchStart?: (pageIndex: number) => void; + + /** + * Enable interactive pagination with keyboard controls + */ + interactive?: boolean; } /** @@ -151,7 +156,11 @@ export interface ListPaginationOptions { export async function setupListPagination( options: ListPaginationOptions ): Promise { - const { initialCursor, fetchPage, displayPage, onFetchStart } = options; + const { initialCursor, fetchPage, displayPage, onFetchStart, interactive } = + options; + + // Interactive mode requires both TTY support and explicit --interactive flag + const enableInteractive = interactive && isInteractive(); // Pages stack - stores all fetched pages const pages: PageData[] = []; @@ -172,7 +181,7 @@ export async function setupListPagination( pageIndex = index; // Clear screen for subsequent pages in interactive mode - if (isInteractive()) { + if (enableInteractive) { console.clear(); } @@ -183,7 +192,13 @@ export async function setupListPagination( // Fetch and display first page const firstPage = await showPage(0, initialCursor); - if (!isInteractive()) { + // In non-interactive mode, show info if there are more pages + if (!enableInteractive) { + if (firstPage.hasMore) { + logger.info( + '\nMore results available. Use --interactive (-i) to paginate through results.' + ); + } return; } @@ -208,7 +223,7 @@ export async function setupListPagination( pageIndex = newPageIndex; const cachedPage = pages[newPageIndex]; - if (isInteractive()) { + if (enableInteractive) { console.clear(); } @@ -257,7 +272,7 @@ export async function setupListPagination( pageIndex--; const prevPage = pages[pageIndex]; - if (isInteractive()) { + if (enableInteractive) { console.clear(); }