11import { postrun as deprecationsHook } from './deprecations.js'
22import { reportAnalyticsEvent } from '../analytics.js'
3- import { outputDebug } from '../output.js'
3+ import { outputDebug , outputWarn } from '../output.js'
4+ import { getOutputUpdateCLIReminder , runCLIUpgrade , versionToAutoUpgrade , warnIfUpgradeAvailable } from '../upgrade.js'
5+ import { inferPackageManagerForGlobalCLI } from '../is-global.js'
46import BaseCommand from '../base-command.js'
57import * as metadata from '../metadata.js'
6-
8+ import { runAtMinimumInterval } from '../../../private/node/conf-store.js'
9+ import { CLI_KIT_VERSION } from '../../common/version.js'
10+ import { isMajorVersionChange } from '../version.js'
711import { Command , Hook } from '@oclif/core'
812
913let postRunHookCompleted = false
@@ -26,6 +30,64 @@ export const hook: Hook.Postrun = async ({config, Command}) => {
2630 const command = Command . id . replace ( / : / g, ' ' )
2731 outputDebug ( `Completed command ${ command } ` )
2832 postRunHookCompleted = true
33+
34+ if ( ! Command . id ?. includes ( 'upgrade' ) && ! Command . id ?. startsWith ( 'notifications' ) ) {
35+ try {
36+ await autoUpgradeIfNeeded ( )
37+ // eslint-disable-next-line no-catch-all/no-catch-all
38+ } catch ( error ) {
39+ outputDebug ( `Auto-upgrade check failed: ${ error } ` )
40+ }
41+ }
42+ }
43+
44+ /**
45+ * Auto-upgrades the CLI after a command completes, if a newer version is available.
46+ * The entire flow is rate-limited to once per day unless forced via SHOPIFY_CLI_FORCE_AUTO_UPGRADE.
47+ *
48+ * @returns Resolves when the upgrade attempt (or fallback warning) is complete.
49+ */
50+ export async function autoUpgradeIfNeeded ( ) : Promise < void > {
51+ const newerVersion = versionToAutoUpgrade ( )
52+ if ( ! newerVersion ) {
53+ await warnIfUpgradeAvailable ( )
54+ return
55+ }
56+
57+ const forced = process . env . SHOPIFY_CLI_FORCE_AUTO_UPGRADE === '1'
58+
59+ // SHOPIFY_CLI_FORCE_AUTO_UPGRADE bypasses the daily rate limit so tests and intentional upgrades always run.
60+ if ( forced ) {
61+ await performAutoUpgrade ( newerVersion )
62+ } else {
63+ // Rate-limit the entire upgrade flow to once per day to avoid repeated attempts and major-version warnings.
64+ await runAtMinimumInterval ( 'auto-upgrade' , { days : 1 } , async ( ) => {
65+ await performAutoUpgrade ( newerVersion )
66+ } )
67+ }
68+ }
69+
70+ async function performAutoUpgrade ( newerVersion : string ) : Promise < void > {
71+ if ( isMajorVersionChange ( CLI_KIT_VERSION , newerVersion ) ) {
72+ return outputWarn ( getOutputUpdateCLIReminder ( newerVersion ) )
73+ }
74+
75+ try {
76+ await runCLIUpgrade ( )
77+ // eslint-disable-next-line no-catch-all/no-catch-all
78+ } catch ( error ) {
79+ const errorMessage = `Auto-upgrade failed: ${ error } `
80+ outputDebug ( errorMessage )
81+ outputWarn ( getOutputUpdateCLIReminder ( newerVersion ) )
82+ // Report to Observe as a handled error without showing anything extra to the user
83+ const { sendErrorToBugsnag} = await import ( '../error-handler.js' )
84+ const enrichedError = Object . assign ( new Error ( errorMessage ) , {
85+ packageManager : inferPackageManagerForGlobalCLI ( ) ,
86+ platform : process . platform ,
87+ cliVersion : CLI_KIT_VERSION ,
88+ } )
89+ await sendErrorToBugsnag ( enrichedError , 'expected_error' )
90+ }
2991}
3092
3193/**
0 commit comments