diff --git a/.changeset/remove-cli-kit-minimatch.md b/.changeset/remove-cli-kit-minimatch.md new file mode 100644 index 0000000000..08d1b5d330 --- /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 1725431d06..6418c311f6 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 d30612b5b9..38674abc48 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 5948c9f6bb..266eec8625 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 6355cc3989..990ab02abb 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