From 69a62a8a912548a4df60b2c633c0b37305511efe Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Fri, 13 Mar 2026 11:05:28 +0100 Subject: [PATCH 1/3] feat: track auto-upgrade metrics in Monorail (#22365) - Adds Monorail tracking for auto-upgrade events - Tracked metrics: triggered, package_manager, skipped_reason, success - Updated postrun hook to record metrics at each decision point - Added tests for all metric recording scenarios Co-Authored-By: Claude Sonnet 4.6 --- .changeset/auto-upgrade-metrics.md | 5 ++ .changeset/auto-upgrade-phase1.md | 6 ++ .../cli-kit/src/private/node/analytics.ts | 3 + .../src/public/node/hooks/postrun.test.ts | 78 +++++++++++++++++++ .../cli-kit/src/public/node/hooks/postrun.ts | 24 +++--- packages/cli-kit/src/public/node/metadata.ts | 5 +- packages/cli-kit/src/public/node/monorail.ts | 8 ++ packages/cli-kit/src/public/node/upgrade.ts | 2 +- packages/cli/src/cli/commands/upgrade.test.ts | 45 +++++++++-- packages/cli/src/cli/commands/upgrade.ts | 4 +- 10 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 .changeset/auto-upgrade-metrics.md create mode 100644 .changeset/auto-upgrade-phase1.md diff --git a/.changeset/auto-upgrade-metrics.md b/.changeset/auto-upgrade-metrics.md new file mode 100644 index 00000000000..0bbb69d6c21 --- /dev/null +++ b/.changeset/auto-upgrade-metrics.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli-kit': patch +--- + +Track auto-upgrade events (trigger rate, success/failure, package manager, skip reason) in Monorail. diff --git a/.changeset/auto-upgrade-phase1.md b/.changeset/auto-upgrade-phase1.md new file mode 100644 index 00000000000..dde17f0eea7 --- /dev/null +++ b/.changeset/auto-upgrade-phase1.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli-kit': minor +'@shopify/cli': minor +--- + +Port auto-upgrade POC to main (Phase 1): `shopify upgrade` now prompts to enable automatic upgrades and runs the upgrade. After opting in, the CLI auto-upgrades post-command when a newer version is available. Homebrew detection added to package manager inference. diff --git a/packages/cli-kit/src/private/node/analytics.ts b/packages/cli-kit/src/private/node/analytics.ts index 0306936343c..968e5a24fd7 100644 --- a/packages/cli-kit/src/private/node/analytics.ts +++ b/packages/cli-kit/src/private/node/analytics.ts @@ -1,4 +1,5 @@ import {getLastSeenAuthMethod} from './session.js' +import {getAutoUpgradeEnabled} from './conf-store.js' import {hashString} from '../../public/node/crypto.js' import {getPackageManager, packageManagerFromUserAgent} from '../../public/node/node-package-manager.js' import BaseCommand from '../../public/node/base-command.js' @@ -67,6 +68,7 @@ interface EnvironmentData { env_auth_method: string env_is_wsl: boolean env_build_repository: string + env_auto_upgrade_enabled: boolean | null } export async function getEnvironmentData(config: Interfaces.Config): Promise { @@ -92,6 +94,7 @@ export async function getEnvironmentData(config: Interfaces.Config): Promise { @@ -22,6 +24,22 @@ vi.mock('../version.js', async (importOriginal) => { } }) +vi.mock('../is-global.js', async (importOriginal) => { + const actual: any = await importOriginal() + return { + ...actual, + inferPackageManagerForGlobalCLI: vi.fn(), + } +}) + +vi.mock('../metadata.js', async (importOriginal) => { + const actual: any = await importOriginal() + return { + ...actual, + addPublicMetadata: vi.fn().mockResolvedValue(undefined), + } +}) + // Always execute the task so the rate limit doesn't interfere with tests vi.mock('../../../private/node/conf-store.js', async (importOriginal) => { const actual: any = await importOriginal() @@ -36,6 +54,7 @@ vi.mock('../../../private/node/conf-store.js', async (importOriginal) => { afterEach(() => { mockAndCaptureOutput().clear() + vi.mocked(addPublicMetadata).mockClear() }) describe('autoUpgradeIfNeeded', () => { @@ -93,4 +112,63 @@ describe('autoUpgradeIfNeeded', () => { expect(getOutputUpdateCLIReminder).toHaveBeenCalledWith('4.0.0', true) expect(outputMock.warn()).toMatch(installReminder) }) + + test('records triggered metric for minor/patch upgrade', async () => { + vi.mocked(versionToAutoUpgrade).mockReturnValue('3.100.0') + vi.mocked(isMajorVersionChange).mockReturnValue(false) + vi.mocked(inferPackageManagerForGlobalCLI).mockReturnValue('npm') + vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) + + await autoUpgradeIfNeeded() + + expect(addPublicMetadata).toHaveBeenCalledWith(expect.any(Function)) + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual( + expect.objectContaining({ + env_auto_upgrade_triggered: true, + env_auto_upgrade_package_manager: 'npm', + }), + ) + }) + + test('records triggered metric with major_version skipped_reason for major bump', async () => { + vi.mocked(versionToAutoUpgrade).mockReturnValue('4.0.0') + vi.mocked(isMajorVersionChange).mockReturnValue(true) + vi.mocked(getOutputUpdateCLIReminder).mockReturnValue('upgrade reminder') + + await autoUpgradeIfNeeded() + + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual( + expect.objectContaining({ + env_auto_upgrade_triggered: true, + env_auto_upgrade_skipped_reason: 'major_version', + }), + ) + }) + + test('records success=true on successful upgrade', async () => { + vi.mocked(versionToAutoUpgrade).mockReturnValue('3.100.0') + vi.mocked(isMajorVersionChange).mockReturnValue(false) + vi.mocked(inferPackageManagerForGlobalCLI).mockReturnValue('npm') + vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) + + await autoUpgradeIfNeeded() + + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual(expect.objectContaining({env_auto_upgrade_success: true})) + }) + + test('records success=false on failed upgrade', async () => { + vi.mocked(versionToAutoUpgrade).mockReturnValue('3.100.0') + vi.mocked(isMajorVersionChange).mockReturnValue(false) + vi.mocked(inferPackageManagerForGlobalCLI).mockReturnValue('npm') + vi.mocked(runCLIUpgrade).mockRejectedValue(new Error('upgrade failed')) + vi.mocked(getOutputUpdateCLIReminder).mockReturnValue('upgrade reminder') + + await autoUpgradeIfNeeded() + + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual(expect.objectContaining({env_auto_upgrade_success: false})) + }) }) diff --git a/packages/cli-kit/src/public/node/hooks/postrun.ts b/packages/cli-kit/src/public/node/hooks/postrun.ts index 922848a9df5..d6111de2bd5 100644 --- a/packages/cli-kit/src/public/node/hooks/postrun.ts +++ b/packages/cli-kit/src/public/node/hooks/postrun.ts @@ -31,14 +31,7 @@ export const hook: Hook.Postrun = async ({config, Command}) => { outputDebug(`Completed command ${command}`) postRunHookCompleted = true - if (!Command.id?.includes('upgrade') && !Command.id?.startsWith('notifications')) { - try { - await autoUpgradeIfNeeded() - // eslint-disable-next-line no-catch-all/no-catch-all - } catch (error) { - outputDebug(`Auto-upgrade check failed: ${error}`) - } - } + if (!command.includes('notifications') && !command.includes('upgrade')) await autoUpgradeIfNeeded() } /** @@ -69,16 +62,29 @@ export async function autoUpgradeIfNeeded(): Promise { async function performAutoUpgrade(newerVersion: string): Promise { if (isMajorVersionChange(CLI_KIT_VERSION, newerVersion)) { - return outputWarn(getOutputUpdateCLIReminder(newerVersion, true)) + outputWarn(getOutputUpdateCLIReminder(newerVersion, true)) + await metadata.addPublicMetadata(() => ({ + env_auto_upgrade_triggered: true, + env_auto_upgrade_skipped_reason: 'major_version', + })) + return } + const packageManager = inferPackageManagerForGlobalCLI() + await metadata.addPublicMetadata(() => ({ + env_auto_upgrade_triggered: true, + env_auto_upgrade_package_manager: packageManager, + })) + try { await runCLIUpgrade() + await metadata.addPublicMetadata(() => ({env_auto_upgrade_success: true})) // eslint-disable-next-line no-catch-all/no-catch-all } catch (error) { const errorMessage = `Auto-upgrade failed: ${error}` outputDebug(errorMessage) outputWarn(getOutputUpdateCLIReminder(newerVersion)) + await metadata.addPublicMetadata(() => ({env_auto_upgrade_success: false})) // Report to Observe as a handled error without showing anything extra to the user const {sendErrorToBugsnag} = await import('../error-handler.js') const enrichedError = Object.assign(new Error(errorMessage), { diff --git a/packages/cli-kit/src/public/node/metadata.ts b/packages/cli-kit/src/public/node/metadata.ts index ae77344aa77..1b6223f7b4b 100644 --- a/packages/cli-kit/src/public/node/metadata.ts +++ b/packages/cli-kit/src/public/node/metadata.ts @@ -176,12 +176,13 @@ export function createRuntimeMetadataContainer< } // We want to track anything that ends up getting sent to monorail as `cmd_all_*`, -// `cmd_app_*`, `cmd_theme_*`, and `store_*` +// `cmd_app_*`, `cmd_theme_*`, `store_*`, and `env_auto_upgrade_*` type CmdFieldsFromMonorail = PickByPrefix & PickByPrefix & PickByPrefix & PickByPrefix & - PickByPrefix + PickByPrefix & + PickByPrefix const coreData = createRuntimeMetadataContainer< CmdFieldsFromMonorail, diff --git a/packages/cli-kit/src/public/node/monorail.ts b/packages/cli-kit/src/public/node/monorail.ts index dc6130bc372..a909015917e 100644 --- a/packages/cli-kit/src/public/node/monorail.ts +++ b/packages/cli-kit/src/public/node/monorail.ts @@ -63,6 +63,14 @@ export interface Schemas { cmd_all_timing_prompts_ms?: Optional cmd_all_timing_active_ms?: Optional + // Auto-upgrade + env_auto_upgrade_enabled?: Optional + env_auto_upgrade_accepted?: Optional + env_auto_upgrade_triggered?: Optional + env_auto_upgrade_package_manager?: Optional + env_auto_upgrade_skipped_reason?: Optional + env_auto_upgrade_success?: Optional + // Any extension related command cmd_extensions_binary_from_source?: Optional diff --git a/packages/cli-kit/src/public/node/upgrade.ts b/packages/cli-kit/src/public/node/upgrade.ts index cd5031550f8..482d62ceff8 100644 --- a/packages/cli-kit/src/public/node/upgrade.ts +++ b/packages/cli-kit/src/public/node/upgrade.ts @@ -22,7 +22,7 @@ import {CLI_KIT_VERSION} from '../common/version.js' * Utility function for generating an install command for the user to run * to install an updated version of Shopify CLI. * - * @returns A string with the command to run. + * @returns A string with the command to run, or undefined if the package manager cannot be determined. */ export function cliInstallCommand(): string | undefined { const packageManager = inferPackageManagerForGlobalCLI() diff --git a/packages/cli/src/cli/commands/upgrade.test.ts b/packages/cli/src/cli/commands/upgrade.test.ts index e56a55ace44..f7a5a44d194 100644 --- a/packages/cli/src/cli/commands/upgrade.test.ts +++ b/packages/cli/src/cli/commands/upgrade.test.ts @@ -1,10 +1,45 @@ -import {describe, test, vi, expect} from 'vitest' +import Upgrade from './upgrade.js' +import {promptAutoUpgrade, runCLIUpgrade} from '@shopify/cli-kit/node/upgrade' +import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' +import {describe, test, vi, expect, afterEach} from 'vitest' -vi.mock('../services/upgrade.js') +vi.mock('@shopify/cli-kit/node/upgrade') +vi.mock('@shopify/cli-kit/node/metadata', () => ({ + addPublicMetadata: vi.fn().mockResolvedValue(undefined), +})) + +afterEach(() => { + vi.mocked(addPublicMetadata).mockClear() +}) describe('upgrade command', () => { - test('launches service with path', async () => { - // PENDING - expect(true).toBeTruthy() + test('calls promptAutoUpgrade and runCLIUpgrade', async () => { + vi.mocked(promptAutoUpgrade).mockResolvedValue(true) + vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) + + await Upgrade.run([], import.meta.url) + + expect(promptAutoUpgrade).toHaveBeenCalledOnce() + expect(runCLIUpgrade).toHaveBeenCalledOnce() + }) + + test('records env_auto_upgrade_accepted=true when user opts in', async () => { + vi.mocked(promptAutoUpgrade).mockResolvedValue(true) + vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) + + await Upgrade.run([], import.meta.url) + + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual(expect.objectContaining({env_auto_upgrade_accepted: true})) + }) + + test('records env_auto_upgrade_accepted=false when user opts out', async () => { + vi.mocked(promptAutoUpgrade).mockResolvedValue(false) + vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) + + await Upgrade.run([], import.meta.url) + + const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) + expect(calls).toContainEqual(expect.objectContaining({env_auto_upgrade_accepted: false})) }) }) diff --git a/packages/cli/src/cli/commands/upgrade.ts b/packages/cli/src/cli/commands/upgrade.ts index aeb5217ee2f..28af7f872fe 100644 --- a/packages/cli/src/cli/commands/upgrade.ts +++ b/packages/cli/src/cli/commands/upgrade.ts @@ -1,5 +1,6 @@ import Command from '@shopify/cli-kit/node/base-command' import {promptAutoUpgrade, runCLIUpgrade} from '@shopify/cli-kit/node/upgrade' +import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' export default class Upgrade extends Command { static summary = 'Upgrades Shopify CLI.' @@ -9,7 +10,8 @@ export default class Upgrade extends Command { static description = this.descriptionWithoutMarkdown() async run(): Promise { - await promptAutoUpgrade() + const accepted = await promptAutoUpgrade() + await addPublicMetadata(() => ({env_auto_upgrade_accepted: accepted})) await runCLIUpgrade() } } From 9162684ac85a66b30f16e13be85cad71bba44f84 Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Mon, 13 Apr 2026 10:31:07 +0200 Subject: [PATCH 2/3] feat: align auto-upgrade metrics with Monorail schema v1.21 Aligns CLI metrics with the Monorail schema defined in Shopify/monorail#23360. - Remove env_auto_upgrade_triggered (not in schema) - Keep env_auto_upgrade_package_manager, env_auto_upgrade_skipped_reason, env_auto_upgrade_success (match schema fields) - env_auto_upgrade_enabled already sent via analytics layer - env_auto_upgrade_accepted to be wired when prompt flow lands Co-Authored-By: Claude Sonnet 4.6 --- packages/cli-kit/src/public/node/hooks/postrun.test.ts | 6 ++---- packages/cli-kit/src/public/node/hooks/postrun.ts | 2 -- packages/cli-kit/src/public/node/monorail.ts | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/cli-kit/src/public/node/hooks/postrun.test.ts b/packages/cli-kit/src/public/node/hooks/postrun.test.ts index a13ee16a2ba..414c6f46d18 100644 --- a/packages/cli-kit/src/public/node/hooks/postrun.test.ts +++ b/packages/cli-kit/src/public/node/hooks/postrun.test.ts @@ -113,7 +113,7 @@ describe('autoUpgradeIfNeeded', () => { expect(outputMock.warn()).toMatch(installReminder) }) - test('records triggered metric for minor/patch upgrade', async () => { + test('records package_manager metric for minor/patch upgrade', async () => { vi.mocked(versionToAutoUpgrade).mockReturnValue('3.100.0') vi.mocked(isMajorVersionChange).mockReturnValue(false) vi.mocked(inferPackageManagerForGlobalCLI).mockReturnValue('npm') @@ -125,13 +125,12 @@ describe('autoUpgradeIfNeeded', () => { const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) expect(calls).toContainEqual( expect.objectContaining({ - env_auto_upgrade_triggered: true, env_auto_upgrade_package_manager: 'npm', }), ) }) - test('records triggered metric with major_version skipped_reason for major bump', async () => { + test('records skipped_reason for major version bump', async () => { vi.mocked(versionToAutoUpgrade).mockReturnValue('4.0.0') vi.mocked(isMajorVersionChange).mockReturnValue(true) vi.mocked(getOutputUpdateCLIReminder).mockReturnValue('upgrade reminder') @@ -141,7 +140,6 @@ describe('autoUpgradeIfNeeded', () => { const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) expect(calls).toContainEqual( expect.objectContaining({ - env_auto_upgrade_triggered: true, env_auto_upgrade_skipped_reason: 'major_version', }), ) diff --git a/packages/cli-kit/src/public/node/hooks/postrun.ts b/packages/cli-kit/src/public/node/hooks/postrun.ts index d6111de2bd5..24ec5831a5a 100644 --- a/packages/cli-kit/src/public/node/hooks/postrun.ts +++ b/packages/cli-kit/src/public/node/hooks/postrun.ts @@ -64,7 +64,6 @@ async function performAutoUpgrade(newerVersion: string): Promise { if (isMajorVersionChange(CLI_KIT_VERSION, newerVersion)) { outputWarn(getOutputUpdateCLIReminder(newerVersion, true)) await metadata.addPublicMetadata(() => ({ - env_auto_upgrade_triggered: true, env_auto_upgrade_skipped_reason: 'major_version', })) return @@ -72,7 +71,6 @@ async function performAutoUpgrade(newerVersion: string): Promise { const packageManager = inferPackageManagerForGlobalCLI() await metadata.addPublicMetadata(() => ({ - env_auto_upgrade_triggered: true, env_auto_upgrade_package_manager: packageManager, })) diff --git a/packages/cli-kit/src/public/node/monorail.ts b/packages/cli-kit/src/public/node/monorail.ts index a909015917e..dd662657836 100644 --- a/packages/cli-kit/src/public/node/monorail.ts +++ b/packages/cli-kit/src/public/node/monorail.ts @@ -66,7 +66,6 @@ export interface Schemas { // Auto-upgrade env_auto_upgrade_enabled?: Optional env_auto_upgrade_accepted?: Optional - env_auto_upgrade_triggered?: Optional env_auto_upgrade_package_manager?: Optional env_auto_upgrade_skipped_reason?: Optional env_auto_upgrade_success?: Optional From 8e81a4b19c7644220bdacf49ee4e936381e1f2b3 Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Mon, 13 Apr 2026 10:45:27 +0200 Subject: [PATCH 3/3] refactor: remove env_auto_upgrade_package_manager metric Package manager is already tracked via env_package_manager in the analytics layer. No need to duplicate it in auto-upgrade metrics. --- .changeset/auto-upgrade-metrics.md | 5 ----- .changeset/auto-upgrade-phase1.md | 6 ------ .../src/public/node/hooks/postrun.test.ts | 17 ----------------- .../cli-kit/src/public/node/hooks/postrun.ts | 5 ----- packages/cli-kit/src/public/node/monorail.ts | 3 +-- 5 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 .changeset/auto-upgrade-metrics.md delete mode 100644 .changeset/auto-upgrade-phase1.md diff --git a/.changeset/auto-upgrade-metrics.md b/.changeset/auto-upgrade-metrics.md deleted file mode 100644 index 0bbb69d6c21..00000000000 --- a/.changeset/auto-upgrade-metrics.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@shopify/cli-kit': patch ---- - -Track auto-upgrade events (trigger rate, success/failure, package manager, skip reason) in Monorail. diff --git a/.changeset/auto-upgrade-phase1.md b/.changeset/auto-upgrade-phase1.md deleted file mode 100644 index dde17f0eea7..00000000000 --- a/.changeset/auto-upgrade-phase1.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@shopify/cli-kit': minor -'@shopify/cli': minor ---- - -Port auto-upgrade POC to main (Phase 1): `shopify upgrade` now prompts to enable automatic upgrades and runs the upgrade. After opting in, the CLI auto-upgrades post-command when a newer version is available. Homebrew detection added to package manager inference. diff --git a/packages/cli-kit/src/public/node/hooks/postrun.test.ts b/packages/cli-kit/src/public/node/hooks/postrun.test.ts index 414c6f46d18..9de987c649f 100644 --- a/packages/cli-kit/src/public/node/hooks/postrun.test.ts +++ b/packages/cli-kit/src/public/node/hooks/postrun.test.ts @@ -113,23 +113,6 @@ describe('autoUpgradeIfNeeded', () => { expect(outputMock.warn()).toMatch(installReminder) }) - test('records package_manager metric for minor/patch upgrade', async () => { - vi.mocked(versionToAutoUpgrade).mockReturnValue('3.100.0') - vi.mocked(isMajorVersionChange).mockReturnValue(false) - vi.mocked(inferPackageManagerForGlobalCLI).mockReturnValue('npm') - vi.mocked(runCLIUpgrade).mockResolvedValue(undefined) - - await autoUpgradeIfNeeded() - - expect(addPublicMetadata).toHaveBeenCalledWith(expect.any(Function)) - const calls = vi.mocked(addPublicMetadata).mock.calls.map((call) => call[0]()) - expect(calls).toContainEqual( - expect.objectContaining({ - env_auto_upgrade_package_manager: 'npm', - }), - ) - }) - test('records skipped_reason for major version bump', async () => { vi.mocked(versionToAutoUpgrade).mockReturnValue('4.0.0') vi.mocked(isMajorVersionChange).mockReturnValue(true) diff --git a/packages/cli-kit/src/public/node/hooks/postrun.ts b/packages/cli-kit/src/public/node/hooks/postrun.ts index 24ec5831a5a..564938e7da1 100644 --- a/packages/cli-kit/src/public/node/hooks/postrun.ts +++ b/packages/cli-kit/src/public/node/hooks/postrun.ts @@ -69,11 +69,6 @@ async function performAutoUpgrade(newerVersion: string): Promise { return } - const packageManager = inferPackageManagerForGlobalCLI() - await metadata.addPublicMetadata(() => ({ - env_auto_upgrade_package_manager: packageManager, - })) - try { await runCLIUpgrade() await metadata.addPublicMetadata(() => ({env_auto_upgrade_success: true})) diff --git a/packages/cli-kit/src/public/node/monorail.ts b/packages/cli-kit/src/public/node/monorail.ts index dd662657836..3cbabc52275 100644 --- a/packages/cli-kit/src/public/node/monorail.ts +++ b/packages/cli-kit/src/public/node/monorail.ts @@ -10,7 +10,7 @@ const url = 'https://monorail-edge.shopifysvc.com/v1/produce' type Optional = T | null // This is the topic name of the main event we log to Monorail, the command tracker -export const MONORAIL_COMMAND_TOPIC = 'app_cli3_command/1.20' +export const MONORAIL_COMMAND_TOPIC = 'app_cli3_command/1.21' export interface Schemas { [MONORAIL_COMMAND_TOPIC]: { @@ -66,7 +66,6 @@ export interface Schemas { // Auto-upgrade env_auto_upgrade_enabled?: Optional env_auto_upgrade_accepted?: Optional - env_auto_upgrade_package_manager?: Optional env_auto_upgrade_skipped_reason?: Optional env_auto_upgrade_success?: Optional