diff --git a/packages/cli-kit/src/public/node/context/local.test.ts b/packages/cli-kit/src/public/node/context/local.test.ts index abe71bb52b7..5cda8af8a85 100644 --- a/packages/cli-kit/src/public/node/context/local.test.ts +++ b/packages/cli-kit/src/public/node/context/local.test.ts @@ -3,6 +3,7 @@ import { hasGit, isDevelopment, isShopify, + _resetIsShopifyMemo, isTerminalInteractive, isUnitTest, analyticsDisabled, @@ -14,7 +15,7 @@ import { import {fileExists} from '../fs.js' import {exec} from '../system.js' -import {afterEach, expect, describe, vi, test} from 'vitest' +import {afterEach, beforeEach, expect, describe, vi, test} from 'vitest' vi.mock('../fs.js') vi.mock('../system.js') @@ -97,6 +98,10 @@ describe('isDevelopment', () => { }) describe('isShopify', () => { + beforeEach(() => { + _resetIsShopifyMemo() + }) + test('returns false when the SHOPIFY_RUN_AS_USER env. variable is truthy', async () => { // Given const env = {SHOPIFY_RUN_AS_USER: '1'} @@ -120,6 +125,21 @@ describe('isShopify', () => { // When await expect(isShopify()).resolves.toBe(true) }) + + test('memoizes the result when called with the same env', async () => { + // Given + vi.mocked(fileExists).mockResolvedValue(true) + + // When + const first = isShopify() + const second = isShopify() + + // Then + await expect(first).resolves.toBe(true) + await expect(second).resolves.toBe(true) + expect(first).toBe(second) + expect(fileExists).toHaveBeenCalledTimes(1) + }) }) describe('hasGit', () => { diff --git a/packages/cli-kit/src/public/node/context/local.ts b/packages/cli-kit/src/public/node/context/local.ts index ad816399db9..11f1c657a08 100644 --- a/packages/cli-kit/src/public/node/context/local.ts +++ b/packages/cli-kit/src/public/node/context/local.ts @@ -44,6 +44,11 @@ let memoizedIsVerbose: boolean | undefined */ let memoizedIsUnitTest: boolean | undefined +/** + * Memoized value for the Shopify check. + */ +let memoizedIsShopify: Promise | undefined + /** * Returns true if the CLI is running in debug mode. * @@ -76,12 +81,33 @@ export function isVerbose(env = process.env): boolean { * @param env - The environment variables from the environment of the current process. * @returns True if the CLI is used in a Shopify environment. */ -export async function isShopify(env = process.env): Promise { - if (Object.prototype.hasOwnProperty.call(env, environmentVariables.runAsUser)) { - return !isTruthy(env[environmentVariables.runAsUser]) +export function isShopify(env = process.env): Promise { + if (env === process.env && memoizedIsShopify !== undefined) { + // Memoize the result to avoid repeated filesystem checks for the presence of the dev binary. + return memoizedIsShopify } - const devInstalled = await lazyFileExists(pathConstants.executables.dev) - return devInstalled + + const resultPromise = (async () => { + if (Object.prototype.hasOwnProperty.call(env, environmentVariables.runAsUser)) { + return !isTruthy(env[environmentVariables.runAsUser]) + } + const devInstalled = await lazyFileExists(pathConstants.executables.dev) + return devInstalled + })() + + if (env === process.env) { + memoizedIsShopify = resultPromise + } + + return resultPromise +} + +/** + * Resets the memoized value for the Shopify check. + * This is only used for testing purposes. + */ +export function _resetIsShopifyMemo(): void { + memoizedIsShopify = undefined } /**