From bcf4742e70760f8e5ccde53fc9b65f5a83e9e781 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Wed, 7 Jan 2026 17:13:07 +0100 Subject: [PATCH] Fix: Respect .gitignore when discovering web and extension configurations --- .changeset/respect-gitignore-load.md | 5 ++ .../app/src/cli/models/app/loader.test.ts | 77 +++++++++++++++++++ packages/app/src/cli/models/app/loader.ts | 26 ++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 .changeset/respect-gitignore-load.md diff --git a/.changeset/respect-gitignore-load.md b/.changeset/respect-gitignore-load.md new file mode 100644 index 0000000000..0d141ff4b9 --- /dev/null +++ b/.changeset/respect-gitignore-load.md @@ -0,0 +1,5 @@ +--- +"@shopify/app": patch +--- + +Respect .gitignore when discovering web and extension configurations diff --git a/packages/app/src/cli/models/app/loader.test.ts b/packages/app/src/cli/models/app/loader.test.ts index 624a2d02b8..8c54f9de6f 100644 --- a/packages/app/src/cli/models/app/loader.test.ts +++ b/packages/app/src/cli/models/app/loader.test.ts @@ -753,6 +753,45 @@ redirect_urls = [ "https://example.com/api/auth" ] expect(app.webs.length).toBe(0) }) + test('ignores web blocks in gitignored directories', async () => { + // Given + await writeConfig(appConfiguration) + + // Create a gitignored directory with a web config + const ignoredDirectory = joinPath(tmpDir, 'ignored-worktree') + await mkdir(ignoredDirectory) + await writeWebConfiguration({webDirectory: ignoredDirectory, role: 'backend'}) + + // Create .gitignore file + const gitignoreContent = 'ignored-worktree/\n' + await writeFile(joinPath(tmpDir, '.gitignore'), gitignoreContent) + + // When + const app = await loadTestingApp() + + // Then + // Should only load the web from the non-ignored directory + expect(app.webs.length).toBe(1) + expect(app.webs[0]!.directory).not.toContain('ignored-worktree') + }) + + test('loads all web blocks when no .gitignore file exists', async () => { + // Given + await writeConfig(appConfiguration) + + // Create another directory with a web config (but no .gitignore file) + const anotherDirectory = joinPath(tmpDir, 'another-web') + await mkdir(anotherDirectory) + await writeWebConfiguration({webDirectory: anotherDirectory, role: 'frontend'}) + + // When + const app = await loadTestingApp() + + // Then + // Should load both web blocks since there's no .gitignore + expect(app.webs.length).toBe(2) + }) + test('loads the app when it has a extension with a valid configuration', async () => { // Given await writeConfig(appConfiguration) @@ -834,6 +873,44 @@ redirect_urls = [ "https://example.com/api/auth" ] expect(app.allExtensions[0]!.localIdentifier).toBe('custom-extension') }) + test('ignores extensions in gitignored directories', async () => { + // Given + await writeConfig(appConfiguration) + + // Create a non-ignored extension + const blockConfiguration = ` + name = "my_extension" + type = "checkout_post_purchase" + ` + await writeBlockConfig({ + blockConfiguration, + name: 'my-extension', + }) + await writeFile(joinPath(blockPath('my-extension'), 'index.js'), '') + + // Create a gitignored directory with an extension + const ignoredExtensionDir = joinPath(tmpDir, 'extensions', 'ignored-extension') + await mkdir(ignoredExtensionDir) + const ignoredBlockConfiguration = ` + name = "ignored_extension" + type = "checkout_post_purchase" + ` + await writeFile(joinPath(ignoredExtensionDir, 'my-ignored-extension.extension.toml'), ignoredBlockConfiguration) + await writeFile(joinPath(ignoredExtensionDir, 'index.js'), '') + + // Create .gitignore file + const gitignoreContent = 'extensions/ignored-extension/\n' + await writeFile(joinPath(tmpDir, '.gitignore'), gitignoreContent) + + // When + const app = await loadTestingApp() + + // Then + // Should only load the non-ignored extension + expect(app.allExtensions.length).toBe(1) + expect(app.allExtensions[0]!.configuration.name).toBe('my_extension') + }) + test('loads the app from a extension directory when it has a extension with a valid configuration', async () => { // Given await writeConfig(appConfiguration) diff --git a/packages/app/src/cli/models/app/loader.ts b/packages/app/src/cli/models/app/loader.ts index 56672c9b25..76da2eb184 100644 --- a/packages/app/src/cli/models/app/loader.ts +++ b/packages/app/src/cli/models/app/loader.ts @@ -400,7 +400,10 @@ class AppLoader this.loadWeb(path))) this.validateWebs(webs) @@ -559,7 +562,10 @@ class AppLoader { const directory = dirname(configurationPath) @@ -999,6 +1005,22 @@ async function checkIfGitTracked(appDirectory: string, configurationPath: string return isTracked } +/** + * Filters an array of paths to exclude those that match patterns in .gitignore + */ +async function filterIgnoredPaths(appDirectory: string, paths: string[]): Promise { + const gitIgnorePath = joinPath(appDirectory, '.gitignore') + if (!fileExistsSync(gitIgnorePath)) return paths + + const gitIgnoreContent = await readFile(gitIgnorePath) + const ignored = ignore.default().add(gitIgnoreContent) + + return paths.filter((path) => { + const relative = relativePath(appDirectory, path) + return !ignored.ignores(relative) + }) +} + async function getConfigurationPath(appDirectory: string, configName: string | undefined) { const configurationFileName = getAppConfigurationFileName(configName) const configurationPath = joinPath(appDirectory, configurationFileName)