From 000eb8b3d2693e3f0889b5168d04bfee4563a804 Mon Sep 17 00:00:00 2001 From: Konstantin Tarkus Date: Fri, 30 Jan 2026 16:56:53 +0100 Subject: [PATCH 1/2] feat: skip gitignore for external patterns Patterns referencing parent directories (../) now skip .gitignore filtering since it only applies to files within cwd. --- package.json | 2 +- src/bundle.ts | 39 +++++++++++++++++++++++++++++++++++---- tests/unit/bundle.test.ts | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9b49f25..25fc89b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "srcpack", - "version": "0.1.14", + "version": "0.1.15", "description": "Zero-config CLI for bundling code into LLM-optimized context files", "keywords": [ "llm", diff --git a/src/bundle.ts b/src/bundle.ts index 94857a4..49a815f 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -161,11 +161,22 @@ async function loadGitignore(cwd: string): Promise { return { ignore: ig, globPatterns }; } +/** + * Check if a glob pattern references paths outside cwd. + * Patterns traversing to parent directories start with ../ (or ./../). + */ +function isExternalPattern(pattern: string): boolean { + // Handle redundant ./ prefix (e.g., ./../other) + const normalized = pattern.startsWith("./") ? pattern.slice(2) : pattern; + return normalized.startsWith("../"); +} + /** * Resolve bundle config to a list of file paths. * - Regular patterns respect .gitignore * - Force patterns (+prefix) bypass .gitignore * - Exclude patterns (!prefix) filter both + * - External patterns (../) skip .gitignore entirely */ export async function resolvePatterns( config: BundleConfigInput, @@ -176,10 +187,13 @@ export async function resolvePatterns( const { ignore: gitignore, globPatterns } = await loadGitignore(cwd); const files = new Set(); - // Regular includes: respect .gitignore - // Pass gitignore patterns to fast-glob to skip ignored directories during traversal - if (include.length > 0) { - const matches = await glob(include, { + // Split patterns into internal (within cwd) and external (../ prefixed) + const internalPatterns = include.filter((p) => !isExternalPattern(p)); + const externalPatterns = include.filter(isExternalPattern); + + // Internal patterns: respect .gitignore + if (internalPatterns.length > 0) { + const matches = await glob(internalPatterns, { cwd, onlyFiles: true, dot: true, @@ -195,6 +209,23 @@ export async function resolvePatterns( } } + // External patterns: skip .gitignore (it doesn't apply outside cwd) + if (externalPatterns.length > 0) { + const matches = await glob(externalPatterns, { + cwd, + onlyFiles: true, + dot: true, + }); + for (const match of matches) { + if (!isExcluded(match, excludeMatchers)) { + const fullPath = join(cwd, match); + if (!(await isBinary(fullPath))) { + files.add(match); + } + } + } + } + // Force includes: bypass .gitignore (no ignore patterns passed to glob) if (force.length > 0) { const matches = await glob(force, { cwd, onlyFiles: true, dot: true }); diff --git a/tests/unit/bundle.test.ts b/tests/unit/bundle.test.ts index 6cad97d..8829da9 100644 --- a/tests/unit/bundle.test.ts +++ b/tests/unit/bundle.test.ts @@ -167,6 +167,40 @@ describe("resolvePatterns", () => { expect(files).toContain("build/keep.txt"); // Re-included by negation expect(files).not.toContain("build/bundle.js"); // Still ignored }); + + test("should handle patterns pointing outside cwd", async () => { + // Use sample-project as cwd and resolve pattern pointing to gitignore-project + // External patterns skip .gitignore entirely (it doesn't apply to external files) + const files = await resolvePatterns( + "../gitignore-project/src/**/*.ts", + fixturesDir, + ); + + // Should include files from the external directory + expect(files).toContain("../gitignore-project/src/index.ts"); + expect(files).toContain("../gitignore-project/src/utils.ts"); + }); + + test("should not apply cwd gitignore to external patterns", async () => { + // fixturesDir is sample-project; external pattern points to gitignore-project + // Even though "dist" is a common gitignore pattern, external paths skip .gitignore + const files = await resolvePatterns( + "../gitignore-project/dist/**/*.js", + fixturesDir, + ); + + expect(files).toContain("../gitignore-project/dist/bundle.js"); + }); + + test("should handle ./../ prefix as external pattern", async () => { + // Redundant ./ prefix should still be recognized as external + const files = await resolvePatterns( + "./../gitignore-project/src/**/*.ts", + fixturesDir, + ); + + expect(files).toContain("./../gitignore-project/src/index.ts"); + }); }); describe("formatIndex", () => { From cb4b30cb7d424284f750d31edaf2916e20981e38 Mon Sep 17 00:00:00 2001 From: Konstantin Tarkus Date: Fri, 30 Jan 2026 17:05:38 +0100 Subject: [PATCH 2/2] test: track dist fixture for external pattern test --- tests/fixtures/gitignore-project/dist/bundle.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/fixtures/gitignore-project/dist/bundle.js diff --git a/tests/fixtures/gitignore-project/dist/bundle.js b/tests/fixtures/gitignore-project/dist/bundle.js new file mode 100644 index 0000000..3f917a9 --- /dev/null +++ b/tests/fixtures/gitignore-project/dist/bundle.js @@ -0,0 +1 @@ +// compiled output