docs-cache 0.5.3 → 0.5.5

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
@@ -100,10 +100,12 @@ These fields can be set in `defaults` and are inherited by every source unless o
100
100
  | `maxBytes` | Maximum total bytes to materialize. Default: `200000000` (200 MB). |
101
101
  | `maxFiles` | Maximum total files to materialize. |
102
102
  | `ignoreHidden` | Skip hidden files and directories (dotfiles). Default: `false`. |
103
- | `allowHosts` | Allowed Git hosts. Default: `["github.com", "gitlab.com", "visualstudio.com"]`. |
103
+ | `allowHosts` | Allowed Git hosts. Default: `["github.com", "gitlab.com", "visualstudio.com"]`. |
104
104
  | `toc` | Generate per-source `TOC.md`. Default: `true`. Supports `true`, `false`, or a format: `"tree"` (human readable), `"compressed"` |
105
105
  | `unwrapSingleRootDir` | If the materialized output is nested under a single directory, unwrap it (recursively). Default: `true`. |
106
106
 
107
+ > Brace expansion in `include` supports comma-separated lists (including multiple groups) like `**/*.{md,mdx}` and is capped at 500 expanded patterns per include entry. It does not support nested braces or numeric ranges.
108
+
107
109
  ### Source options
108
110
 
109
111
  #### Required
@@ -7,36 +7,12 @@ import { execa } from "execa";
7
7
  import { getErrnoCode } from "#core/errors";
8
8
  import { assertSafeSourceId } from "#core/source-id";
9
9
  import { exists, resolveGitCacheDir } from "#git/cache-dir";
10
+ import { buildGitEnv, resolveGitCommand } from "#git/git-env";
10
11
  const DEFAULT_TIMEOUT_MS = 12e4;
11
12
  const DEFAULT_GIT_DEPTH = 1;
12
13
  const DEFAULT_RM_RETRIES = 3;
13
14
  const DEFAULT_RM_BACKOFF_MS = 100;
14
- const buildGitEnv = () => {
15
- const pathValue = process.env.PATH ?? process.env.Path;
16
- const pathExtValue = process.env.PATHEXT ?? (process.platform === "win32" ? ".COM;.EXE;.BAT;.CMD" : void 0);
17
- return {
18
- ...process.env,
19
- ...pathValue ? { PATH: pathValue, Path: pathValue } : {},
20
- ...pathExtValue ? { PATHEXT: pathExtValue } : {},
21
- HOME: process.env.HOME,
22
- USER: process.env.USER,
23
- USERPROFILE: process.env.USERPROFILE,
24
- TMPDIR: process.env.TMPDIR,
25
- TMP: process.env.TMP,
26
- TEMP: process.env.TEMP,
27
- SYSTEMROOT: process.env.SYSTEMROOT,
28
- WINDIR: process.env.WINDIR,
29
- SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK,
30
- SSH_AGENT_PID: process.env.SSH_AGENT_PID,
31
- HTTP_PROXY: process.env.HTTP_PROXY,
32
- HTTPS_PROXY: process.env.HTTPS_PROXY,
33
- NO_PROXY: process.env.NO_PROXY,
34
- GIT_TERMINAL_PROMPT: "0",
35
- GIT_CONFIG_NOSYSTEM: "1",
36
- GIT_CONFIG_NOGLOBAL: "1",
37
- ...process.platform === "win32" ? {} : { GIT_ASKPASS: "/bin/false" }
38
- };
39
- };
15
+ const MAX_BRACE_EXPANSIONS = 500;
40
16
  const buildGitConfigs = (allowFileProtocol) => [
41
17
  "-c",
42
18
  "core.hooksPath=/dev/null",
@@ -91,7 +67,7 @@ const git = async (args, options) => {
91
67
  );
92
68
  const commandLabel = `git ${commandArgs.join(" ")}`;
93
69
  options?.logger?.(commandLabel);
94
- const subprocess = execa("git", commandArgs, {
70
+ const subprocess = execa(resolveGitCommand(), commandArgs, {
95
71
  cwd: options?.cwd,
96
72
  timeout: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
97
73
  maxBuffer: 10 * 1024 * 1024,
@@ -177,35 +153,95 @@ const ensureCommitAvailable = async (repoPath, commit, options) => {
177
153
  logger: options?.logger
178
154
  });
179
155
  };
180
- const isSparseEligible = (include) => {
181
- if (!include || include.length === 0) {
182
- return false;
183
- }
184
- for (const pattern of include) {
185
- if (!pattern || pattern.includes("**")) {
186
- return false;
156
+ const patternHasGlob = (pattern) => pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
157
+ const expandBracePattern = (pattern) => {
158
+ const results = [];
159
+ const expand = (value) => {
160
+ const braceMatch = value.match(/^(.*?){([^}]+)}(.*)$/);
161
+ if (!braceMatch) {
162
+ if (results.length >= MAX_BRACE_EXPANSIONS) {
163
+ throw new Error(
164
+ `Brace expansion exceeded ${MAX_BRACE_EXPANSIONS} patterns for '${pattern}'.`
165
+ );
166
+ }
167
+ results.push(value);
168
+ return;
169
+ }
170
+ const [, prefix, values, suffix] = braceMatch;
171
+ const valueList = values.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
172
+ if (valueList.length === 0) {
173
+ if (results.length >= MAX_BRACE_EXPANSIONS) {
174
+ throw new Error(
175
+ `Brace expansion exceeded ${MAX_BRACE_EXPANSIONS} patterns for '${pattern}'.`
176
+ );
177
+ }
178
+ results.push(value);
179
+ return;
187
180
  }
181
+ for (const entry of valueList) {
182
+ const expandedPattern = `${prefix}${entry}${suffix}`;
183
+ expand(expandedPattern);
184
+ }
185
+ };
186
+ expand(pattern);
187
+ return results;
188
+ };
189
+ const normalizeSparsePatterns = (include) => {
190
+ const patterns = include ?? [];
191
+ const expanded = [];
192
+ for (const pattern of patterns) {
193
+ const normalized = pattern.replace(/\\/g, "/");
194
+ if (!normalized) continue;
195
+ expanded.push(...expandBracePattern(normalized));
188
196
  }
189
- return true;
197
+ return expanded;
190
198
  };
191
- const extractSparsePaths = (include) => {
192
- if (!include) {
193
- return [];
199
+ const isDirectoryLiteral = (pattern) => pattern.endsWith("/");
200
+ const toNoConePattern = (pattern) => {
201
+ if (!patternHasGlob(pattern) && isDirectoryLiteral(pattern)) {
202
+ return pattern.endsWith("/") ? pattern : `${pattern}/`;
194
203
  }
195
- const paths = include.map((pattern) => {
196
- const normalized = pattern.replace(/\\/g, "/");
197
- const starIndex = normalized.indexOf("*");
198
- const base = starIndex === -1 ? normalized : normalized.slice(0, starIndex);
199
- return base.replace(/\/+$|\/$/, "");
200
- });
201
- return Array.from(new Set(paths.filter((value) => value.length > 0)));
204
+ return pattern;
205
+ };
206
+ const resolveSparseSpec = (include) => {
207
+ const normalized = normalizeSparsePatterns(include);
208
+ if (normalized.length === 0) {
209
+ return { enabled: false, mode: "cone", patterns: [] };
210
+ }
211
+ const conePaths = [];
212
+ let coneEligible = true;
213
+ for (const pattern of normalized) {
214
+ if (pattern.includes("**")) {
215
+ coneEligible = false;
216
+ break;
217
+ }
218
+ if (patternHasGlob(pattern)) {
219
+ coneEligible = false;
220
+ break;
221
+ }
222
+ if (isDirectoryLiteral(pattern)) {
223
+ conePaths.push(pattern.replace(/\/+$/, ""));
224
+ continue;
225
+ }
226
+ coneEligible = false;
227
+ break;
228
+ }
229
+ const uniquePaths = Array.from(new Set(conePaths.filter(Boolean)));
230
+ if (coneEligible && uniquePaths.length > 0) {
231
+ return { enabled: true, mode: "cone", patterns: uniquePaths };
232
+ }
233
+ return {
234
+ enabled: true,
235
+ mode: "no-cone",
236
+ patterns: normalized.map(toNoConePattern)
237
+ };
202
238
  };
203
239
  const cloneRepo = async (params, outDir) => {
204
240
  if (params.offline) {
205
241
  throw new Error(`Cannot clone ${params.repo} while offline.`);
206
242
  }
207
243
  const isCommitRef = /^[0-9a-f]{7,40}$/i.test(params.ref);
208
- const useSparse = isSparseEligible(params.include);
244
+ const sparseSpec = resolveSparseSpec(params.include);
209
245
  const buildCloneArgs = () => {
210
246
  const cloneArgs2 = [
211
247
  "clone",
@@ -218,7 +254,7 @@ const cloneRepo = async (params, outDir) => {
218
254
  return cloneArgs2;
219
255
  };
220
256
  const cloneArgs = buildCloneArgs();
221
- if (useSparse) {
257
+ if (sparseSpec.enabled) {
222
258
  cloneArgs.push("--sparse");
223
259
  }
224
260
  if (!isCommitRef) {
@@ -239,14 +275,16 @@ const cloneRepo = async (params, outDir) => {
239
275
  logger: params.logger,
240
276
  offline: params.offline
241
277
  });
242
- if (useSparse) {
243
- const sparsePaths = extractSparsePaths(params.include);
244
- if (sparsePaths.length > 0) {
245
- await git(["-C", outDir, "sparse-checkout", "set", ...sparsePaths], {
246
- timeoutMs: params.timeoutMs,
247
- logger: params.logger
248
- });
278
+ if (sparseSpec.enabled) {
279
+ const sparseArgs = ["-C", outDir, "sparse-checkout", "set"];
280
+ if (sparseSpec.mode === "no-cone") {
281
+ sparseArgs.push("--no-cone");
249
282
  }
283
+ sparseArgs.push(...sparseSpec.patterns);
284
+ await git(sparseArgs, {
285
+ timeoutMs: params.timeoutMs,
286
+ logger: params.logger
287
+ });
250
288
  }
251
289
  await git(
252
290
  ["-C", outDir, "checkout", "--quiet", "--detach", params.resolvedCommit],
@@ -281,9 +319,14 @@ const addWorktreeFromCache = async (params, cachePath, outDir) => {
281
319
  allowFileProtocol: true
282
320
  }
283
321
  );
284
- const sparsePaths = isSparseEligible(params.include) ? extractSparsePaths(params.include) : [];
285
- if (sparsePaths.length > 0) {
286
- await git(["-C", outDir, "sparse-checkout", "set", ...sparsePaths], {
322
+ const sparseSpec = resolveSparseSpec(params.include);
323
+ if (sparseSpec.enabled) {
324
+ const sparseArgs = ["-C", outDir, "sparse-checkout", "set"];
325
+ if (sparseSpec.mode === "no-cone") {
326
+ sparseArgs.push("--no-cone");
327
+ }
328
+ sparseArgs.push(...sparseSpec.patterns);
329
+ await git(sparseArgs, {
287
330
  timeoutMs: params.timeoutMs,
288
331
  logger: params.logger,
289
332
  allowFileProtocol: true
@@ -380,7 +423,7 @@ const cloneOrUpdateRepo = async (params, outDir) => {
380
423
  const cacheExists = await exists(cachePath);
381
424
  const cacheValid = cacheExists && await isValidGitRepo(cachePath);
382
425
  const isCommitRef = /^[0-9a-f]{7,40}$/i.test(params.ref);
383
- const useSparse = isSparseEligible(params.include);
426
+ const sparseSpec = resolveSparseSpec(params.include);
384
427
  let usedCache = cacheValid;
385
428
  let worktreeUsed = false;
386
429
  const cacheRoot = resolveGitCacheDir();
@@ -410,7 +453,7 @@ const cloneOrUpdateRepo = async (params, outDir) => {
410
453
  if (await isPartialClone(cachePath)) {
411
454
  localCloneArgs.splice(2, 0, "--filter=blob:none");
412
455
  }
413
- if (useSparse) {
456
+ if (sparseSpec.enabled) {
414
457
  localCloneArgs.push("--sparse");
415
458
  }
416
459
  if (!isCommitRef) {
@@ -428,15 +471,17 @@ const cloneOrUpdateRepo = async (params, outDir) => {
428
471
  progressLogger: params.progressLogger,
429
472
  forceProgress: Boolean(params.progressLogger)
430
473
  });
431
- if (useSparse) {
432
- const sparsePaths = extractSparsePaths(params.include);
433
- if (sparsePaths.length > 0) {
434
- await git(["-C", outDir, "sparse-checkout", "set", ...sparsePaths], {
435
- timeoutMs: params.timeoutMs,
436
- allowFileProtocol: true,
437
- logger: params.logger
438
- });
474
+ if (sparseSpec.enabled) {
475
+ const sparseArgs = ["-C", outDir, "sparse-checkout", "set"];
476
+ if (sparseSpec.mode === "no-cone") {
477
+ sparseArgs.push("--no-cone");
439
478
  }
479
+ sparseArgs.push(...sparseSpec.patterns);
480
+ await git(sparseArgs, {
481
+ timeoutMs: params.timeoutMs,
482
+ allowFileProtocol: true,
483
+ logger: params.logger
484
+ });
440
485
  }
441
486
  await ensureCommitAvailable(outDir, params.resolvedCommit, {
442
487
  timeoutMs: params.timeoutMs,
@@ -0,0 +1,3 @@
1
+ declare const resolveGitCommand: () => string;
2
+ declare const buildGitEnv: () => NodeJS.ProcessEnv;
3
+ export { buildGitEnv, resolveGitCommand };
@@ -0,0 +1,33 @@
1
+ const resolveGitCommand = () => {
2
+ const override = process.env.DOCS_CACHE_GIT_COMMAND;
3
+ if (override) {
4
+ return override;
5
+ }
6
+ return "git";
7
+ };
8
+ const buildGitEnv = () => {
9
+ const pathValue = process.env.PATH ?? process.env.Path;
10
+ const pathExtValue = process.env.PATHEXT ?? (process.platform === "win32" ? ".COM;.EXE;.BAT;.CMD" : void 0);
11
+ return {
12
+ ...process.env,
13
+ ...pathValue ? { PATH: pathValue, Path: pathValue } : {},
14
+ ...pathExtValue ? { PATHEXT: pathExtValue } : {},
15
+ HOME: process.env.HOME,
16
+ USER: process.env.USER,
17
+ USERPROFILE: process.env.USERPROFILE,
18
+ TMPDIR: process.env.TMPDIR,
19
+ TMP: process.env.TMP,
20
+ TEMP: process.env.TEMP,
21
+ SYSTEMROOT: process.env.SYSTEMROOT,
22
+ WINDIR: process.env.WINDIR,
23
+ SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK,
24
+ SSH_AGENT_PID: process.env.SSH_AGENT_PID,
25
+ HTTP_PROXY: process.env.HTTP_PROXY,
26
+ HTTPS_PROXY: process.env.HTTPS_PROXY,
27
+ NO_PROXY: process.env.NO_PROXY,
28
+ GIT_TERMINAL_PROMPT: "0",
29
+ GIT_CONFIG_NOSYSTEM: "1",
30
+ ...process.platform === "win32" ? {} : { GIT_ASKPASS: "/bin/false" }
31
+ };
32
+ };
33
+ export { buildGitEnv, resolveGitCommand };
@@ -1,7 +1,6 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
1
+ import { execa } from "execa";
2
+ import { buildGitEnv, resolveGitCommand } from "#git/git-env";
3
3
  import { redactRepoUrl } from "#git/redact";
4
- const execFileAsync = promisify(execFile);
5
4
  const DEFAULT_TIMEOUT_MS = 3e4;
6
5
  const BLOCKED_PROTOCOLS = /* @__PURE__ */ new Set(["file:", "ftp:", "data:", "javascript:"]);
7
6
  const assertAllowedProtocol = (repo) => {
@@ -65,12 +64,13 @@ export const resolveRemoteCommit = async (params) => {
65
64
  enforceHostAllowlist(params.repo, params.allowHosts);
66
65
  const repoLabel = redactRepoUrl(params.repo);
67
66
  params.logger?.(`git ls-remote ${repoLabel} ${params.ref}`);
68
- const { stdout } = await execFileAsync(
69
- "git",
67
+ const { stdout } = await execa(
68
+ resolveGitCommand(),
70
69
  ["ls-remote", params.repo, params.ref],
71
70
  {
72
71
  timeout: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
73
- maxBuffer: 1024 * 1024
72
+ maxBuffer: 1024 * 1024,
73
+ env: buildGitEnv()
74
74
  }
75
75
  );
76
76
  const resolvedCommit = parseLsRemote(stdout);
package/package.json CHANGED
@@ -1,139 +1,136 @@
1
1
  {
2
- "name": "docs-cache",
3
- "private": false,
4
- "type": "module",
5
- "version": "0.5.3",
6
- "packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
7
- "description": "CLI for deterministic local caching of external documentation for agents and tools",
8
- "author": "Frederik Bosch",
9
- "license": "MIT",
10
- "homepage": "https://github.com/fbosch/docs-cache#readme",
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/fbosch/docs-cache.git"
14
- },
15
- "bugs": {
16
- "url": "https://github.com/fbosch/docs-cache/issues"
17
- },
18
- "keywords": [
19
- "docs",
20
- "documentation",
21
- "cache",
22
- "agent",
23
- "ai",
24
- "git",
25
- "cli"
26
- ],
27
- "sideEffects": false,
28
- "engines": {
29
- "node": ">=18"
30
- },
31
- "bin": {
32
- "docs-cache": "./bin/docs-cache.mjs"
33
- },
34
- "files": [
35
- "bin",
36
- "dist/cli.mjs",
37
- "dist/esm/**/*.mjs",
38
- "dist/esm/**/*.d.ts",
39
- "dist/lock.mjs",
40
- "dist/shared/*.mjs",
41
- "README.md",
42
- "LICENSE"
43
- ],
44
- "scripts": {
45
- "build": "unbuild",
46
- "dev": "unbuild --stub",
47
- "lint": "biome check .",
48
- "prepublishOnly": "pnpm audit --audit-level=high && pnpm build && pnpm size && pnpm schema:build",
49
- "release": "pnpm run lint && pnpm run typecheck && bumpp && pnpm publish --access public",
50
- "test": "pnpm build && node --test",
51
- "test:coverage": "pnpm build && c8 --include dist --exclude bin --reporter=text node --test",
52
- "bench": "pnpm build && node scripts/benchmarks/run.mjs",
53
- "complexity": "node scripts/complexity/run.mjs",
54
- "schema:build": "node scripts/generate-schema.mjs",
55
- "size": "size-limit",
56
- "test:watch": "node --test --watch",
57
- "typecheck": "tsc --noEmit",
58
- "prepare": "simple-git-hooks"
59
- },
60
- "imports": {
61
- "#cache/*": {
62
- "types": "./dist/esm/cache/*.d.ts",
63
- "default": "./dist/esm/cache/*.mjs"
64
- },
65
- "#cli/*": {
66
- "types": "./dist/esm/cli/*.d.ts",
67
- "default": "./dist/esm/cli/*.mjs"
68
- },
69
- "#commands/*": {
70
- "types": "./dist/esm/commands/*.d.ts",
71
- "default": "./dist/esm/commands/*.mjs"
72
- },
73
- "#core/*": {
74
- "types": "./dist/esm/*.d.ts",
75
- "default": "./dist/esm/*.mjs"
76
- },
77
- "#config": {
78
- "types": "./dist/esm/config/index.d.ts",
79
- "default": "./dist/esm/config/index.mjs"
80
- },
81
- "#config/*": {
82
- "types": "./dist/esm/config/*.d.ts",
83
- "default": "./dist/esm/config/*.mjs"
84
- },
85
- "#git/*": {
86
- "types": "./dist/esm/git/*.d.ts",
87
- "default": "./dist/esm/git/*.mjs"
88
- },
89
- "#types/*": {
90
- "types": "./dist/esm/types/*.d.ts",
91
- "default": "./dist/esm/types/*.mjs"
92
- }
93
- },
94
- "dependencies": {
95
- "@clack/prompts": "^1.0.0",
96
- "cac": "^6.7.14",
97
- "cli-truncate": "^4.0.0",
98
- "execa": "^9.6.1",
99
- "fast-glob": "^3.3.2",
100
- "log-update": "^7.0.2",
101
- "picocolors": "^1.1.1",
102
- "picomatch": "^4.0.3",
103
- "zod": "^4.3.6"
104
- },
105
- "devDependencies": {
106
- "@biomejs/biome": "^2.3.14",
107
- "@size-limit/file": "^12.0.0",
108
- "@types/node": "^25.2.0",
109
- "bumpp": "^10.3.2",
110
- "c8": "^10.1.3",
111
- "jiti": "^2.5.1",
112
- "lint-staged": "^16.2.7",
113
- "simple-git-hooks": "^2.13.1",
114
- "size-limit": "^12.0.0",
115
- "tinybench": "^6.0.0",
116
- "ts-complex": "^1.0.0",
117
- "typescript": "^5.9.3",
118
- "unbuild": "^3.6.1"
119
- },
120
- "size-limit": [
121
- {
122
- "path": "dist/cli.mjs",
123
- "limit": "10 kB"
124
- }
125
- ],
126
- "complexity": {
127
- "maxCyclomatic": 20,
128
- "minMaintainability": 60,
129
- "top": 10
130
- },
131
- "simple-git-hooks": {
132
- "pre-commit": "pnpm lint-staged && pnpm typecheck"
133
- },
134
- "lint-staged": {
135
- "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
136
- "biome check --write --no-errors-on-unmatched"
137
- ]
138
- }
139
- }
2
+ "name": "docs-cache",
3
+ "private": false,
4
+ "type": "module",
5
+ "version": "0.5.5",
6
+ "description": "CLI for deterministic local caching of external documentation for agents and tools",
7
+ "author": "Frederik Bosch",
8
+ "license": "MIT",
9
+ "homepage": "https://github.com/fbosch/docs-cache#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/fbosch/docs-cache.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/fbosch/docs-cache/issues"
16
+ },
17
+ "keywords": [
18
+ "docs",
19
+ "documentation",
20
+ "cache",
21
+ "agent",
22
+ "ai",
23
+ "git",
24
+ "cli"
25
+ ],
26
+ "sideEffects": false,
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "bin": {
31
+ "docs-cache": "./bin/docs-cache.mjs"
32
+ },
33
+ "files": [
34
+ "bin",
35
+ "dist/cli.mjs",
36
+ "dist/esm/**/*.mjs",
37
+ "dist/esm/**/*.d.ts",
38
+ "dist/lock.mjs",
39
+ "dist/shared/*.mjs",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "imports": {
44
+ "#cache/*": {
45
+ "types": "./dist/esm/cache/*.d.ts",
46
+ "default": "./dist/esm/cache/*.mjs"
47
+ },
48
+ "#cli/*": {
49
+ "types": "./dist/esm/cli/*.d.ts",
50
+ "default": "./dist/esm/cli/*.mjs"
51
+ },
52
+ "#commands/*": {
53
+ "types": "./dist/esm/commands/*.d.ts",
54
+ "default": "./dist/esm/commands/*.mjs"
55
+ },
56
+ "#core/*": {
57
+ "types": "./dist/esm/*.d.ts",
58
+ "default": "./dist/esm/*.mjs"
59
+ },
60
+ "#config": {
61
+ "types": "./dist/esm/config/index.d.ts",
62
+ "default": "./dist/esm/config/index.mjs"
63
+ },
64
+ "#config/*": {
65
+ "types": "./dist/esm/config/*.d.ts",
66
+ "default": "./dist/esm/config/*.mjs"
67
+ },
68
+ "#git/*": {
69
+ "types": "./dist/esm/git/*.d.ts",
70
+ "default": "./dist/esm/git/*.mjs"
71
+ },
72
+ "#types/*": {
73
+ "types": "./dist/esm/types/*.d.ts",
74
+ "default": "./dist/esm/types/*.mjs"
75
+ }
76
+ },
77
+ "dependencies": {
78
+ "@clack/prompts": "^1.0.0",
79
+ "cac": "^6.7.14",
80
+ "cli-truncate": "^4.0.0",
81
+ "execa": "^9.6.1",
82
+ "fast-glob": "^3.3.2",
83
+ "log-update": "^7.0.2",
84
+ "picocolors": "^1.1.1",
85
+ "picomatch": "^4.0.3",
86
+ "zod": "^4.3.6"
87
+ },
88
+ "devDependencies": {
89
+ "@biomejs/biome": "^2.3.14",
90
+ "@size-limit/file": "^12.0.0",
91
+ "@types/node": "^25.2.0",
92
+ "bumpp": "^10.3.2",
93
+ "c8": "^10.1.3",
94
+ "jiti": "^2.5.1",
95
+ "lint-staged": "^16.2.7",
96
+ "simple-git-hooks": "^2.13.1",
97
+ "size-limit": "^12.0.0",
98
+ "tinybench": "^6.0.0",
99
+ "ts-complex": "^1.0.0",
100
+ "typescript": "^5.9.3",
101
+ "unbuild": "^3.6.1"
102
+ },
103
+ "size-limit": [
104
+ {
105
+ "path": "dist/cli.mjs",
106
+ "limit": "10 kB"
107
+ }
108
+ ],
109
+ "complexity": {
110
+ "maxCyclomatic": 20,
111
+ "minMaintainability": 60,
112
+ "top": 10
113
+ },
114
+ "simple-git-hooks": {
115
+ "pre-commit": "pnpm lint-staged && pnpm typecheck"
116
+ },
117
+ "lint-staged": {
118
+ "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
119
+ "biome check --write --no-errors-on-unmatched"
120
+ ]
121
+ },
122
+ "scripts": {
123
+ "build": "unbuild",
124
+ "dev": "unbuild --stub",
125
+ "lint": "biome check .",
126
+ "release": "pnpm run lint && pnpm run typecheck && bumpp && pnpm publish --access public",
127
+ "test": "pnpm build && node --test",
128
+ "test:coverage": "pnpm build && c8 --include dist --exclude bin --reporter=text node --test",
129
+ "bench": "pnpm build && node scripts/benchmarks/run.mjs",
130
+ "complexity": "node scripts/complexity/run.mjs",
131
+ "schema:build": "node scripts/generate-schema.mjs",
132
+ "size": "size-limit",
133
+ "test:watch": "node --test --watch",
134
+ "typecheck": "tsc --noEmit"
135
+ }
136
+ }