docs-cache 0.5.4 → 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,
@@ -178,7 +154,48 @@ const ensureCommitAvailable = async (repoPath, commit, options) => {
178
154
  });
179
155
  };
180
156
  const patternHasGlob = (pattern) => pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
181
- const normalizeSparsePatterns = (include) => (include ?? []).map((pattern) => pattern.replace(/\\/g, "/")).filter(Boolean);
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;
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));
196
+ }
197
+ return expanded;
198
+ };
182
199
  const isDirectoryLiteral = (pattern) => pattern.endsWith("/");
183
200
  const toNoConePattern = (pattern) => {
184
201
  if (!patternHasGlob(pattern) && isDirectoryLiteral(pattern)) {
@@ -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.4",
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
+ }