diff --git a/.changeset/khaki-hornets-care.md b/.changeset/khaki-hornets-care.md new file mode 100644 index 000000000..ed1157126 --- /dev/null +++ b/.changeset/khaki-hornets-care.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme-check-node': patch +--- + +[Bug fix] Fix file URI handling for Windows platforms diff --git a/packages/theme-check-node/src/index.spec.ts b/packages/theme-check-node/src/index.spec.ts index 5092259c7..04883b333 100644 --- a/packages/theme-check-node/src/index.spec.ts +++ b/packages/theme-check-node/src/index.spec.ts @@ -1,6 +1,7 @@ import { afterEach, assert, beforeEach, describe, expect, it } from 'vitest'; -import { Config, SourceCodeType, getTheme } from './index'; +import { Config, SourceCodeType, getTheme, getThemeFilesPathPattern } from './index'; import { Workspace, makeTempWorkspace } from './test/test-helpers'; +import { pathToFileURL } from 'node:url'; describe('Unit: getTheme', () => { let workspace: Workspace; @@ -37,3 +38,14 @@ describe('Unit: getTheme', () => { expect(jsonFile.uri).to.equal(workspace.uri('locales/en.default.json').replace(/\\/g, '/')); }); }); + +describe('Unit: getThemeFilesPathPattern', () => { + // This is mostly just to catch edge cases in Windows paths. We want + // to ensure that paths do not start with a leading slash on Windows. + it('should correctly format the glob pattern', () => { + const rootUri = pathToFileURL(__dirname); + const normalizedGlob = getThemeFilesPathPattern(rootUri.toString()); + + expect(normalizedGlob).to.equal(__dirname.replace(/\\/g, '/') + '/**/*.{liquid,json}'); + }); +}); diff --git a/packages/theme-check-node/src/index.ts b/packages/theme-check-node/src/index.ts index c3877a982..0614095f2 100644 --- a/packages/theme-check-node/src/index.ts +++ b/packages/theme-check-node/src/index.ts @@ -26,6 +26,7 @@ import glob = require('glob'); import { autofix } from './autofix'; import { findConfigPath, loadConfig as resolveConfig } from './config'; import { NodeFileSystem } from './NodeFileSystem'; +import { fileURLToPath } from 'node:url'; const asyncGlob = promisify(glob); @@ -153,13 +154,18 @@ export async function getTheme(config: Config): Promise { // as mentioned in the documentation of node-glob // the path is normalised and '\' are replaced with '/' and then passed to the glob function - const normalizedGlob = path - .normalize(path.join(config.rootUri.replace(/^file:/, ''), '**/*.{liquid,json}')) - .replace(/\\/g, '/'); - const paths = await asyncGlob(normalizedGlob).then((result) => + let normalizedGlob = getThemeFilesPathPattern(config.rootUri); + + const paths = await asyncGlob(normalizedGlob, { absolute: true }).then((result) => // Global ignored paths should not be part of the theme result.filter((filePath) => !isIgnored(filePath, config)), ); const sourceCodes = await Promise.all(paths.map(toSourceCode)); return sourceCodes.filter((x): x is LiquidSourceCode | JSONSourceCode => x !== undefined); } + +export function getThemeFilesPathPattern(rootUri: string) { + return path + .normalize(path.join(fileURLToPath(rootUri), '**/*.{liquid,json}')) + .replace(/\\/g, '/'); +}