From 99c594686c88c2fe95f28c3bc68c94244454b39f Mon Sep 17 00:00:00 2001 From: Ariel Caplan Date: Fri, 5 Jun 2026 06:52:28 +0300 Subject: [PATCH] Remove minimatch from cli-kit; use native path.matchesGlob Co-Authored-By: Claude --- .changeset/remove-cli-kit-minimatch.md | 5 +++ packages/cli-kit/package.json | 1 - packages/cli-kit/src/public/node/fs.test.ts | 36 +++++++++++++++++++++ packages/cli-kit/src/public/node/fs.ts | 36 ++++++++++++++++++--- pnpm-lock.yaml | 3 -- 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 .changeset/remove-cli-kit-minimatch.md diff --git a/.changeset/remove-cli-kit-minimatch.md b/.changeset/remove-cli-kit-minimatch.md new file mode 100644 index 00000000000..08d1b5d3300 --- /dev/null +++ b/.changeset/remove-cli-kit-minimatch.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli-kit': patch +--- + +Remove the minimatch dependency from cli-kit glob matching. diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 1725431d06d..6418c311f67 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -143,7 +143,6 @@ "liquidjs": "10.26.0", "lodash": "4.17.23", "macaddress": "0.5.3", - "minimatch": "9.0.8", "mrmime": "1.0.1", "network-interfaces": "1.1.0", "node-abort-controller": "3.1.1", diff --git a/packages/cli-kit/src/public/node/fs.test.ts b/packages/cli-kit/src/public/node/fs.test.ts index d30612b5b96..38674abc480 100644 --- a/packages/cli-kit/src/public/node/fs.test.ts +++ b/packages/cli-kit/src/public/node/fs.test.ts @@ -22,6 +22,7 @@ import { copyDirectoryContents, symlink, fileRealPath, + matchGlob, } from './fs.js' import {joinPath, normalizePath} from './path.js' import * as array from '../common/array.js' @@ -319,6 +320,41 @@ describe('glob', () => { }) }) +describe('matchGlob', () => { + test('matches liquid file patterns with native Node glob semantics', () => { + expect(matchGlob('theme.liquid', '*.liquid')).toBe(true) + expect(matchGlob('layout/theme.liquid', '*.liquid')).toBe(false) + expect(matchGlob('layout/theme.liquid', '**/*.liquid')).toBe(true) + expect(matchGlob('assets/theme.css', '*.liquid')).toBe(false) + }) + + test('matches watch-style globs used by extension file watching', () => { + expect(matchGlob('src/main.rs', 'src/**/*.rs')).toBe(true) + expect(matchGlob('src/lib/main.rs', 'src/**/*.rs')).toBe(true) + expect(matchGlob('src/main.ts', 'src/**/*.rs')).toBe(false) + expect(matchGlob('/tmp/my-function/src/main.rs', '/tmp/my-function/src/**/*.rs')).toBe(true) + expect(matchGlob('/tmp/my-function/src/lib/main.rs', '/tmp/my-function/src/**/*.rs')).toBe(true) + }) + + test('matches project config selection globs', () => { + expect(matchGlob('extensions/my-ext/shopify.extension.toml', 'extensions/*/*.extension.toml')).toBe(true) + expect(matchGlob('extensions/nested/my-ext/shopify.extension.toml', 'extensions/*/*.extension.toml')).toBe(false) + expect(matchGlob('web/shopify.web.toml', '**/shopify.web.toml')).toBe(true) + }) + + test('supports matchBase for theme ignore patterns', () => { + expect(matchGlob('assets/theme.css', '*.css', {matchBase: true, noglobstar: true})).toBe(true) + expect(matchGlob('assets/theme.css', '*.liquid', {matchBase: true, noglobstar: true})).toBe(false) + }) + + test('supports noglobstar for theme ignore patterns', () => { + expect( + matchGlob('templates/customers/account.json', 'templates/**/*.json', {matchBase: true, noglobstar: true}), + ).toBe(true) + expect(matchGlob('templates/404.json', 'templates/**/*.json', {matchBase: true, noglobstar: true})).toBe(false) + }) +}) + 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..266eec86255 100644 --- a/packages/cli-kit/src/public/node/fs.ts +++ b/packages/cli-kit/src/public/node/fs.ts @@ -16,8 +16,9 @@ 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' +// eslint-disable-next-line no-restricted-imports -- Node 22 native glob matching is not wrapped by cli-kit path helpers. +import * as nodePath from 'node:path' import { mkdirSync as fsMkdirSync, readFileSync as fsReadFileSync, @@ -688,10 +689,12 @@ export function findPathUpSync( } export interface MatchGlobOptions { - matchBase: boolean - noglobstar: boolean + matchBase?: boolean + noglobstar?: boolean } +const {matchesGlob} = nodePath as typeof nodePath & {matchesGlob(path: string, pattern: string): boolean} + /** * Matches a key against a glob pattern. * @param key - The key to match. @@ -699,8 +702,31 @@ export interface MatchGlobOptions { * @param options - The options to refine the matching approach. * @returns true if the key matches the pattern, false otherwise. */ -export function matchGlob(key: string, pattern: string, options?: MatchGlobOptions): boolean { - return minimatch(key, pattern, options) +export function matchGlob(key: string, pattern: string, options: MatchGlobOptions = {}): boolean { + const effectivePattern = options.noglobstar ? patternWithoutGlobstars(pattern) : pattern + + if (matchesGlob(key, effectivePattern)) return true + + if (options.matchBase && !hasPathSeparator(effectivePattern)) { + return matchesGlob(baseName(key), effectivePattern) + } + + return false +} + +function patternWithoutGlobstars(pattern: string): string { + return pattern + .split(/([/\\])/) + .map((part) => (part === '**' ? '*' : part)) + .join('') +} + +function hasPathSeparator(path: string): boolean { + return path.includes('/') || path.includes('\\') +} + +function baseName(path: string): string { + return path.split(/[/\\]/).pop() ?? path } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6355cc39894..990ab02abb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -420,9 +420,6 @@ importers: macaddress: specifier: 0.5.3 version: 0.5.3 - minimatch: - specifier: 9.0.8 - version: 9.0.8 mrmime: specifier: 1.0.1 version: 1.0.1