Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,31 @@ describe('rescanImports', async () => {
})
})
})

describe('SHOPIFY_CLI_DISABLE_IMPORT_SCANNING', () => {
test('skips import scanning when env var is set', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extensionInstance = await testUIExtension({
directory: tmpDir,
entrySourceFilePath: joinPath(tmpDir, 'src', 'index.ts'),
})

const srcDir = joinPath(tmpDir, 'src')
await mkdir(srcDir)
await writeFile(joinPath(srcDir, 'index.ts'), 'import "../shared"')

vi.mocked(extractImportPathsRecursively).mockReset()
vi.mocked(extractImportPathsRecursively).mockReturnValue(['/some/external/file.ts'])

process.env.SHOPIFY_CLI_DISABLE_IMPORT_SCANNING = '1'
try {
const watched = extensionInstance.watchedFiles()
expect(extractImportPathsRecursively).not.toHaveBeenCalled()
expect(watched.some((file) => file.includes('external'))).toBe(false)
} finally {
delete process.env.SHOPIFY_CLI_DISABLE_IMPORT_SCANNING
vi.mocked(extractImportPathsRecursively).mockReset()
}
})
})
})
13 changes: 12 additions & 1 deletion packages/app/src/cli/models/extensions/extension-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ import {joinPath, basename, normalizePath, resolvePath} from '@shopify/cli-kit/n
import {fileExists, touchFile, moveFile, writeFile, glob, copyFile, globSync} from '@shopify/cli-kit/node/fs'
import {getPathValue} from '@shopify/cli-kit/common/object'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {extractJSImports, extractImportPathsRecursively} from '@shopify/cli-kit/node/import-extractor'
import {
extractJSImports,
extractImportPathsRecursively,
clearImportPathsCache,
} from '@shopify/cli-kit/node/import-extractor'
import {isTruthy} from '@shopify/cli-kit/node/context/utilities'
import {uniq} from '@shopify/cli-kit/common/array'

export const CONFIG_EXTENSION_IDS: string[] = [
Expand Down Expand Up @@ -516,6 +521,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
async rescanImports(): Promise<boolean> {
const oldImportPaths = this.cachedImportPaths
this.cachedImportPaths = undefined
clearImportPathsCache()
this.scanImports()
return oldImportPaths !== this.cachedImportPaths
}
Expand All @@ -530,6 +536,11 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
return this.cachedImportPaths
}

if (isTruthy(process.env.SHOPIFY_CLI_DISABLE_IMPORT_SCANNING)) {
this.cachedImportPaths = []
return this.cachedImportPaths
}

try {
const imports = this.devSessionDefaultWatchPaths().flatMap((entryFile) => {
return extractImportPathsRecursively(entryFile).map((importPath) => normalizePath(resolvePath(importPath)))
Expand Down
1 change: 1 addition & 0 deletions packages/cli-kit/src/private/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const environmentVariables = {
neverUsePartnersApi: 'SHOPIFY_CLI_NEVER_USE_PARTNERS_API',
skipNetworkLevelRetry: 'SHOPIFY_CLI_SKIP_NETWORK_LEVEL_RETRY',
maxRequestTimeForNetworkCalls: 'SHOPIFY_CLI_MAX_REQUEST_TIME_FOR_NETWORK_CALLS',
disableImportScanning: 'SHOPIFY_CLI_DISABLE_IMPORT_SCANNING',
}

export const defaultThemeKitAccessDomain = 'theme-kit-access.shopifyapps.com'
Expand Down
39 changes: 37 additions & 2 deletions packages/cli-kit/src/public/node/import-extractor.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {inTemporaryDirectory, mkdir, writeFile} from './fs.js'
import {joinPath} from './path.js'
import {extractImportPaths, extractImportPathsRecursively} from './import-extractor.js'
import {describe, test, expect} from 'vitest'
import {extractImportPaths, extractImportPathsRecursively, clearImportPathsCache} from './import-extractor.js'
import {describe, test, expect, beforeEach} from 'vitest'

beforeEach(() => {
clearImportPathsCache()
})

describe('extractImportPaths', () => {
describe('JavaScript imports', () => {
Expand Down Expand Up @@ -610,3 +614,34 @@ describe('extractImportPathsRecursively', () => {
})
})
})

describe('clearImportPathsCache', () => {
test('picks up file changes after cache is cleared', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const mainFile = joinPath(tmpDir, 'main.js')
const utilsFile = joinPath(tmpDir, 'utils.js')

await writeFile(utilsFile, 'export const foo = "bar"')
await writeFile(mainFile, `import { foo } from './utils.js'`)

const firstResult = extractImportPaths(mainFile)
expect(firstResult).toContain(utilsFile)

// Modify the file to add a new import
const helpersFile = joinPath(tmpDir, 'helpers.js')
await writeFile(helpersFile, 'export const helper = () => {}')
await writeFile(mainFile, `import { foo } from './utils.js'\nimport { helper } from './helpers.js'`)

// Without clearing, we still get cached results
const cachedResult = extractImportPaths(mainFile)
expect(cachedResult).toHaveLength(1)

// After clearing, new imports are picked up
clearImportPathsCache()
const freshResult = extractImportPaths(mainFile)
expect(freshResult).toContain(utilsFile)
expect(freshResult).toContain(helpersFile)
expect(freshResult).toHaveLength(2)
})
})
})
28 changes: 25 additions & 3 deletions packages/cli-kit/src/public/node/import-extractor.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
import {readFileSync, fileExistsSync, isDirectorySync} from './fs.js'
import {dirname, joinPath} from './path.js'

// Caches direct import results per file path to avoid redundant file reads and parsing
// when multiple extensions import the same shared code.
const directImportsCache = new Map<string, string[]>()

/**
* Clears the import paths cache. Should be called when watched files change
* so that rescanning picks up updated imports.
*/
export function clearImportPathsCache(): void {
directImportsCache.clear()
}

/**
* Extracts import paths from a source file.
* Supports JavaScript, TypeScript, and Rust files.
* Results are cached per file path to avoid redundant I/O.
*
* @param filePath - Path to the file to analyze.
* @returns Array of absolute paths to imported files.
*/
export function extractImportPaths(filePath: string): string[] {
const cached = directImportsCache.get(filePath)
if (cached) return cached

const content = readFileSync(filePath).toString()
const ext = filePath.substring(filePath.lastIndexOf('.'))

let result: string[]
switch (ext) {
case '.js':
case '.mjs':
case '.cjs':
case '.ts':
case '.tsx':
case '.jsx':
return extractJSLikeImports(content, filePath)
result = extractJSLikeImports(content, filePath)
break
case '.rs':
return extractRustImports(content, filePath)
result = extractRustImports(content, filePath)
break
default:
return []
result = []
}

directImportsCache.set(filePath, result)
return result
}

/**
Expand Down
Loading