srcpack 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,6 +159,8 @@ const config = await loadConfig();
159
159
  - [Discord](https://discord.com/invite/aG83xEb6RX) — Questions, feedback, and discussion
160
160
  - [GitHub Issues](https://github.com/kriasoft/srcpack/issues) — Bug reports and feature requests
161
161
 
162
+ New contributors and OSS maintainers are welcome — join us on Discord or open an issue / PR.
163
+
162
164
  ## Backers
163
165
 
164
166
  <a href="https://reactstarter.com/b/1"><img src="https://reactstarter.com/b/1.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/2"><img src="https://reactstarter.com/b/2.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/3"><img src="https://reactstarter.com/b/3.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/4"><img src="https://reactstarter.com/b/4.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/5"><img src="https://reactstarter.com/b/5.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/6"><img src="https://reactstarter.com/b/6.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/7"><img src="https://reactstarter.com/b/7.png" height="60" /></a>&nbsp;&nbsp;<a href="https://reactstarter.com/b/8"><img src="https://reactstarter.com/b/8.png" height="60" /></a>
package/dist/cli.js CHANGED
@@ -207577,22 +207577,52 @@ function normalizePatterns(config) {
207577
207577
  function isExcluded(filePath, matchers) {
207578
207578
  return matchers.some((match) => match(filePath));
207579
207579
  }
207580
+ function gitignoreToGlobPatterns(lines) {
207581
+ const hasNegation = lines.some((line) => {
207582
+ const trimmed = line.trim();
207583
+ return trimmed.startsWith("!");
207584
+ });
207585
+ if (hasNegation)
207586
+ return [];
207587
+ const patterns = [];
207588
+ for (const line of lines) {
207589
+ const trimmed = line.trim();
207590
+ if (!trimmed || trimmed.startsWith("#"))
207591
+ continue;
207592
+ if (trimmed.startsWith("/") || trimmed.includes("*") || trimmed.includes("?") || trimmed.includes("[") || trimmed.includes("/") || trimmed.includes("\\")) {
207593
+ continue;
207594
+ }
207595
+ const name = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
207596
+ if (name && /^[\w.-]+$/.test(name)) {
207597
+ patterns.push(`**/${name}/**`);
207598
+ }
207599
+ }
207600
+ return patterns;
207601
+ }
207580
207602
  async function loadGitignore(cwd) {
207581
207603
  const ig = import_ignore.default();
207582
207604
  const gitignorePath = join(cwd, ".gitignore");
207605
+ let globPatterns = [];
207583
207606
  try {
207584
207607
  const content = await readFile(gitignorePath, "utf-8");
207585
207608
  ig.add(content);
207609
+ globPatterns = gitignoreToGlobPatterns(content.split(`
207610
+ `));
207586
207611
  } catch {}
207587
- return ig;
207612
+ return { ignore: ig, globPatterns };
207588
207613
  }
207589
207614
  async function resolvePatterns(config, cwd) {
207590
207615
  const { include, exclude, force } = normalizePatterns(config);
207591
207616
  const excludeMatchers = exclude.map((p) => import_picomatch.default(p));
207592
- const gitignore = await loadGitignore(cwd);
207617
+ const { ignore: gitignore, globPatterns } = await loadGitignore(cwd);
207593
207618
  const files = new Set;
207594
207619
  if (include.length > 0) {
207595
- const matches = await import_fast_glob.glob(include, { cwd, onlyFiles: true, dot: true });
207620
+ const matches = await import_fast_glob.glob(include, {
207621
+ cwd,
207622
+ onlyFiles: true,
207623
+ dot: true,
207624
+ ignore: globPatterns
207625
+ });
207596
207626
  for (const match of matches) {
207597
207627
  if (!isExcluded(match, excludeMatchers) && !gitignore.ignores(match)) {
207598
207628
  const fullPath = join(cwd, match);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "srcpack",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Zero-config CLI for bundling code into LLM-optimized context files",
5
5
  "keywords": [
6
6
  "llm",
package/src/bundle.ts CHANGED
@@ -86,20 +86,79 @@ function isExcluded(filePath: string, matchers: Matcher[]): boolean {
86
86
  }
87
87
 
88
88
  /**
89
- * Load and parse .gitignore file from a directory
89
+ * Convert gitignore patterns to glob ignore patterns for fast-glob.
90
+ * This prevents traversing into ignored directories (performance optimization).
91
+ *
92
+ * Conservative approach: only convert simple, unambiguous directory patterns.
93
+ * Complex patterns (negations, root-anchored, globs) are left to the ignore filter.
90
94
  */
91
- async function loadGitignore(cwd: string): Promise<Ignore> {
95
+ function gitignoreToGlobPatterns(lines: string[]): string[] {
96
+ // If any negation patterns exist, skip optimization entirely
97
+ // (negations could re-include files in otherwise-ignored directories)
98
+ const hasNegation = lines.some((line) => {
99
+ const trimmed = line.trim();
100
+ // Any line starting with ! is a negation (including !#file which negates "#file")
101
+ return trimmed.startsWith("!");
102
+ });
103
+ if (hasNegation) return [];
104
+
105
+ const patterns: string[] = [];
106
+
107
+ for (const line of lines) {
108
+ const trimmed = line.trim();
109
+ // Skip empty lines and comments
110
+ if (!trimmed || trimmed.startsWith("#")) continue;
111
+
112
+ // Skip patterns with special gitignore features we can't safely convert:
113
+ // - Root-anchored (starts with /)
114
+ // - Contains globs (*, ?, [)
115
+ // - Contains path separators (complex paths)
116
+ // - Escaped characters
117
+ if (
118
+ trimmed.startsWith("/") ||
119
+ trimmed.includes("*") ||
120
+ trimmed.includes("?") ||
121
+ trimmed.includes("[") ||
122
+ trimmed.includes("/") ||
123
+ trimmed.includes("\\")
124
+ ) {
125
+ continue;
126
+ }
127
+
128
+ // Only convert simple directory names (e.g., "node_modules", "dist")
129
+ // These are safe to prune at any depth
130
+ const name = trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
131
+ if (name && /^[\w.-]+$/.test(name)) {
132
+ patterns.push(`**/${name}/**`);
133
+ }
134
+ }
135
+
136
+ return patterns;
137
+ }
138
+
139
+ interface GitignoreResult {
140
+ ignore: Ignore;
141
+ globPatterns: string[];
142
+ }
143
+
144
+ /**
145
+ * Load and parse .gitignore file from a directory.
146
+ * Returns both an Ignore instance for filtering and glob patterns for fast-glob.
147
+ */
148
+ async function loadGitignore(cwd: string): Promise<GitignoreResult> {
92
149
  const ig = ignore();
93
150
  const gitignorePath = join(cwd, ".gitignore");
151
+ let globPatterns: string[] = [];
94
152
 
95
153
  try {
96
154
  const content = await readFile(gitignorePath, "utf-8");
97
155
  ig.add(content);
156
+ globPatterns = gitignoreToGlobPatterns(content.split("\n"));
98
157
  } catch {
99
158
  // No .gitignore file, return empty ignore instance
100
159
  }
101
160
 
102
- return ig;
161
+ return { ignore: ig, globPatterns };
103
162
  }
104
163
 
105
164
  /**
@@ -114,12 +173,18 @@ export async function resolvePatterns(
114
173
  ): Promise<string[]> {
115
174
  const { include, exclude, force } = normalizePatterns(config);
116
175
  const excludeMatchers = exclude.map((p) => picomatch(p));
117
- const gitignore = await loadGitignore(cwd);
176
+ const { ignore: gitignore, globPatterns } = await loadGitignore(cwd);
118
177
  const files = new Set<string>();
119
178
 
120
179
  // Regular includes: respect .gitignore
180
+ // Pass gitignore patterns to fast-glob to skip ignored directories during traversal
121
181
  if (include.length > 0) {
122
- const matches = await glob(include, { cwd, onlyFiles: true, dot: true });
182
+ const matches = await glob(include, {
183
+ cwd,
184
+ onlyFiles: true,
185
+ dot: true,
186
+ ignore: globPatterns,
187
+ });
123
188
  for (const match of matches) {
124
189
  if (!isExcluded(match, excludeMatchers) && !gitignore.ignores(match)) {
125
190
  const fullPath = join(cwd, match);
@@ -130,7 +195,7 @@ export async function resolvePatterns(
130
195
  }
131
196
  }
132
197
 
133
- // Force includes: bypass .gitignore
198
+ // Force includes: bypass .gitignore (no ignore patterns passed to glob)
134
199
  if (force.length > 0) {
135
200
  const matches = await glob(force, { cwd, onlyFiles: true, dot: true });
136
201
  for (const match of matches) {