diff --git a/package.json b/package.json index 20bed9b068a..74dac1fd9b9 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "nx": "22.7.0", "oclif": "4.23.0", "octokit-plugin-create-pull-request": "^3.12.2", - "pathe": "1.1.1", + "pathe": "2.0.3", "pin-github-action": "^3.3.1", "rimraf": "^6.1.3", "ts-node": "^10.9.1", diff --git a/packages/cli-kit/package.json b/packages/cli-kit/package.json index 1725431d06d..0041641407c 100644 --- a/packages/cli-kit/package.json +++ b/packages/cli-kit/package.json @@ -143,13 +143,12 @@ "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", "node-fetch": "3.3.2", "open": "8.4.2", - "pathe": "1.1.2", + "pathe": "2.0.3", "react": "19.2.4", "semver": "7.6.3", "stacktracey": "2.1.8", 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..bc16cabae91 100644 --- a/packages/cli-kit/src/public/node/fs.ts +++ b/packages/cli-kit/src/public/node/fs.ts @@ -1,5 +1,5 @@ import {outputContent, outputToken, outputDebug} from './output.js' -import {joinPath, normalizePath, resolvePath} from './path.js' +import {basename, joinPath, matchesGlob, normalizePath, resolvePath, sep} from './path.js' import {OverloadParameters} from '../../private/common/ts/overloaded-parameters.js' import {getRandomName, RandomNameFamily} from '../common/string.js' import {systemTempDir} from '../../private/node/temp-dir.js' @@ -14,9 +14,7 @@ import { // @ts-ignore } from 'fs-extra/esm' -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 { mkdirSync as fsMkdirSync, @@ -67,7 +65,7 @@ import type {Pattern, Options as GlobOptions} from 'fast-glob' */ export function stripUpPath(path: string, strip: number): string { const parts = path.split(sep) - return join(...parts.slice(strip)) + return joinPath(...parts.slice(strip)) } /** @@ -76,7 +74,7 @@ export function stripUpPath(path: string, strip: number): string { * @param callback - The callback that receives the temporary directory. */ export async function inTemporaryDirectory(callback: (tmpDir: string) => T | Promise): Promise { - const tmpDir = await fsMkdtemp(join(systemTempDir, 'tmp-')) + const tmpDir = await fsMkdtemp(joinPath(systemTempDir, 'tmp-')) try { return await callback(tmpDir) } finally { @@ -89,7 +87,7 @@ export async function inTemporaryDirectory(callback: (tmpDir: string) => T | * @returns - The path to the temporary directory. */ export function tempDirectory(): string { - return fsMkdtempSync(join(systemTempDir, 'tmp-')) + return fsMkdtempSync(joinPath(systemTempDir, 'tmp-')) } /** @@ -688,8 +686,8 @@ export function findPathUpSync( } export interface MatchGlobOptions { - matchBase: boolean - noglobstar: boolean + matchBase?: boolean + noglobstar?: boolean } /** @@ -699,8 +697,27 @@ 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('\\') } /** diff --git a/packages/cli-kit/src/public/node/path.ts b/packages/cli-kit/src/public/node/path.ts index f3721780110..ddc0e171a9d 100644 --- a/packages/cli-kit/src/public/node/path.ts +++ b/packages/cli-kit/src/public/node/path.ts @@ -8,6 +8,8 @@ import { extname as extnamePathe, parse, isAbsolute, + matchesGlob as matchesGlobPathe, + sep as patheSep, } from 'pathe' import {fileURLToPath} from 'url' // eslint-disable-next-line n/prefer-global/url @@ -95,6 +97,22 @@ export function extname(path: string): string { return extnamePathe(path) } +/** + * The platform-agnostic path segment separator (always `/`, via pathe). + */ +export const sep = patheSep + +/** + * Determines whether a path matches a glob pattern. + * + * @param path - The path to match. + * @param pattern - The glob pattern (or patterns) to match against. + * @returns Whether the path matches the pattern. + */ +export function matchesGlob(path: string, pattern: string | string[]): boolean { + return matchesGlobPathe(path, pattern) +} + /** * Parses a path into its components (root, dir, base, ext, name). * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6355cc39894..6b502e9c356 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,8 +122,8 @@ importers: specifier: ^3.12.2 version: 3.13.1 pathe: - specifier: 1.1.1 - version: 1.1.1 + specifier: 2.0.3 + version: 2.0.3 pin-github-action: specifier: ^3.3.1 version: 3.4.0 @@ -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 @@ -439,8 +436,8 @@ importers: specifier: 8.4.2 version: 8.4.2 pathe: - specifier: 1.1.2 - version: 1.1.2 + specifier: 2.0.3 + version: 2.0.3 react: specifier: 19.2.4 version: 19.2.4 @@ -7672,14 +7669,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@1.1.1: - resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz} pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} @@ -17670,10 +17661,6 @@ snapshots: path-type@4.0.0: {} - pathe@1.1.1: {} - - pathe@1.1.2: {} - pathe@2.0.3: {} pathval@2.0.1: {}