Skip to content

Commit b20a912

Browse files
authored
feat: skip gitignore for external patterns (#8)
1 parent f336787 commit b20a912

4 files changed

Lines changed: 71 additions & 5 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "srcpack",
3-
"version": "0.1.14",
3+
"version": "0.1.15",
44
"description": "Zero-config CLI for bundling code into LLM-optimized context files",
55
"keywords": [
66
"llm",

src/bundle.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,22 @@ async function loadGitignore(cwd: string): Promise<GitignoreResult> {
161161
return { ignore: ig, globPatterns };
162162
}
163163

164+
/**
165+
* Check if a glob pattern references paths outside cwd.
166+
* Patterns traversing to parent directories start with ../ (or ./../).
167+
*/
168+
function isExternalPattern(pattern: string): boolean {
169+
// Handle redundant ./ prefix (e.g., ./../other)
170+
const normalized = pattern.startsWith("./") ? pattern.slice(2) : pattern;
171+
return normalized.startsWith("../");
172+
}
173+
164174
/**
165175
* Resolve bundle config to a list of file paths.
166176
* - Regular patterns respect .gitignore
167177
* - Force patterns (+prefix) bypass .gitignore
168178
* - Exclude patterns (!prefix) filter both
179+
* - External patterns (../) skip .gitignore entirely
169180
*/
170181
export async function resolvePatterns(
171182
config: BundleConfigInput,
@@ -176,10 +187,13 @@ export async function resolvePatterns(
176187
const { ignore: gitignore, globPatterns } = await loadGitignore(cwd);
177188
const files = new Set<string>();
178189

179-
// Regular includes: respect .gitignore
180-
// Pass gitignore patterns to fast-glob to skip ignored directories during traversal
181-
if (include.length > 0) {
182-
const matches = await glob(include, {
190+
// Split patterns into internal (within cwd) and external (../ prefixed)
191+
const internalPatterns = include.filter((p) => !isExternalPattern(p));
192+
const externalPatterns = include.filter(isExternalPattern);
193+
194+
// Internal patterns: respect .gitignore
195+
if (internalPatterns.length > 0) {
196+
const matches = await glob(internalPatterns, {
183197
cwd,
184198
onlyFiles: true,
185199
dot: true,
@@ -195,6 +209,23 @@ export async function resolvePatterns(
195209
}
196210
}
197211

212+
// External patterns: skip .gitignore (it doesn't apply outside cwd)
213+
if (externalPatterns.length > 0) {
214+
const matches = await glob(externalPatterns, {
215+
cwd,
216+
onlyFiles: true,
217+
dot: true,
218+
});
219+
for (const match of matches) {
220+
if (!isExcluded(match, excludeMatchers)) {
221+
const fullPath = join(cwd, match);
222+
if (!(await isBinary(fullPath))) {
223+
files.add(match);
224+
}
225+
}
226+
}
227+
}
228+
198229
// Force includes: bypass .gitignore (no ignore patterns passed to glob)
199230
if (force.length > 0) {
200231
const matches = await glob(force, { cwd, onlyFiles: true, dot: true });
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// compiled output

tests/unit/bundle.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,40 @@ describe("resolvePatterns", () => {
167167
expect(files).toContain("build/keep.txt"); // Re-included by negation
168168
expect(files).not.toContain("build/bundle.js"); // Still ignored
169169
});
170+
171+
test("should handle patterns pointing outside cwd", async () => {
172+
// Use sample-project as cwd and resolve pattern pointing to gitignore-project
173+
// External patterns skip .gitignore entirely (it doesn't apply to external files)
174+
const files = await resolvePatterns(
175+
"../gitignore-project/src/**/*.ts",
176+
fixturesDir,
177+
);
178+
179+
// Should include files from the external directory
180+
expect(files).toContain("../gitignore-project/src/index.ts");
181+
expect(files).toContain("../gitignore-project/src/utils.ts");
182+
});
183+
184+
test("should not apply cwd gitignore to external patterns", async () => {
185+
// fixturesDir is sample-project; external pattern points to gitignore-project
186+
// Even though "dist" is a common gitignore pattern, external paths skip .gitignore
187+
const files = await resolvePatterns(
188+
"../gitignore-project/dist/**/*.js",
189+
fixturesDir,
190+
);
191+
192+
expect(files).toContain("../gitignore-project/dist/bundle.js");
193+
});
194+
195+
test("should handle ./../ prefix as external pattern", async () => {
196+
// Redundant ./ prefix should still be recognized as external
197+
const files = await resolvePatterns(
198+
"./../gitignore-project/src/**/*.ts",
199+
fixturesDir,
200+
);
201+
202+
expect(files).toContain("./../gitignore-project/src/index.ts");
203+
});
170204
});
171205

172206
describe("formatIndex", () => {

0 commit comments

Comments
 (0)