Skip to content

Commit 0f571af

Browse files
gonzaloriestraalfonso-noriegacursoragent
committed
Auto-upgrade
Co-Authored-By: Alfonso Noriega <alfonso.noriega.meneses@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c9cb952 commit 0f571af

File tree

24 files changed

+720
-151
lines changed

24 files changed

+720
-151
lines changed

docs-shopify.dev/commands/upgrade.doc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'
33

44
const data: ReferenceEntityTemplateSchema = {
55
name: 'upgrade',
6-
description: `Shows details on how to upgrade Shopify CLI.`,
7-
overviewPreviewDescription: `Shows details on how to upgrade Shopify CLI.`,
6+
description: `Upgrades Shopify CLI using your package manager.`,
7+
overviewPreviewDescription: `Upgrades Shopify CLI.`,
88
type: 'command',
99
isVisualComponent: false,
1010
defaultExample: {

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7815,8 +7815,8 @@
78157815
},
78167816
{
78177817
"name": "upgrade",
7818-
"description": "Shows details on how to upgrade Shopify CLI.",
7819-
"overviewPreviewDescription": "Shows details on how to upgrade Shopify CLI.",
7818+
"description": "Upgrades Shopify CLI using your package manager.",
7819+
"overviewPreviewDescription": "Upgrades Shopify CLI.",
78207820
"type": "command",
78217821
"isVisualComponent": false,
78227822
"defaultExample": {

packages/app/src/cli/services/app/config/link.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ vi.mock('@shopify/cli-kit/node/ui')
3737
vi.mock('../../context/partner-account-info.js')
3838
vi.mock('../../context.js')
3939
vi.mock('../select-app.js')
40+
vi.mock('@shopify/cli-kit/node/is-global', () => ({
41+
currentProcessIsGlobal: () => false,
42+
}))
4043

4144
const DEFAULT_REMOTE_CONFIGURATION = {
4245
name: 'app1',

packages/app/src/cli/services/app/config/use.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ vi.mock('../../local-storage.js')
1919
vi.mock('../../../models/app/loader.js')
2020
vi.mock('@shopify/cli-kit/node/ui')
2121
vi.mock('../../context.js')
22+
vi.mock('@shopify/cli-kit/node/is-global', () => ({
23+
currentProcessIsGlobal: () => false,
24+
}))
2225

2326
describe('use', () => {
2427
test('clears currentConfiguration when reset is true', async () => {
@@ -31,6 +34,7 @@ describe('use', () => {
3134
developerPlatformClient: testDeveloperPlatformClient(),
3235
}
3336
writeFileSync(joinPath(tmp, 'package.json'), '{}')
37+
writeFileSync(joinPath(tmp, 'shopify.app.toml'), '')
3438

3539
// When
3640
await use(options)

packages/app/src/cli/services/generate.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ vi.mock('../prompts/generate/extension.js')
3838
vi.mock('../services/generate/extension.js')
3939
vi.mock('../services/context.js')
4040
vi.mock('./local-storage.js')
41+
vi.mock('@shopify/cli-kit/node/is-global', () => ({
42+
currentProcessIsGlobal: () => false,
43+
}))
4144

4245
afterEach(() => {
4346
mockAndCaptureOutput().clear()

packages/app/src/cli/services/init/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ async function init(options: InitOptions) {
124124
await appendFile(joinPath(templateScaffoldDir, '.npmrc'), `auto-install-peers=true\n`)
125125
break
126126
}
127+
case 'homebrew':
127128
case 'unknown':
128129
throw new UnknownPackageManagerError()
129130
}

packages/cli-kit/src/private/node/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const environmentVariables = {
4646
neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',
4747
skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',
4848
maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',
49+
noAutoUpgrade: 'SHOPIFY_CLI_NO_AUTO_UPGRADE',
4950
}
5051

5152
export const defaultThemeKitAccessDomain = 'theme-kit-access.shopifyapps.com'

packages/cli-kit/src/public/node/context/local.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,14 @@ export function opentelemetryDomain(env = process.env): string {
292292
return isSet(domain) ? domain : 'https://otlp-http-production-cli.shopifysvc.com'
293293
}
294294

295+
/**
296+
* Returns true if the CLIshould not automatically upgrade.
297+
*
298+
* @param env - The environment variables from the environment of the current process.
299+
* @returns True if the CLI should not automatically upgrade.
300+
*/
301+
export function noAutoUpgrade(env = process.env): boolean {
302+
return isTruthy(env[environmentVariables.noAutoUpgrade])
303+
}
304+
295305
export type CIMetadata = Metadata

packages/cli-kit/src/public/node/fs.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515

1616
import {temporaryDirectory, temporaryDirectoryTask} from 'tempy'
1717
import {sep, join} from 'pathe'
18-
import {findUp as internalFindUp} from 'find-up'
18+
import {findUp as internalFindUp, findUpSync as internalFindUpSync} from 'find-up'
1919
import {minimatch} from 'minimatch'
2020
import fastGlobLib from 'fast-glob'
2121
import {
@@ -650,6 +650,23 @@ export async function findPathUp(
650650
return got ? normalizePath(got) : undefined
651651
}
652652

653+
/**
654+
* Find a file by walking parent directories.
655+
*
656+
* @param matcher - A pattern or an array of patterns to match a file name.
657+
* @param options - Options for the search.
658+
* @returns The first path found that matches or `undefined` if none could be found.
659+
*/
660+
export function findPathUpSync(
661+
matcher: OverloadParameters<typeof internalFindUp>[0],
662+
options: OverloadParameters<typeof internalFindUp>[1],
663+
): ReturnType<typeof internalFindUpSync> {
664+
// findUp has odd typing
665+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
666+
const got = internalFindUpSync(matcher as any, options)
667+
return got ? normalizePath(got) : undefined
668+
}
669+
653670
export interface MatchGlobOptions {
654671
matchBase: boolean
655672
noglobstar: boolean
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {autoUpgradeIfNeeded} from './postrun.js'
2+
import {mockAndCaptureOutput} from '../testing/output.js'
3+
import {getOutputUpdateCLIReminder, runCLIUpgrade, versionToAutoUpgrade} from '../upgrade.js'
4+
import {isMajorVersionChange} from '../version.js'
5+
import {describe, expect, test, vi, afterEach} from 'vitest'
6+
7+
vi.mock('../upgrade.js', async (importOriginal) => {
8+
const actual: any = await importOriginal()
9+
return {
10+
...actual,
11+
runCLIUpgrade: vi.fn(),
12+
getOutputUpdateCLIReminder: vi.fn(),
13+
versionToAutoUpgrade: vi.fn(),
14+
}
15+
})
16+
17+
vi.mock('../version.js', async (importOriginal) => {
18+
const actual: any = await importOriginal()
19+
return {
20+
...actual,
21+
isMajorVersionChange: vi.fn(),
22+
}
23+
})
24+
25+
afterEach(() => {
26+
mockAndCaptureOutput().clear()
27+
})
28+
29+
describe('autoUpgradeIfNeeded', () => {
30+
test('runs the upgrade when versionToAutoUpgrade returns a version', async () => {
31+
// Given
32+
vi.mocked(versionToAutoUpgrade).mockReturnValue('3.91.0')
33+
vi.mocked(runCLIUpgrade).mockResolvedValue()
34+
35+
// When
36+
await autoUpgradeIfNeeded()
37+
38+
// Then
39+
expect(runCLIUpgrade).toHaveBeenCalled()
40+
})
41+
42+
test('falls back to warning when the upgrade fails', async () => {
43+
// Given
44+
const outputMock = mockAndCaptureOutput()
45+
vi.mocked(versionToAutoUpgrade).mockReturnValue('3.91.0')
46+
vi.mocked(runCLIUpgrade).mockRejectedValue(new Error('upgrade failed'))
47+
const installReminder = '💡 Version 3.91.0 available! Run `npm install @shopify/cli@latest`'
48+
vi.mocked(getOutputUpdateCLIReminder).mockReturnValue(installReminder)
49+
50+
// When
51+
await autoUpgradeIfNeeded()
52+
53+
// Then
54+
expect(outputMock.warn()).toMatch(installReminder)
55+
})
56+
57+
test('does nothing when versionToAutoUpgrade returns undefined', async () => {
58+
// Given
59+
vi.mocked(versionToAutoUpgrade).mockReturnValue(undefined)
60+
61+
// When
62+
await autoUpgradeIfNeeded()
63+
64+
// Then
65+
expect(runCLIUpgrade).not.toHaveBeenCalled()
66+
})
67+
68+
test('shows warning instead of upgrading for a major version change', async () => {
69+
// Given
70+
const outputMock = mockAndCaptureOutput()
71+
vi.mocked(versionToAutoUpgrade).mockReturnValue('4.0.0')
72+
vi.mocked(isMajorVersionChange).mockReturnValue(true)
73+
const installReminder = '💡 Version 4.0.0 available! Run `npm install @shopify/cli@latest`'
74+
vi.mocked(getOutputUpdateCLIReminder).mockReturnValue(installReminder)
75+
76+
// When
77+
await autoUpgradeIfNeeded()
78+
79+
// Then
80+
expect(runCLIUpgrade).not.toHaveBeenCalled()
81+
expect(outputMock.warn()).toMatch(installReminder)
82+
})
83+
})

0 commit comments

Comments
 (0)