From feac0a11fdb0575b04015facb3f272a0ca0be6ca Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Fri, 8 May 2026 00:54:44 +0000 Subject: [PATCH 1/2] [Performance] Optimize CLI startup and project discovery --- .jules/bolt.md | 1 + packages/cli-kit/src/public/node/fs.test.ts | 35 +++++++++++++++++++ packages/cli-kit/src/public/node/fs.ts | 11 +++--- packages/cli-kit/src/public/node/is-global.ts | 14 ++++++-- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000000..93df40e4fef --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1 @@ +## 2026-05-20 - Optimize CLI startup and project discovery, **Learning**: Lazy loading heavy dependencies like `fast-glob` via `createRequire` and providing `fileExistsSync` fast-paths for common files avoids significant overhead during initial CLI execution and upward directory traversals. **Action**: Deferred `fast-glob` loading in `fs.ts` and optimized `getProjectDir` in `is-global.ts`. diff --git a/packages/cli-kit/src/public/node/fs.test.ts b/packages/cli-kit/src/public/node/fs.test.ts index d30612b5b96..6616642ee21 100644 --- a/packages/cli-kit/src/public/node/fs.test.ts +++ b/packages/cli-kit/src/public/node/fs.test.ts @@ -18,6 +18,7 @@ import { generateRandomNameForSubdirectory, readFileSync, glob, + globSync, detectEOL, copyDirectoryContents, symlink, @@ -319,6 +320,40 @@ describe('glob', () => { }) }) +describe('globSync', () => { + test('returns matches for the given pattern, including dotfiles by default (dot:true)', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const visiblePath = joinPath(tmpDir, 'visible.toml') + const hiddenPath = joinPath(tmpDir, '.hidden.toml') + await writeFile(visiblePath, '') + await writeFile(hiddenPath, '') + + // When + const matches = globSync(joinPath(tmpDir, '*.toml')).map(normalizePath).sort() + + // Then + expect(matches).toEqual([normalizePath(hiddenPath), normalizePath(visiblePath)].sort()) + }) + }) + + test('honors an explicit dot:false option, excluding dotfiles', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const visiblePath = joinPath(tmpDir, 'visible.toml') + const hiddenPath = joinPath(tmpDir, '.hidden.toml') + await writeFile(visiblePath, '') + await writeFile(hiddenPath, '') + + // When + const matches = globSync(joinPath(tmpDir, '*.toml'), {dot: false}).map(normalizePath) + + // Then + expect(matches).toEqual([normalizePath(visiblePath)]) + }) + }) +}) + describe('detectEOL', () => { test('detects the EOL of a file', async () => { // Given diff --git a/packages/cli-kit/src/public/node/fs.ts b/packages/cli-kit/src/public/node/fs.ts index 5948c9f6bb6..61e427f7adb 100644 --- a/packages/cli-kit/src/public/node/fs.ts +++ b/packages/cli-kit/src/public/node/fs.ts @@ -17,7 +17,7 @@ import { import {sep, join} from 'pathe' import {findUp as internalFindUp, findUpSync as internalFindUpSync} from 'find-up' import {minimatch} from 'minimatch' -import fastGlobLib from 'fast-glob' +import {createRequire} from 'module' import { mkdirSync as fsMkdirSync, readFileSync as fsReadFileSync, @@ -33,7 +33,6 @@ import { accessSync, ReadStream, WriteStream, - statSync, } from 'fs' import { @@ -58,6 +57,8 @@ import * as os from 'os' import type {Pattern, Options as GlobOptions} from 'fast-glob' +const require = createRequire(import.meta.url) + /** * Strip the first `strip` parts of the path. * @@ -510,7 +511,7 @@ export function unixFileIsOwnedByCurrentUser(path: string): boolean | undefined if (!fileExistsSync(path)) return false try { - const stats = statSync(path) + const stats = fsStatSync(path) const currentUid = process.getuid() return stats.uid === currentUid @@ -594,11 +595,13 @@ export async function glob(pattern: Pattern | Pattern[], options?: GlobOptions): * @returns An array of pathnames that match the given pattern. */ export function globSync(pattern: Pattern | Pattern[], options?: GlobOptions): string[] { + // Performance: fast-glob is a heavy dependency. We lazy-load it here to avoid + // overhead during CLI startup for commands that don't need globbing. let overridenOptions = options if (options?.dot == null) { overridenOptions = {...options, dot: true} } - return fastGlobLib.sync(pattern, overridenOptions) + return require('fast-glob').sync(pattern, overridenOptions) } /** diff --git a/packages/cli-kit/src/public/node/is-global.ts b/packages/cli-kit/src/public/node/is-global.ts index 2a60d6745ba..f599c4195e5 100644 --- a/packages/cli-kit/src/public/node/is-global.ts +++ b/packages/cli-kit/src/public/node/is-global.ts @@ -1,6 +1,6 @@ import {cwd, dirname, isSubpath, joinPath, sniffForPath} from './path.js' import {isUnitTest} from './context/local.js' -import {findPathUpSync, globSync} from './fs.js' +import {findPathUpSync, globSync, fileExistsSync} from './fs.js' import {realpathSync} from 'fs' import type {PackageManager} from './node-package-manager.js' @@ -140,9 +140,17 @@ export function inferPackageManagerForGlobalCLI(argv = process.argv, env = proce * @returns The project root directory, or undefined if not found. */ export function getProjectDir(directory: string): string | undefined { - const configFiles = ['shopify.app{,.*}.toml', 'hydrogen.config.js', 'hydrogen.config.ts'] + // Performance: Check for the most common config files first using fileExistsSync (fast-path) + // to avoid the overhead of globbing/directory scanning in the common case. + const configFiles = ['shopify.app.toml', 'hydrogen.config.js', 'hydrogen.config.ts'] const existsConfigFile = (directory: string) => { - const configPaths = globSync(configFiles.map((file) => joinPath(directory, file))) + for (const file of configFiles) { + const configPath = joinPath(directory, file) + if (fileExistsSync(configPath)) return configPath + } + + // Fallback to glob for custom app config files or if fast-path files were not found. + const configPaths = globSync(joinPath(directory, 'shopify.app.*.toml')) return configPaths.length > 0 ? configPaths[0] : undefined } try { From f427e60e855ef8c6e035a63d6cacbec51eb21da8 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Wed, 20 May 2026 12:41:12 +0200 Subject: [PATCH 2/2] Improve typing Co-authored-by: Craig Martin --- .jules/bolt.md | 1 - packages/cli-kit/src/public/node/fs.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md deleted file mode 100644 index 93df40e4fef..00000000000 --- a/.jules/bolt.md +++ /dev/null @@ -1 +0,0 @@ -## 2026-05-20 - Optimize CLI startup and project discovery, **Learning**: Lazy loading heavy dependencies like `fast-glob` via `createRequire` and providing `fileExistsSync` fast-paths for common files avoids significant overhead during initial CLI execution and upward directory traversals. **Action**: Deferred `fast-glob` loading in `fs.ts` and optimized `getProjectDir` in `is-global.ts`. diff --git a/packages/cli-kit/src/public/node/fs.ts b/packages/cli-kit/src/public/node/fs.ts index 61e427f7adb..e4f7cfcc057 100644 --- a/packages/cli-kit/src/public/node/fs.ts +++ b/packages/cli-kit/src/public/node/fs.ts @@ -601,7 +601,7 @@ export function globSync(pattern: Pattern | Pattern[], options?: GlobOptions): s if (options?.dot == null) { overridenOptions = {...options, dot: true} } - return require('fast-glob').sync(pattern, overridenOptions) + return (require('fast-glob') as typeof import('fast-glob')).sync(pattern, overridenOptions) } /**