srcpack 0.1.3 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "srcpack",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Zero-config CLI for bundling code into LLM-optimized context files",
5
5
  "keywords": [
6
6
  "llm",
@@ -31,6 +31,10 @@
31
31
  "license": "MIT",
32
32
  "homepage": "https://kriasoft.com/srcpack/",
33
33
  "repository": "github:kriasoft/srcpack",
34
+ "bugs": "https://github.com/kriasoft/srcpack/issues",
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
34
38
  "type": "module",
35
39
  "types": "./dist/index.d.ts",
36
40
  "exports": {
@@ -48,8 +52,7 @@
48
52
  "schema.json"
49
53
  ],
50
54
  "scripts": {
51
- "build": "bun build ./src/cli.ts ./src/index.ts --outdir ./dist --target bun && tsc",
52
- "typecheck": "tsc -p tsconfig.check.json",
55
+ "build": "bun build ./src/cli.ts ./src/index.ts --outdir ./dist --target node && tsc",
53
56
  "check": "tsc -p tsconfig.check.json",
54
57
  "test": "bun test tests/unit/ tests/e2e/",
55
58
  "test:unit": "bun test tests/unit/",
@@ -67,17 +70,21 @@
67
70
  "@clack/prompts": "^0.11.0",
68
71
  "@googleapis/drive": "^20.0.0",
69
72
  "cosmiconfig": "^9.0.0",
73
+ "fast-glob": "^3.3.3",
70
74
  "google-auth-library": "^10.5.0",
71
75
  "ignore": "^7.0.5",
72
76
  "oauth-callback": "^1.2.5",
73
77
  "ora": "^9.0.0",
78
+ "picomatch": "^4.0.2",
74
79
  "zod": "^4.3.5"
75
80
  },
76
81
  "devDependencies": {
77
82
  "@types/bun": "^1.3.6",
83
+ "@types/picomatch": "^4.0.2",
78
84
  "gh-pages": "^6.3.0",
79
85
  "prettier": "^3.8.0",
80
86
  "typescript": "^5.9.3",
81
- "vitepress": "^2.0.0-alpha.15"
87
+ "vitepress": "^2.0.0-alpha.15",
88
+ "vitepress-plugin-llms": "^1.10.0"
82
89
  }
83
90
  }
package/src/bundle.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
+ import { open, readFile, stat } from "node:fs/promises";
3
4
  import { join } from "node:path";
4
- import { Glob } from "bun";
5
+ import { glob } from "fast-glob";
6
+ import picomatch from "picomatch";
5
7
  import ignore, { type Ignore } from "ignore";
6
8
  import type { BundleConfigInput } from "./config.ts";
7
9
 
@@ -9,12 +11,17 @@ import type { BundleConfigInput } from "./config.ts";
9
11
  const BINARY_CHECK_SIZE = 8192;
10
12
 
11
13
  async function isBinary(filePath: string): Promise<boolean> {
12
- const file = Bun.file(filePath);
13
- const size = file.size;
14
- if (size === 0) return false;
14
+ const stats = await stat(filePath);
15
+ if (stats.size === 0) return false;
15
16
 
16
- const chunk = await file.slice(0, Math.min(size, BINARY_CHECK_SIZE)).bytes();
17
- return chunk.includes(0);
17
+ const fd = await open(filePath, "r");
18
+ try {
19
+ const buffer = Buffer.alloc(Math.min(stats.size, BINARY_CHECK_SIZE));
20
+ await fd.read(buffer, 0, buffer.length, 0);
21
+ return buffer.includes(0);
22
+ } finally {
23
+ await fd.close();
24
+ }
18
25
  }
19
26
 
20
27
  export interface FileEntry {
@@ -30,11 +37,15 @@ export interface BundleResult {
30
37
  }
31
38
 
32
39
  /**
33
- * Normalize BundleConfig to arrays of include/exclude patterns
40
+ * Normalize BundleConfig to arrays of include/exclude/force patterns.
41
+ * - Regular patterns: included, filtered by .gitignore
42
+ * - `!pattern`: excluded from results
43
+ * - `+pattern`: force-included, bypasses .gitignore
34
44
  */
35
45
  function normalizePatterns(config: BundleConfigInput): {
36
46
  include: string[];
37
47
  exclude: string[];
48
+ force: string[];
38
49
  } {
39
50
  let patterns: string[];
40
51
 
@@ -50,29 +61,28 @@ function normalizePatterns(config: BundleConfigInput): {
50
61
 
51
62
  const include: string[] = [];
52
63
  const exclude: string[] = [];
64
+ const force: string[] = [];
53
65
 
54
66
  for (const p of patterns) {
55
67
  if (p.startsWith("!")) {
56
68
  exclude.push(p.slice(1));
69
+ } else if (p.startsWith("+")) {
70
+ force.push(p.slice(1));
57
71
  } else {
58
72
  include.push(p);
59
73
  }
60
74
  }
61
75
 
62
- return { include, exclude };
76
+ return { include, exclude, force };
63
77
  }
64
78
 
79
+ type Matcher = (path: string) => boolean;
80
+
65
81
  /**
66
- * Check if a path matches any of the exclusion patterns
82
+ * Check if a path matches any of the exclusion matchers
67
83
  */
68
- function isExcluded(filePath: string, excludePatterns: string[]): boolean {
69
- for (const pattern of excludePatterns) {
70
- const glob = new Glob(pattern);
71
- if (glob.match(filePath)) {
72
- return true;
73
- }
74
- }
75
- return false;
84
+ function isExcluded(filePath: string, matchers: Matcher[]): boolean {
85
+ return matchers.some((match) => match(filePath));
76
86
  }
77
87
 
78
88
  /**
@@ -83,7 +93,7 @@ async function loadGitignore(cwd: string): Promise<Ignore> {
83
93
  const gitignorePath = join(cwd, ".gitignore");
84
94
 
85
95
  try {
86
- const content = await Bun.file(gitignorePath).text();
96
+ const content = await readFile(gitignorePath, "utf-8");
87
97
  ig.add(content);
88
98
  } catch {
89
99
  // No .gitignore file, return empty ignore instance
@@ -94,20 +104,37 @@ async function loadGitignore(cwd: string): Promise<Ignore> {
94
104
 
95
105
  /**
96
106
  * Resolve bundle config to a list of file paths.
97
- * Respects .gitignore patterns in the working directory.
107
+ * - Regular patterns respect .gitignore
108
+ * - Force patterns (+prefix) bypass .gitignore
109
+ * - Exclude patterns (!prefix) filter both
98
110
  */
99
111
  export async function resolvePatterns(
100
112
  config: BundleConfigInput,
101
113
  cwd: string,
102
114
  ): Promise<string[]> {
103
- const { include, exclude } = normalizePatterns(config);
115
+ const { include, exclude, force } = normalizePatterns(config);
116
+ const excludeMatchers = exclude.map((p) => picomatch(p));
104
117
  const gitignore = await loadGitignore(cwd);
105
118
  const files = new Set<string>();
106
119
 
107
- for (const pattern of include) {
108
- const glob = new Glob(pattern);
109
- for await (const match of glob.scan({ cwd, onlyFiles: true })) {
110
- if (!isExcluded(match, exclude) && !gitignore.ignores(match)) {
120
+ // Regular includes: respect .gitignore
121
+ if (include.length > 0) {
122
+ const matches = await glob(include, { cwd, onlyFiles: true, dot: true });
123
+ for (const match of matches) {
124
+ if (!isExcluded(match, excludeMatchers) && !gitignore.ignores(match)) {
125
+ const fullPath = join(cwd, match);
126
+ if (!(await isBinary(fullPath))) {
127
+ files.add(match);
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Force includes: bypass .gitignore
134
+ if (force.length > 0) {
135
+ const matches = await glob(force, { cwd, onlyFiles: true, dot: true });
136
+ for (const match of matches) {
137
+ if (!isExcluded(match, excludeMatchers)) {
111
138
  const fullPath = join(cwd, match);
112
139
  if (!(await isBinary(fullPath))) {
113
140
  files.add(match);
@@ -184,7 +211,7 @@ export async function createBundle(
184
211
  for (let i = 0; i < files.length; i++) {
185
212
  const filePath = files[i]!;
186
213
  const fullPath = join(cwd, filePath);
187
- const content = await Bun.file(fullPath).text();
214
+ const content = await readFile(fullPath, "utf-8");
188
215
  const lines = countLines(content);
189
216
 
190
217
  // Separator takes 1 line, then content starts on next line
package/src/cli.ts CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- import { mkdir } from "node:fs/promises";
4
+ import { mkdir, writeFile } from "node:fs/promises";
5
5
  import { dirname, join } from "node:path";
6
6
  import ora from "ora";
7
7
  import { bundleOne, type BundleResult } from "./bundle.ts";
@@ -154,7 +154,7 @@ Options:
154
154
  }
155
155
  } else {
156
156
  await mkdir(dirname(outPath), { recursive: true });
157
- await Bun.write(outPath, result.content);
157
+ await writeFile(outPath, result.content);
158
158
  console.log(
159
159
  ` ${nameCol} ${filesCol} ${plural(fileCount, "file")} ${linesCol} ${plural(lineCount, "line")} → ${outfile}`,
160
160
  );
package/src/gdrive.ts CHANGED
@@ -13,7 +13,12 @@ const GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
13
13
  const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
14
14
  const SCOPES = ["https://www.googleapis.com/auth/drive.file"];
15
15
  const REDIRECT_URI = "http://localhost:3000/callback";
16
- const CREDENTIALS_PATH = join(homedir(), ".config", "srcpack", "credentials.json");
16
+ const CREDENTIALS_PATH = join(
17
+ homedir(),
18
+ ".config",
19
+ "srcpack",
20
+ "credentials.json",
21
+ );
17
22
 
18
23
  export interface Tokens {
19
24
  access_token: string;
package/src/init.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
 
3
3
  import * as p from "@clack/prompts";
4
- import { appendFile, readFile } from "node:fs/promises";
4
+ import { appendFile, readFile, writeFile } from "node:fs/promises";
5
5
  import { existsSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
 
@@ -70,7 +70,7 @@ export async function runInit(): Promise<void> {
70
70
 
71
71
  // Generate and write config
72
72
  const config = generateConfig(bundles, outDirValue);
73
- await Bun.write(configPath, config);
73
+ await writeFile(configPath, config);
74
74
 
75
75
  // Add output directory to .gitignore
76
76
  await addToGitignore(cwd, outDirValue);