docs-cache 0.3.1 → 0.4.1
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 +36 -42
- package/dist/chunks/add.mjs +1 -1
- package/dist/chunks/clean.mjs +1 -1
- package/dist/chunks/init.mjs +1 -1
- package/dist/chunks/prune.mjs +1 -1
- package/dist/chunks/remove.mjs +1 -1
- package/dist/chunks/status.mjs +1 -1
- package/dist/chunks/sync.mjs +8 -8
- package/dist/chunks/verify.mjs +1 -1
- package/dist/cli.mjs +13 -13
- package/dist/lock.mjs +1 -1
- package/dist/shared/docs-cache.B9Pjydwx.mjs +3 -0
- package/dist/shared/{docs-cache.C96VehGu.mjs → docs-cache.DH8jN6rl.mjs} +2 -2
- package/dist/shared/{docs-cache.DDgb7yxQ.mjs → docs-cache.Oi01HUbh.mjs} +3 -3
- package/package.json +3 -2
- package/dist/shared/docs-cache.BWEcxcrg.mjs +0 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🗃️ docs-cache
|
|
1
|
+
# 🗃️ `docs-cache`
|
|
2
2
|
|
|
3
|
-
Deterministic local caching of external documentation for agents and
|
|
3
|
+
Deterministic local caching of external documentation for agents and developers
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.npmjs.com/package/docs-cache)
|
|
@@ -8,14 +8,14 @@ Deterministic local caching of external documentation for agents and tools
|
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
10
10
|
|
|
11
|
-
Provides agents and
|
|
11
|
+
Provides agents and developers with local access to external documentation without committing it to the repository.
|
|
12
12
|
|
|
13
13
|
Documentation is cached in a gitignored location, exposed to agent and tool targets via links or copies, and updated through sync commands or postinstall hooks.
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- **Local only**: Cache lives in the directory `.docs` (or a custom location) and
|
|
18
|
-
- **Deterministic**: `docs.
|
|
17
|
+
- **Local only**: Cache lives in the directory `.docs` (or a custom location) and can be gitignored.
|
|
18
|
+
- **Deterministic**: `docs-lock.json` pins commits and file metadata.
|
|
19
19
|
- **Fast**: Local cache avoids network roundtrips after sync.
|
|
20
20
|
- **Flexible**: Cache full repos or just the subdirectories you need.
|
|
21
21
|
|
|
@@ -54,20 +54,21 @@ npx docs-cache clean
|
|
|
54
54
|
|
|
55
55
|
## Configuration
|
|
56
56
|
|
|
57
|
-
`docs.config.json` at project root (or `docs-cache`
|
|
57
|
+
`docs.config.json` at project root (or a `docs-cache` field in `package.json`):
|
|
58
58
|
|
|
59
|
-
```
|
|
59
|
+
```jsonc
|
|
60
60
|
{
|
|
61
61
|
"$schema": "https://github.com/fbosch/docs-cache/blob/master/docs.config.schema.json",
|
|
62
62
|
"sources": [
|
|
63
63
|
{
|
|
64
64
|
"id": "framework",
|
|
65
65
|
"repo": "https://github.com/framework/core.git",
|
|
66
|
-
"ref": "main",
|
|
67
|
-
"targetDir": "./agents/skills/framework-skill/references",
|
|
68
|
-
"include": ["guide/**"]
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
"ref": "main", // or specific commit hash
|
|
67
|
+
"targetDir": "./agents/skills/framework-skill/references", // symlink/copy target
|
|
68
|
+
"include": ["guide/**"], // file globs to include from the source
|
|
69
|
+
"toc": true, // defaults to "compressed" (for agents)
|
|
70
|
+
},
|
|
71
|
+
],
|
|
71
72
|
}
|
|
72
73
|
```
|
|
73
74
|
|
|
@@ -78,28 +79,29 @@ npx docs-cache clean
|
|
|
78
79
|
| Field | Details | Required |
|
|
79
80
|
| ---------- | -------------------------------------- | -------- |
|
|
80
81
|
| `cacheDir` | Directory for cache. Default: `.docs`. | Optional |
|
|
81
|
-
| `sources` | List of repositories to sync. | Required |
|
|
82
82
|
| `defaults` | Default settings for all sources. | Optional |
|
|
83
|
+
| `sources` | List of repositories to sync. | Required |
|
|
83
84
|
|
|
84
85
|
<details>
|
|
85
86
|
<summary>Show default and source options</summary>
|
|
86
87
|
|
|
87
88
|
### Default options
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
| Field
|
|
92
|
-
|
|
|
93
|
-
| `ref`
|
|
94
|
-
| `mode`
|
|
95
|
-
| `include`
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `required`
|
|
99
|
-
| `maxBytes`
|
|
100
|
-
| `maxFiles`
|
|
101
|
-
| `allowHosts`
|
|
102
|
-
| `toc`
|
|
90
|
+
These fields can be set in `defaults` and are inherited by every source unless overridden per-source.
|
|
91
|
+
|
|
92
|
+
| Field | Details |
|
|
93
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `ref` | Branch, tag, or commit. Default: `"HEAD"`. |
|
|
95
|
+
| `mode` | Cache mode. Default: `"materialize"`. |
|
|
96
|
+
| `include` | Glob patterns to copy. Default: `["**/*.{md,mdx,markdown,mkd,txt,rst,adoc,asciidoc}"]`. |
|
|
97
|
+
| `exclude` | Glob patterns to skip. Default: `[]`. |
|
|
98
|
+
| `targetMode` | How to link or copy from the cache to the destination. Default: `"symlink"` on Unix, `"copy"` on Windows. |
|
|
99
|
+
| `required` | Whether missing sources should fail. Default: `true`. |
|
|
100
|
+
| `maxBytes` | Maximum total bytes to materialize. Default: `200000000` (200 MB). |
|
|
101
|
+
| `maxFiles` | Maximum total files to materialize. |
|
|
102
|
+
| `allowHosts` | Allowed Git hosts. Default: `["github.com", "gitlab.com"]`. |
|
|
103
|
+
| `toc` | Generate per-source `TOC.md`. Default: `true`. Supports `true`, `false`, or a format: `"tree"` (human readable), `"compressed"` (optimized for agents). |
|
|
104
|
+
| `unwrapSingleRootDir` | If the materialized output is nested under a single directory, unwrap it (recursively). Default: `false`. |
|
|
103
105
|
|
|
104
106
|
### Source options
|
|
105
107
|
|
|
@@ -110,21 +112,13 @@ All fields in `defaults` apply to all sources unless overridden per-source.
|
|
|
110
112
|
| `repo` | Git URL. |
|
|
111
113
|
| `id` | Unique identifier for the source. |
|
|
112
114
|
|
|
113
|
-
#### Optional
|
|
114
|
-
|
|
115
|
-
| Field
|
|
116
|
-
|
|
|
117
|
-
| `
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| `targetDir` | Path where files should be symlinked/copied to, outside `.docs`. |
|
|
121
|
-
| `targetMode` | How to link or copy from the cache to the destination. |
|
|
122
|
-
| `required` | Whether missing sources should fail. |
|
|
123
|
-
| `maxBytes` | Maximum total bytes to materialize. |
|
|
124
|
-
| `maxFiles` | Maximum total files to materialize. |
|
|
125
|
-
| `toc` | Generate per-source `TOC.md`. Supports `true`, `false`, or a format (`"tree"`, `"compressed"`). |
|
|
126
|
-
|
|
127
|
-
> **Note**: Sources are always downloaded to `.docs/<id>/`. If you provide a `targetDir`, `docs-cache` will create a symlink or copy pointing from the cache to that target directory. The target should be outside `.docs`. Git operation timeout is configured via the `--timeout-ms` CLI flag, not as a per-source configuration option.
|
|
115
|
+
#### Optional (source-only)
|
|
116
|
+
|
|
117
|
+
| Field | Details |
|
|
118
|
+
| ----------- | ---------------------------------------------------------------- |
|
|
119
|
+
| `targetDir` | Path where files should be symlinked/copied to, outside `.docs`. |
|
|
120
|
+
|
|
121
|
+
> **Note**: Sources are always downloaded to `.docs/<id>/`. If you provide a `targetDir`; `docs-cache` will create a symlink or copy pointing from the cache to that target directory.
|
|
128
122
|
|
|
129
123
|
</details>
|
|
130
124
|
|
package/dist/chunks/add.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{readFile as D,writeFile as k,access as E}from"node:fs/promises";import l from"node:path";import{v,D as P,a as A,s as F,w as I,b as U,r as S}from"../shared/docs-cache.
|
|
1
|
+
import{readFile as D,writeFile as k,access as E}from"node:fs/promises";import l from"node:path";import{v,D as P,a as A,s as F,w as I,b as U,r as S}from"../shared/docs-cache.B9Pjydwx.mjs";import{e as N}from"../shared/docs-cache.DH8jN6rl.mjs";import{r as O}from"../shared/docs-cache.Oi01HUbh.mjs";import{r as x}from"../shared/docs-cache.BSvQNKuf.mjs";import"zod";import"node:process";import"cac";import"picocolors";const u=async o=>{try{return await E(o),!0}catch{return!1}},y="package.json",C=async o=>{const i=await D(o,"utf8"),e=JSON.parse(i),a=e["docs-cache"];return a?{parsed:e,config:v(a)}:{parsed:e,config:null}},J=async o=>{if(o){const a=S(o);return{resolvedPath:a,mode:l.basename(a)===y?"package":"config"}}const i=S();if(await u(i))return{resolvedPath:i,mode:"config"};const e=l.resolve(process.cwd(),y);return await u(e)&&(await C(e)).config?{resolvedPath:e,mode:"package"}:{resolvedPath:i,mode:"config"}},T=async o=>{const i=await J(o.configPath),e=i.resolvedPath;let a=P,t=null,g=null,f=!1;if(await u(e))if(i.mode==="package"){const r=await C(e);g=r.parsed,t=r.config,a=t??P,f=!!t}else{const r=await D(e,"utf8");t=JSON.parse(r.toString()),a=v(t),f=!0}const b="https://raw.githubusercontent.com/fbosch/docs-cache/main/docs.config.schema.json",p=new Set(a.sources.map(r=>r.id)),m=[],d=o.entries.map(r=>{const n=x(r.repo),w=r.id||n.inferredId;if(!w)throw new Error("Unable to infer id. Provide an explicit id.");const c=A(w,"source id");return p.has(c)?(m.push(c),null):(p.add(c),r.targetDir&&O(e,r.targetDir),{id:c,repo:n.repoUrl,...r.targetDir?{targetDir:r.targetDir}:{},...n.ref?{ref:n.ref}:{}})}).filter(Boolean);if(d.length===0)throw new Error("All sources already exist in config.");const s={$schema:b,sources:[...a.sources,...d]};if(t?.cacheDir&&(s.cacheDir=t.cacheDir),t?.defaults&&(s.defaults=t.defaults),i.mode==="package"){const r=g??{};r["docs-cache"]=F(s),await k(e,`${JSON.stringify(r,null,2)}
|
|
2
2
|
`,"utf8")}else await I(e,s);const h=f?null:await N(l.dirname(e),t?.cacheDir??U);return{configPath:e,sources:d,skipped:m,created:!0,gitignoreUpdated:h?.updated??!1,gitignorePath:h?.gitignorePath??null}};export{T as addSources};
|
|
3
3
|
//# sourceMappingURL=add.mjs.map
|
package/dist/chunks/clean.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{rm as o,access as i}from"node:fs/promises";import{l as s,b as m}from"../shared/docs-cache.
|
|
1
|
+
import{rm as o,access as i}from"node:fs/promises";import{l as s,b as m}from"../shared/docs-cache.B9Pjydwx.mjs";import{b as n}from"../shared/docs-cache.Oi01HUbh.mjs";import"node:path";import"zod";import"node:process";import"cac";import"picocolors";const f=async r=>{try{return await i(r),!0}catch{return!1}},p=async r=>{const{config:t,resolvedPath:c}=await s(r.configPath),a=n(c,t.cacheDir??m,r.cacheDirOverride),e=await f(a);return e&&await o(a,{recursive:!0,force:!0}),{cacheDir:a,removed:e}};export{p as cleanCache};
|
|
2
2
|
//# sourceMappingURL=clean.mjs.map
|
package/dist/chunks/init.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{readFile as C,writeFile as N,access as S}from"node:fs/promises";import c from"node:path";import{confirm as V,isCancel as G,select as J,text as U}from"@clack/prompts";import{c as _,b as r,s as L,w as T}from"../shared/docs-cache.
|
|
1
|
+
import{readFile as C,writeFile as N,access as S}from"node:fs/promises";import c from"node:path";import{confirm as V,isCancel as G,select as J,text as U}from"@clack/prompts";import{c as _,b as r,s as L,w as T}from"../shared/docs-cache.B9Pjydwx.mjs";import{g as H,e as k}from"../shared/docs-cache.DH8jN6rl.mjs";import"zod";import"../shared/docs-cache.Oi01HUbh.mjs";import"node:process";import"cac";import"picocolors";const h=async n=>{try{return await S(n),!0}catch{return!1}},M=async(n,s={})=>{const y=s.confirm??V,l=s.isCancel??G,x=s.select??J,F=s.text??U,f=n.cwd??process.cwd(),d=c.resolve(f,_),i=c.resolve(f,"package.json"),g=[];if(await h(d)&&g.push(d),await h(i)){const e=await C(i,"utf8");JSON.parse(e)["docs-cache"]&&g.push(i)}if(g.length>0)throw new Error(`Config already exists at ${g.join(", ")}. Init aborted.`);let b=!1;if(await h(i)){const e=await C(i,"utf8");if(!JSON.parse(e)["docs-cache"]){const o=await x({message:"Config location",options:[{value:"config",label:"docs.config.json"},{value:"package",label:"package.json"}],initialValue:"config"});if(l(o))throw new Error("Init cancelled.");b=o==="package"}}const $=b?i:d,v=n.cacheDirOverride??r,u=await F({message:"Cache directory",initialValue:v});if(l(u))throw new Error("Init cancelled.");const A=u||r,D=await y({message:"Generate TOC.md (table of contents with links to all documentation)",initialValue:!0});if(l(D))throw new Error("Init cancelled.");const I=await H(f,A);let P=!1;if(I.entry&&!I.hasEntry){const e=await y({message:"Add cache directory to .gitignore",initialValue:!0});if(l(e))throw new Error("Init cancelled.");P=e}const a={configPath:$,cacheDir:u,toc:D,gitignore:P},t=c.resolve(f,a.configPath);if(c.basename(t)==="package.json"){const e=await C(t,"utf8"),o=JSON.parse(e);if(o["docs-cache"])throw new Error(`docs-cache config already exists in ${t}.`);const p={$schema:"https://raw.githubusercontent.com/fbosch/docs-cache/main/docs.config.schema.json",sources:[]},E=a.cacheDir||r;E!==r&&(p.cacheDir=E),a.toc||(p.defaults={toc:!1}),o["docs-cache"]=L(p),await N(t,`${JSON.stringify(o,null,2)}
|
|
2
2
|
`,"utf8");const O=a.gitignore?await k(c.dirname(t),E):null;return{configPath:t,created:!0,gitignoreUpdated:O?.updated??!1,gitignorePath:O?.gitignorePath??null}}if(await h(t))throw new Error(`Config already exists at ${t}.`);const w={$schema:"https://raw.githubusercontent.com/fbosch/docs-cache/main/docs.config.schema.json",sources:[]},m=a.cacheDir||r;m!==r&&(w.cacheDir=m),a.toc||(w.defaults={toc:!1}),await T(t,w);const j=a.gitignore?await k(c.dirname(t),m):null;return{configPath:t,created:!0,gitignoreUpdated:j?.updated??!1,gitignorePath:j?.gitignorePath??null}};export{M as initConfig};
|
|
3
3
|
//# sourceMappingURL=init.mjs.map
|
package/dist/chunks/prune.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readdir as p,rm as f,access as h}from"node:fs/promises";import u from"node:path";import{l as d,b as D}from"../shared/docs-cache.
|
|
1
|
+
import{readdir as p,rm as f,access as h}from"node:fs/promises";import u from"node:path";import{l as d,b as D}from"../shared/docs-cache.B9Pjydwx.mjs";import{b as v}from"../shared/docs-cache.Oi01HUbh.mjs";import"zod";import"node:process";import"cac";import"picocolors";const w=async t=>{try{return await h(t),!0}catch{return!1}},l=async t=>{const{config:c,resolvedPath:s,sources:a}=await d(t.configPath),e=v(s,c.cacheDir??D,t.cacheDirOverride);if(!await w(e))return{cacheDir:e,removed:[],kept:a.map(r=>r.id)};const n=new Set(a.map(r=>r.id)),m=await p(e,{withFileTypes:!0}),o=[];for(const r of m){if(!r.isDirectory())continue;const i=r.name;n.has(i)||i.startsWith(".tmp-")||(await f(u.join(e,i),{recursive:!0,force:!0}),o.push(i))}return{cacheDir:e,removed:o,kept:a.map(r=>r.id)}};export{l as pruneCache};
|
|
2
2
|
//# sourceMappingURL=prune.mjs.map
|
package/dist/chunks/remove.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{readFile as w,writeFile as N,rm as $,access as k}from"node:fs/promises";import v from"node:path";import{v as D,D as E,s as F,w as I,r as P}from"../shared/docs-cache.
|
|
1
|
+
import{readFile as w,writeFile as N,rm as $,access as k}from"node:fs/promises";import v from"node:path";import{v as D,D as E,s as F,w as I,r as P}from"../shared/docs-cache.B9Pjydwx.mjs";import{r as O}from"../shared/docs-cache.Oi01HUbh.mjs";import{r as U}from"../shared/docs-cache.BSvQNKuf.mjs";import"zod";import"node:process";import"cac";import"picocolors";const f=async a=>{try{return await k(a),!0}catch{return!1}},y="package.json",S=async a=>{const s=await w(a,"utf8"),o=JSON.parse(s),r=o["docs-cache"];return r?{parsed:o,config:D(r)}:{parsed:o,config:null}},b=async a=>{if(a){const r=P(a);return{resolvedPath:r,mode:v.basename(r)===y?"package":"config"}}const s=P();if(await f(s))return{resolvedPath:s,mode:"config"};const o=v.resolve(process.cwd(),y);return await f(o)&&(await S(o)).config?{resolvedPath:o,mode:"package"}:{resolvedPath:s,mode:"config"}},J=async a=>{if(a.ids.length===0)throw new Error("No sources specified to remove.");const s=await b(a.configPath),o=s.resolvedPath;let r=E,t=null,d=null;if(await f(o))if(s.mode==="package"){const e=await S(o);if(d=e.parsed,t=e.config,!t)throw new Error(`Missing docs-cache config in ${o}.`);r=t}else{const e=await w(o,"utf8");t=JSON.parse(e.toString()),r=D(t)}else throw new Error(`Config not found at ${o}.`);const u=new Map(r.sources.map(e=>[e.id,e])),g=new Map(r.sources.map(e=>[e.repo,e])),n=new Set,l=[];for(const e of a.ids){if(u.has(e)){n.add(e);continue}const i=U(e);if(i.repoUrl&&g.has(i.repoUrl)){const p=g.get(i.repoUrl);p&&n.add(p.id);continue}if(i.inferredId&&u.has(i.inferredId)){n.add(i.inferredId);continue}l.push(e)}const C=r.sources.filter(e=>!n.has(e.id)),h=r.sources.filter(e=>n.has(e.id)).map(e=>e.id),M=r.sources.filter(e=>n.has(e.id));if(h.length===0)throw new Error("No matching sources found to remove.");const c={$schema:t?.$schema??"https://raw.githubusercontent.com/fbosch/docs-cache/main/docs.config.schema.json",sources:C};if(t?.cacheDir&&(c.cacheDir=t.cacheDir),t?.defaults&&(c.defaults=t.defaults),t?.targetMode&&(c.targetMode=t.targetMode),s.mode==="package"){const e=d??{};e["docs-cache"]=F(c),await N(o,`${JSON.stringify(e,null,2)}
|
|
2
2
|
`,"utf8")}else await I(o,c);const m=[];for(const e of M){if(!e.targetDir)continue;const i=O(o,e.targetDir);await $(i,{recursive:!0,force:!0}),m.push({id:e.id,targetDir:i})}return{configPath:o,removed:h,missing:l,targetsRemoved:m}};export{J as removeSources};
|
|
3
3
|
//# sourceMappingURL=remove.mjs.map
|
package/dist/chunks/status.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{access as
|
|
1
|
+
import{access as y}from"node:fs/promises";import a from"picocolors";import{u as o,a as u,b as D,g as w}from"../shared/docs-cache.Oi01HUbh.mjs";import{l as C,b as L}from"../shared/docs-cache.B9Pjydwx.mjs";import{DEFAULT_LOCK_FILENAME as v,resolveLockPath as x,readLock as P}from"../lock.mjs";import"node:process";import"cac";import"node:path";import"zod";const h=async r=>{try{return await y(r),!0}catch{return!1}},A=async r=>{const{config:c,resolvedPath:t,sources:n}=await C(r.configPath),s=D(t,c.cacheDir??L,r.cacheDirOverride),l=await h(s),e=x(t),i=await h(e);let d=!1,f=null;if(i)try{f=await P(e),d=!0}catch{d=!1}const E=await Promise.all(n.map(async m=>{const p=w(s,m.id),g=await h(p.sourceDir),k=f?.sources?.[m.id]??null;return{id:m.id,docsPath:p.sourceDir,docsExists:g,lockEntry:k}}));return{configPath:t,cacheDir:s,cacheDirExists:l,lockPath:e,lockExists:i,lockValid:d,sources:E}},_=r=>{const c=o.path(r.cacheDir),t=r.cacheDirExists?a.green("present"):a.red("missing"),n=r.lockExists?r.lockValid?a.green("valid"):a.red("invalid"):a.yellow("missing");if(o.header("Cache",`${c} (${t})`),o.header("Lock",`${v} (${n})`),r.sources.length===0){o.line(),o.line(`${u.warn} No sources configured.`);return}o.line();for(const s of r.sources){const l=s.docsExists?u.success:u.error,e=s.lockEntry?a.green("locked"):a.yellow("new"),i=o.hash(s.lockEntry?.resolvedCommit);o.item(l,s.id.padEnd(20),`${e.padEnd(10)} ${i}`)}};export{A as getStatus,_ as printStatus};
|
|
2
2
|
//# sourceMappingURL=status.mjs.map
|
package/dist/chunks/sync.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{createHash as
|
|
2
|
-
`).filter(Boolean);return r.length===0?null:r[0].split(/\s+/)[0]||null},
|
|
3
|
-
|
|
4
|
-
`)}throw
|
|
5
|
-
`;o.update(
|
|
6
|
-
`)}await r.cp(
|
|
7
|
-
`)},
|
|
8
|
-
`))if(o.trim()){const
|
|
1
|
+
import{createHash as H,randomBytes as Me}from"node:crypto";import{mkdtemp as Y,rm as R,mkdir as A,readFile as j,writeFile as te,access as L,rename as X,open as W,lstat as Pe,symlink as xe,readdir as Ee,cp as Te}from"node:fs/promises";import h from"node:path";import T from"picocolors";import{g as Oe,t as J,D as re,r as q,u as P,a as O,b as ke}from"../shared/docs-cache.Oi01HUbh.mjs";import{a as K,l as Fe,D as Re,b as be}from"../shared/docs-cache.B9Pjydwx.mjs";import{execFile as oe}from"node:child_process";import Ae,{tmpdir as ie}from"node:os";import{pathToFileURL as je}from"node:url";import{promisify as se}from"node:util";import{execa as Ie}from"execa";import{g as N,M as B,v as ae}from"./verify.mjs";import{e as _e,r as ne}from"../shared/docs-cache.kK1DPQIQ.mjs";import{writeLock as He,resolveLockPath as Le,readLock as Ne}from"../lock.mjs";import{createWriteStream as ce,createReadStream as Be,constants as le}from"node:fs";import{pipeline as ze}from"node:stream/promises";import ue from"fast-glob";const Ue=/^(https?:\/\/)([^@]+)@/i,I=e=>e.replace(Ue,"$1***@"),Ge=se(oe),Ye=3e4,Xe=new Set(["file:","ftp:","data:","javascript:"]),We=e=>{try{const r=new URL(e);if(Xe.has(r.protocol))throw new Error(`Blocked protocol '${r.protocol}' in repo URL '${I(e)}'.`)}catch(r){if(r instanceof TypeError)return;throw r}},Je=e=>{if(We(e),e.startsWith("git@")){const r=e.indexOf("@"),t=e.indexOf(":",r+1);return t===-1?null:e.slice(r+1,t)||null}try{const r=new URL(e);return r.protocol!=="https:"&&r.protocol!=="ssh:"?null:r.hostname||null}catch{return null}},fe=(e,r)=>{const t=Je(e);if(!t)throw new Error(`Unsupported repo URL '${I(e)}'. Use HTTPS or SSH.`);const i=t.toLowerCase();if(!r.map(o=>o.toLowerCase()).includes(i))throw new Error(`Host '${t}' is not in allowHosts for '${I(e)}'.`)},me=e=>{const r=e.trim().split(`
|
|
2
|
+
`).filter(Boolean);return r.length===0?null:r[0].split(/\s+/)[0]||null},qe=async e=>{fe(e.repo,e.allowHosts);const{stdout:r}=await Ge("git",["ls-remote",e.repo,e.ref],{timeout:e.timeoutMs??Ye,maxBuffer:1024*1024}),t=me(r);if(!t)throw new Error(`Unable to resolve ref '${e.ref}' for ${I(e.repo)}.`);return{repo:e.repo,ref:e.ref,resolvedCommit:t}},Ke=se(oe),de=12e4,z=1,Ve=3,Qe=100,x=async(e,r)=>{const t=process.env.PATH??process.env.Path,i=process.env.PATHEXT??(process.platform==="win32"?".COM;.EXE;.BAT;.CMD":void 0),o=["-c","core.hooksPath=/dev/null","-c","submodule.recurse=false","-c","protocol.ext.allow=never"];r?.allowFileProtocol?o.push("-c","protocol.file.allow=always"):o.push("-c","protocol.file.allow=never"),await Ie("git",[...o,...e],{cwd:r?.cwd,timeout:r?.timeoutMs??de,maxBuffer:10*1024*1024,env:{...process.env,...t?{PATH:t,Path:t}:{},...i?{PATHEXT:i}:{},HOME:process.env.HOME,USER:process.env.USER,USERPROFILE:process.env.USERPROFILE,TMPDIR:process.env.TMPDIR,TMP:process.env.TMP,TEMP:process.env.TEMP,SYSTEMROOT:process.env.SYSTEMROOT,WINDIR:process.env.WINDIR,SSH_AUTH_SOCK:process.env.SSH_AUTH_SOCK,SSH_AGENT_PID:process.env.SSH_AGENT_PID,HTTP_PROXY:process.env.HTTP_PROXY,HTTPS_PROXY:process.env.HTTPS_PROXY,NO_PROXY:process.env.NO_PROXY,GIT_TERMINAL_PROMPT:"0",GIT_CONFIG_NOSYSTEM:"1",GIT_CONFIG_NOGLOBAL:"1",...process.platform==="win32"?{}:{GIT_ASKPASS:"/bin/false"}}})},b=async(e,r=Ve)=>{for(let t=0;t<=r;t+=1)try{await R(e,{recursive:!0,force:!0});return}catch(i){const o=N(i);if(o!=="ENOTEMPTY"&&o!=="EBUSY"&&o!=="EPERM"||t===r)throw i;await new Promise(n=>setTimeout(n,Qe*(t+1)))}},Ze=e=>H("sha256").update(e).digest("hex").substring(0,16),et=e=>{const r=Ze(e);return h.join(ne(),r)},tt=async e=>{try{return await x(["rev-parse","--git-dir"],{cwd:e}),!0}catch{return!1}},he=async e=>{try{const r=h.join(e,".git","config"),t=(await j(r,"utf8")).toLowerCase();return t.includes("partialclone")||t.includes("promisor")||t.includes("partialclonefilter")}catch{return!1}},V=async(e,r,t)=>{try{await x(["-C",e,"cat-file","-e",`${r}^{commit}`],{timeoutMs:t?.timeoutMs,allowFileProtocol:t?.allowFileProtocol});return}catch{}await x(["-C",e,"fetch","origin",r],{timeoutMs:t?.timeoutMs,allowFileProtocol:t?.allowFileProtocol})},rt=async(e,r,t,i)=>{const o=h.join(t,"archive.tar");await x(["archive","--remote",e,"--format=tar","--output",o,r],{timeoutMs:i}),await Ke("tar",["-xf",o,"-C",t],{timeout:i??de,maxBuffer:1024*1024}),await R(o,{force:!0})},pe=e=>{if(!e||e.length===0)return!1;for(const r of e)if(!r||r.includes("**"))return!1;return!0},we=e=>{if(!e)return[];const r=e.map(t=>{const i=t.replace(/\\/g,"/"),o=i.indexOf("*");return(o===-1?i:i.slice(0,o)).replace(/\/+$|\/$/,"")});return Array.from(new Set(r.filter(t=>t.length>0)))},Q=async(e,r)=>{const t=/^[0-9a-f]{7,40}$/i.test(e.ref),i=pe(e.include),o=["clone","--no-checkout","--depth",String(z),"--recurse-submodules=no","--no-tags"];if(i&&o.push("--sparse"),t||(o.push("--single-branch"),e.ref!=="HEAD"&&o.push("--branch",e.ref)),o.push(e.repo,r),await x(o,{timeoutMs:e.timeoutMs}),await V(r,e.resolvedCommit,{timeoutMs:e.timeoutMs}),i){const n=we(e.include);n.length>0&&await x(["-C",r,"sparse-checkout","set",...n],{timeoutMs:e.timeoutMs})}await x(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs})},ot=async(e,r)=>{const t=et(e.repo),i=await _e(t),o=/^[0-9a-f]{7,40}$/i.test(e.ref),n=pe(e.include),s=ne();if(await A(s,{recursive:!0}),i&&await tt(t))if(await he(t))await b(t),await Q(e,t);else try{const f=["fetch","origin"];if(o)f.push("--depth",String(z));else{const p=e.ref==="HEAD"?"HEAD":`${e.ref}:refs/remotes/origin/${e.ref}`;f.push(p,"--depth",String(z))}await x(["-C",t,...f],{timeoutMs:e.timeoutMs}),await V(t,e.resolvedCommit,{timeoutMs:e.timeoutMs})}catch{await b(t),await Q(e,t)}else i&&await b(t),await Q(e,t);await A(r,{recursive:!0});const a=["clone","--no-checkout","--depth",String(z),"--recurse-submodules=no","--no-tags"];await he(t)&&a.splice(2,0,"--filter=blob:none"),n&&a.push("--sparse"),o||(a.push("--single-branch"),e.ref!=="HEAD"&&a.push("--branch",e.ref));const u=je(t).href;if(a.push(u,r),await x(a,{timeoutMs:e.timeoutMs,allowFileProtocol:!0}),n){const f=we(e.include);f.length>0&&await x(["-C",r,"sparse-checkout","set",...f],{timeoutMs:e.timeoutMs,allowFileProtocol:!0})}await V(r,e.resolvedCommit,{timeoutMs:e.timeoutMs,allowFileProtocol:!0}),await x(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs,allowFileProtocol:!0})},it=async e=>{const r=await Y(h.join(ie(),`docs-cache-${e.sourceId}-`));try{return await rt(e.repo,e.resolvedCommit,r,e.timeoutMs),r}catch(t){throw await b(r),t}},st=async e=>{K(e.sourceId,"sourceId");try{const r=await it(e);return{repoDir:r,cleanup:async()=>{await b(r)},fromCache:!1}}catch{const r=await Y(h.join(ie(),`docs-cache-${e.sourceId}-`));try{return await ot(e,r),{repoDir:r,cleanup:async()=>{await b(r)},fromCache:!0}}catch(t){throw await b(r),t}}},U=e=>J(e),Z=Number(process.env.DOCS_CACHE_STREAM_THRESHOLD_MB??"2"),at=Number.isFinite(Z)&&Z>0?Math.floor(Z*1024*1024):1024*1024,nt=(e,r)=>{const t=h.resolve(e);if(!h.resolve(r).startsWith(t+h.sep))throw new Error(`Path traversal detected: ${r}`)},ge=async e=>{try{return await W(e,le.O_RDONLY|le.O_NOFOLLOW)}catch(r){const t=N(r);if(t==="ELOOP")return null;if(t==="EINVAL"||t==="ENOSYS"||t==="ENOTSUP")return(await Pe(e)).isSymbolicLink()?null:await W(e,"r");throw r}},ct=(e,r)=>{if(!r||e.length===0)return null;let t="";for(;;){let i=null;for(const n of e){const s=(t?n.normalized.slice(t.length):n.normalized).split("/");if(s.length<2)return t||null;const a=s[0];if(!i){i=a;continue}if(i!==a)return t||null}if(!i)return t||null;const o=`${t}${i}/`;if(o===t)return t||null;t=o}},lt=e=>({...e,exclude:e.exclude??[],unwrapSingleRootDir:e.unwrapSingleRootDir??!1}),ut=async(e,r=5e3)=>{const t=Date.now();for(;Date.now()-t<r;)try{const i=await W(e,"wx");return{release:async()=>{await i.close(),await R(e,{force:!0})}}}catch(i){if(N(i)!=="EEXIST")throw i;await new Promise(o=>setTimeout(o,100))}throw new Error(`Failed to acquire lock for ${e}.`)},ft=async e=>{const r=lt(e);K(r.sourceId,"sourceId");const t=Oe(r.cacheDir,r.sourceId);await A(r.cacheDir,{recursive:!0});const i=await Y(h.join(r.cacheDir,`.tmp-${r.sourceId}-`));let o=null;const n=async()=>{const s=o;!s||s.closed||s.destroyed||await new Promise(a=>{const u=()=>{s.off("close",f),s.off("error",p),a()},f=()=>u(),p=()=>u();s.once("close",f),s.once("error",p);try{s.end()}catch{u()}})};try{const s=(await ue(r.include,{cwd:r.repoDir,ignore:[".git/**",...r.exclude],dot:!0,onlyFiles:!0,followSymbolicLinks:!1})).map(l=>({relativePath:l,normalized:U(l)})).sort((l,m)=>l.normalized.localeCompare(m.normalized)),a=ct(s,r.unwrapSingleRootDir),u=new Set;for(const{normalized:l}of s){const m=a?l.slice(a.length):l;u.add(h.posix.dirname(m))}await Promise.all(Array.from(u,l=>A(h.join(i,l),{recursive:!0})));let f=0,p=0;const S=Math.max(1,Math.min(s.length,Math.max(8,Math.min(128,Ae.cpus().length*8)))),$=h.join(i,B),d=ce($,{encoding:"utf8"});o=d;const w=H("sha256"),y=async l=>new Promise((m,g)=>{const c=M=>{d.off("drain",D),g(M)},D=()=>{d.off("error",c),m()};d.once("error",c),d.write(l)?(d.off("error",c),m()):d.once("drain",D)});for(let l=0;l<s.length;l+=S){const m=s.slice(l,l+S),g=await Promise.all(m.map(async c=>{const D=h.join(r.repoDir,c.relativePath),M=await ge(D);if(!M)return null;try{const k=await M.stat();if(!k.isFile())return null;const F=a?c.normalized.slice(a.length):c.normalized,E=h.join(i,F);if(nt(i,E),k.size>=at){const G=Be(D,{fd:M.fd,autoClose:!1}),Ce=ce(E);await ze(G,Ce)}else{const G=await M.readFile();await te(E,G)}return{path:a?c.normalized.slice(a.length):c.normalized,size:k.size}}finally{await M.close()}}));for(const c of g){if(!c)continue;if(r.maxFiles!==void 0&&p+1>r.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${r.maxFiles}).`);if(f+=c.size,f>r.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${r.maxBytes}).`);const D=`${JSON.stringify(c)}
|
|
3
|
+
`;w.update(D),await y(D),p+=1}}await new Promise((l,m)=>{d.end(()=>l()),d.once("error",m)});const v=w.digest("hex"),C=async l=>{try{return await L(l),!0}catch{return!1}};return await(async(l,m)=>{const g=await ut(`${m}.lock`);try{const c=await C(m),D=`${m}.bak-${Me(8).toString("hex")}`;c&&await X(m,D);try{await X(l,m)}catch(M){if(c)try{await X(D,m)}catch(k){const F=k instanceof Error?k.message:String(k);process.stderr.write(`Warning: Failed to restore backup: ${F}
|
|
4
|
+
`)}throw M}c&&await R(D,{recursive:!0,force:!0})}finally{await g.release()}})(i,t.sourceDir),{bytes:f,fileCount:p,manifestSha256:v}}catch(s){try{await n()}catch{}throw await R(i,{recursive:!0,force:!0}),s}},mt=async e=>{K(e.sourceId,"sourceId");const r=await ue(e.include,{cwd:e.repoDir,ignore:[".git/**",...e.exclude??[]],dot:!0,onlyFiles:!0,followSymbolicLinks:!1});r.sort((n,s)=>U(n).localeCompare(U(s)));let t=0,i=0;const o=H("sha256");for(const n of r){const s=U(n),a=h.join(e.repoDir,n),u=await ge(a);if(u)try{const f=await u.stat();if(!f.isFile())continue;if(e.maxFiles!==void 0&&i+1>e.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${e.maxFiles}).`);if(t+=f.size,t>e.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${e.maxBytes}).`);const p=`${JSON.stringify({path:s,size:f.size})}
|
|
5
|
+
`;o.update(p),i+=1}finally{await u.close()}}return{bytes:t,fileCount:i,manifestSha256:o.digest("hex")}},dt=async(e,r)=>{await r.rm(e,{recursive:!0,force:!0})},ht=async(e,r)=>{if(!e.unwrapSingleRootDir)return e.sourceDir;const t=await r.readdir(e.sourceDir,{withFileTypes:!0}),i=new Set([B,re]),o=t.filter(a=>!(a.isFile()&&i.has(a.name))),n=o.filter(a=>a.isDirectory()),s=o.filter(a=>a.isFile());return n.length!==1||s.length>0?e.sourceDir:h.join(e.sourceDir,n[0].name)},ee=async e=>{const r=e.deps??{cp:Te,mkdir:A,readdir:Ee,rm:R,symlink:xe,stderr:process.stderr},t=await ht(e,r),i=h.dirname(e.targetDir);await r.mkdir(i,{recursive:!0}),await dt(e.targetDir,r);const o=process.platform==="win32"?"copy":"symlink";if((e.mode??o)==="copy"){await r.cp(t,e.targetDir,{recursive:!0});return}const n=process.platform==="win32"?"junction":"dir";try{await r.symlink(t,e.targetDir,n)}catch(s){const a=N(s);if(a&&new Set(["EPERM","EACCES","ENOTSUP","EINVAL"]).has(a)){if(e.explicitTargetMode){const u=s instanceof Error?s.message:String(s);r.stderr.write(`Warning: Failed to create symlink at ${e.targetDir}. Falling back to copy. ${u}
|
|
6
|
+
`)}await r.cp(t,e.targetDir,{recursive:!0});return}throw s}},pt=e=>{const r={dirs:new Map,files:[]};for(const t of e){const i=t.split("/").filter(Boolean);if(i.length===0)continue;let o=r;for(const s of i.slice(0,-1)){let a=o.dirs.get(s);a||(a={dirs:new Map,files:[]},o.dirs.set(s,a)),o=a}const n=i[i.length-1];o.files.push({name:n,path:t})}return r},ye=(e,r,t)=>{const i=" ".repeat(r),o=Array.from(e.dirs.keys()).sort(),n=[...e.files].sort((s,a)=>s.name.localeCompare(a.name));for(const s of o){t.push(`${i}- ${s}/`);const a=e.dirs.get(s);a&&ye(a,r+1,t)}for(const s of n)t.push(`${i}- [${s.name}](./${s.path})`)},wt=(e,r,t)=>{const i=[...e].sort((a,u)=>a.localeCompare(u)),o=new Map;for(const a of i){const u=a.lastIndexOf("/"),f=u===-1?"":a.substring(0,u),p=u===-1?a:a.substring(u+1),S=o.get(f);S?S.push(p):o.set(f,[p])}const n=Array.from(o.keys()).sort(),s=[];s.push(`[${t}]`);for(const a of n){const u=o.get(a);if(!u)continue;const f=u.join(",");a===""?s.push(`root:{${f}}`):s.push(`${a}:{${f}}`)}r.push(s.join("|"))},gt=(e,r="compressed")=>{const t=[];if(r==="tree"){t.push(`# ${e.id} - Documentation`),t.push(""),t.push("## Files"),t.push("");const i=pt(e.files);ye(i,0,t)}else{const i=`${e.id} Docs Index`;wt(e.files,t,i)}return t.push(""),t.join(`
|
|
7
|
+
`)},yt=async e=>{const r=h.join(e,".manifest.jsonl");try{const t=await j(r,"utf8"),i=[];for(const o of t.split(`
|
|
8
|
+
`))if(o.trim()){const n=JSON.parse(o);n.path&&i.push(n.path)}return i}catch{return[]}},St=async e=>{const r=new Map(e.sources.map(i=>[i.id,i])),t=new Map((e.results??[]).map(i=>[i.id,i]));for(const[i,o]of Object.entries(e.lock.sources)){const n=r.get(i);n?.targetDir&&J(q(e.configPath,n.targetDir));const s=h.join(e.cacheDir,i);try{await L(s)}catch{continue}const a=await yt(s),u={id:i,repo:o.repo,ref:o.ref,resolvedCommit:o.resolvedCommit,fileCount:o.fileCount,cachePath:J(h.join(e.cacheDir,i)),files:a},f=n?.toc,p=f!==!1;let S="compressed";typeof f=="string"&&(S=f);const $=h.join(s,re);if(p){if(t.get(i)?.status==="up-to-date")try{await L($);continue}catch{}const d=gt(u,S);await te($,d,"utf8")}else try{await R($,{force:!0})}catch{}}},Dt=e=>{if(e<1024)return`${e} B`;const r=["KB","MB","GB","TB"];let t=e,i=-1;for(;t>=1024&&i<r.length-1;)t/=1024,i+=1;return`${t.toFixed(1)} ${r[i]}`},_=async e=>{try{return await L(e),!0}catch{return!1}},Se=async(e,r)=>{const t=h.join(e,r);return await _(t)?await _(h.join(t,B)):!1},vt=e=>{if(!e||e.length===0)return[];const r=e.map(t=>t.trim()).filter(t=>t.length>0);return Array.from(new Set(r)).sort()},$t=["mode","include","exclude","maxBytes","maxFiles","unwrapSingleRootDir"],Ct=(e,r)=>e==="include"&&Array.isArray(r)||e==="exclude"&&Array.isArray(r)?vt(r):r,Mt=e=>{const r=$t.map(o=>[o,Ct(o,e[o])]);r.sort(([o],[n])=>o.localeCompare(n));const t=Object.fromEntries(r),i=H("sha256");return i.update(JSON.stringify(t)),i.digest("hex")},De=async(e,r={})=>{const{config:t,resolvedPath:i,sources:o}=await Fe(e.configPath),n=t.defaults??Re.defaults,s=ke(i,t.cacheDir??be,e.cacheDirOverride),a=Le(i),u=await _(a);let f=null;u&&(f=await Ne(a));const p=r.resolveRemoteCommit??qe,S=e.sourceFilter?.length?o.filter(d=>e.sourceFilter?.includes(d.id)):o,$=await Promise.all(S.map(async d=>{const w=f?.sources?.[d.id],y=d.include??n.include,v=d.exclude??n.exclude,C=Mt({...d,include:y,exclude:v});if(e.offline){const c=await Se(s,d.id);return{id:d.id,repo:w?.repo??d.repo,ref:w?.ref??d.ref??n.ref,resolvedCommit:w?.resolvedCommit??"offline",lockCommit:w?.resolvedCommit??null,lockRulesSha256:w?.rulesSha256,status:w&&c?"up-to-date":"missing",bytes:w?.bytes,fileCount:w?.fileCount,manifestSha256:w?.manifestSha256,rulesSha256:C}}const l=await p({repo:d.repo,ref:d.ref,allowHosts:n.allowHosts,timeoutMs:e.timeoutMs}),m=w?.resolvedCommit===l.resolvedCommit&&w?.rulesSha256===C,g=w?m?"up-to-date":"changed":"missing";return{id:d.id,repo:l.repo,ref:l.ref,resolvedCommit:l.resolvedCommit,lockCommit:w?.resolvedCommit??null,lockRulesSha256:w?.rulesSha256,status:g,bytes:w?.bytes,fileCount:w?.fileCount,manifestSha256:w?.manifestSha256,rulesSha256:C}}));return{config:t,configPath:i,cacheDir:s,lockPath:a,lockExists:u,lockData:f,results:$,sources:S,defaults:n}},Pt=async()=>{const e=h.resolve(process.cwd(),"package.json");try{const r=await j(e,"utf8"),t=JSON.parse(r.toString());return typeof t.version=="string"?t.version:"0.0.0"}catch{}try{const r=await j(new URL("../package.json",import.meta.url),"utf8"),t=JSON.parse(r.toString());return typeof t.version=="string"?t.version:"0.0.0"}catch{}try{const r=await j(new URL("../../package.json",import.meta.url),"utf8"),t=JSON.parse(r.toString());return typeof t.version=="string"?t.version:"0.0.0"}catch{return"0.0.0"}},xt=async(e,r)=>{const t=await Pt(),i=new Date().toISOString(),o={...r?.sources??{}};for(const n of e.results){const s=o[n.id];o[n.id]={repo:n.repo,ref:n.ref,resolvedCommit:n.resolvedCommit,bytes:n.bytes??s?.bytes??0,fileCount:n.fileCount??s?.fileCount??0,manifestSha256:n.manifestSha256??s?.manifestSha256??n.resolvedCommit,rulesSha256:n.rulesSha256??s?.rulesSha256,updatedAt:i}}return{version:1,generatedAt:i,toolVersion:t,sources:o}},ve=async(e,r={})=>{const t=process.hrtime.bigint();let i=0;const o=await De(e,r);await A(o.cacheDir,{recursive:!0});const n=o.lockData,s=o.results.filter(u=>{const f=o.sources.find(p=>p.id===u.id);return u.status==="missing"&&(f?.required??!0)});if(e.failOnMiss&&s.length>0)throw new Error(`Missing required source(s): ${s.map(u=>u.id).join(", ")}.`);if(!e.lockOnly){const u=o.defaults,f=r.fetchSource??st,p=r.materializeSource??ft,S=new Map,$=async(y,v)=>{const C=y?.length?o.results.filter(l=>y.includes(l.id)):o.results;return(await Promise.all(C.map(async l=>{const m=o.sources.find(c=>c.id===l.id);if(!m)return null;if(v)return{result:l,source:m};let g=S.get(l.id);return g===void 0&&(g=await Se(o.cacheDir,l.id),S.set(l.id,g)),l.status!=="up-to-date"||!g?{result:l,source:m}:null}))).filter(Boolean)},d=async()=>{await Promise.all(o.sources.map(async y=>{if(!y.targetDir)return;const v=q(o.configPath,y.targetDir);await _(v)||await ee({sourceDir:h.join(o.cacheDir,y.id),targetDir:v,mode:y.targetMode??u.targetMode,explicitTargetMode:y.targetMode!==void 0,unwrapSingleRootDir:y.unwrapSingleRootDir})}))},w=async y=>{const v=e.concurrency??4;let C=0;const l=async()=>{const m=y[C];if(!m||!m.source)return;C+=1;const{result:g,source:c}=m,D=o.lockData?.sources?.[c.id],M=await f({sourceId:c.id,repo:c.repo,ref:c.ref,resolvedCommit:g.resolvedCommit,cacheDir:o.cacheDir,include:c.include??u.include,timeoutMs:e.timeoutMs});e.json||P.step(M.fromCache?"Restoring from cache":"Downloading repo",c.id);try{const k=h.join(o.cacheDir,c.id,B);if(g.status!=="up-to-date"&&D?.manifestSha256&&D?.rulesSha256===g.rulesSha256&&await _(k)){const E=await mt({sourceId:c.id,repoDir:M.repoDir,cacheDir:o.cacheDir,include:c.include??u.include,exclude:c.exclude,maxBytes:c.maxBytes??u.maxBytes,maxFiles:c.maxFiles??u.maxFiles});if(E.manifestSha256===D.manifestSha256){g.bytes=E.bytes,g.fileCount=E.fileCount,g.manifestSha256=E.manifestSha256,g.status="up-to-date",e.json||P.item(O.success,c.id,"no content changes"),await l();return}}e.json||P.step("Materializing",c.id);const F=await p({sourceId:c.id,repoDir:M.repoDir,cacheDir:o.cacheDir,include:c.include??u.include,exclude:c.exclude,maxBytes:c.maxBytes??u.maxBytes,maxFiles:c.maxFiles??u.maxFiles,unwrapSingleRootDir:c.unwrapSingleRootDir});if(c.targetDir){const E=q(o.configPath,c.targetDir);await ee({sourceDir:h.join(o.cacheDir,c.id),targetDir:E,mode:c.targetMode??u.targetMode,explicitTargetMode:c.targetMode!==void 0,unwrapSingleRootDir:c.unwrapSingleRootDir})}g.bytes=F.bytes,g.fileCount=F.fileCount,g.manifestSha256=F.manifestSha256,e.json||P.item(O.success,c.id,`synced ${F.fileCount} files`)}finally{await M.cleanup()}await l()};await Promise.all(Array.from({length:Math.min(v,y.length)},l))};if(e.offline)await d();else{const y=await $();await w(y),await d()}if(!e.offline){const y=(await ae({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(v=>!v.ok);if(y.length>0){const v=await $(y.map(l=>l.id),!0);v.length>0&&(await w(v),await d());const C=(await ae({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(l=>!l.ok);if(C.length>0&&(i+=1,!e.json)){const l=C.map(m=>`${m.id} (${m.issues.join("; ")})`).join(", ");P.line(`${O.warn} Verify failed for ${C.length} source(s): ${l}`)}}}}const a=await xt(o,n);if(await He(o.lockPath,a),!e.json){const u=Number(process.hrtime.bigint()-t)/1e6,f=o.results.reduce((S,$)=>S+($.bytes??0),0),p=o.results.reduce((S,$)=>S+($.fileCount??0),0);P.line(`${O.info} Completed in ${u.toFixed(0)}ms \xB7 ${Dt(f)} \xB7 ${p} files${i?` \xB7 ${i} warning${i===1?"":"s"}`:""}`)}return await St({cacheDir:o.cacheDir,configPath:o.configPath,lock:a,sources:o.sources,results:o.results}),o.lockExists=!0,o},$e=e=>{const r={upToDate:e.results.filter(t=>t.status==="up-to-date").length,changed:e.results.filter(t=>t.status==="changed").length,missing:e.results.filter(t=>t.status==="missing").length};if(e.results.length===0){P.line(`${O.info} No sources to sync.`);return}P.line(`${O.info} ${e.results.length} sources (${r.upToDate} up-to-date, ${r.changed} changed, ${r.missing} missing)`);for(const t of e.results){const i=P.hash(t.resolvedCommit),o=P.hash(t.lockCommit),n=!!t.lockRulesSha256&&!!t.rulesSha256&&t.lockRulesSha256!==t.rulesSha256;if(t.status==="up-to-date"){P.item(O.success,t.id,`${T.dim("up-to-date")} ${T.gray(i)}`);continue}if(t.status==="changed"){if(t.lockCommit===t.resolvedCommit&&n){P.item(O.warn,t.id,`${T.dim("rules changed")} ${T.gray(i)}`);continue}P.item(O.warn,t.id,`${T.dim("changed")} ${T.gray(o)} ${T.dim("->")} ${T.gray(i)}`);continue}P.item(O.warn,t.id,`${T.dim("missing")} ${T.gray(i)}`)}},Et={__proto__:null,getSyncPlan:De,printSyncPlan:$e,runSync:ve};export{ee as a,$e as b,ve as c,fe as e,me as p,I as r,Et as s};
|
|
9
9
|
//# sourceMappingURL=sync.mjs.map
|
package/dist/chunks/verify.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{stat as
|
|
1
|
+
import{stat as N,access as z}from"node:fs/promises";import h from"node:path";import{b as $,r as v,u as l,a as m}from"../shared/docs-cache.Oi01HUbh.mjs";import{l as M,b as j}from"../shared/docs-cache.B9Pjydwx.mjs";import{createReadStream as k}from"node:fs";import C from"node:readline";const T=t=>typeof t=="object"&&t!==null&&"code"in t&&(typeof t.code=="string"||typeof t.code=="number"||t.code===void 0),y=t=>T(t)&&typeof t.code=="string"?t.code:void 0,_=t=>{if(!t||typeof t!="object")throw new Error("Manifest entry must be an object.");const e=t;if(typeof e.path!="string"||e.path.length===0)throw new Error("Manifest entry path must be a non-empty string.");if(typeof e.size!="number"||Number.isNaN(e.size))throw new Error("Manifest entry size must be a number.");if(e.size<0)throw new Error("Manifest entry size must be zero or greater.");return{path:e.path,size:e.size}},w=".manifest.jsonl",I=async function*(t){const e=h.join(t,w),a=k(e,{encoding:"utf8"}),s=C.createInterface({input:a,crlfDelay:1/0});try{for await(const u of s){const f=u.trim();f&&(yield _(JSON.parse(f)))}}finally{s.close(),a.destroy()}},O=async t=>{try{return await z(t),!0}catch{return!1}},E=async t=>{const{config:e,resolvedPath:a,sources:s}=await M(t.configPath),u=$(a,e.cacheDir??j,t.cacheDirOverride),f=async(i,n)=>{if(!await O(i))return{ok:!1,issues:[n==="source"?"missing source directory":"missing target directory"]};try{let r=0,o=0;for await(const g of I(i)){const D=h.join(i,g.path);try{(await N(D)).size!==g.size&&(o+=1)}catch(p){const d=y(p);if(d==="ENOENT"||d==="ENOTDIR"){r+=1;continue}throw p}}const c=[];return r>0&&c.push(n==="source"?`missing files: ${r}`:`target missing files: ${r}`),o>0&&c.push(n==="source"?`size mismatch: ${o}`:`target size mismatch: ${o}`),{ok:c.length===0,issues:c}}catch(r){const o=y(r);if(o==="ENOENT"||o==="ENOTDIR")return{ok:!1,issues:[n==="source"?"missing manifest":"missing target manifest"]};throw r}},b=await Promise.all(s.map(async i=>{const n=h.join(u,i.id),r=[...(await f(n,"source")).issues];if(i.targetDir&&i.targetMode==="copy"){const o=v(a,i.targetDir),c=await f(o,"target");r.push(...c.issues)}return{id:i.id,ok:r.length===0,issues:r}}));return{cacheDir:u,results:b}},R=t=>{const e=t.results.filter(s=>s.ok).length,a=t.results.length-e;if(t.results.length===0){l.line(`${m.warn} No sources to verify.`);return}l.line(`${m.info} Verified ${t.results.length} sources (${e} ok, ${a} failed)`);for(const s of t.results)s.ok?l.item(m.success,s.id):l.item(m.warn,s.id,s.issues.join(", "))},A={__proto__:null,printVerify:R,verifyCache:E};export{w as M,A as a,y as g,E as v};
|
|
2
2
|
//# sourceMappingURL=verify.mjs.map
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import y from"node:path";import
|
|
2
|
-
Usage: ${
|
|
1
|
+
import y from"node:path";import a from"node:process";import g from"picocolors";import{p as O,s as D,E as u,a as c,u as o}from"./shared/docs-cache.Oi01HUbh.mjs";import"cac";const p="docs-cache",S=`
|
|
2
|
+
Usage: ${p} <command> [options]
|
|
3
3
|
|
|
4
4
|
Commands:
|
|
5
5
|
add Add sources to the config (supports github:org/repo#ref)
|
|
@@ -25,15 +25,15 @@ Global options:
|
|
|
25
25
|
--json
|
|
26
26
|
--timeout-ms <n>
|
|
27
27
|
--silent
|
|
28
|
-
`,
|
|
29
|
-
`)},
|
|
30
|
-
`);else{for(const
|
|
31
|
-
`);else{if(
|
|
32
|
-
`):i
|
|
33
|
-
`):
|
|
34
|
-
`);else if(
|
|
35
|
-
`):
|
|
36
|
-
`):
|
|
37
|
-
`):
|
|
38
|
-
`):(
|
|
28
|
+
`,m=()=>{a.stdout.write(S.trimStart())},$=r=>{a.stderr.write(`${c.error} ${r}
|
|
29
|
+
`)},P=r=>{const h=r.findIndex(t=>!t.startsWith("-")),l=h===-1?[]:r.slice(h+1),e=[];let f=-1;const s=new Set(["--config","--cache-dir","--concurrency","--timeout-ms"]);for(let t=0;t<l.length;t+=1){const i=l[t];if(i==="--source"){const n=l[t+1];if(!n||n.startsWith("-"))throw new Error("--source expects a value.");e.push({repo:n}),f=e.length-1,t+=1;continue}if(i==="--target"||i==="--target-dir"){const n=l[t+1];if(!n||n.startsWith("-"))throw new Error("--target expects a value.");if(f===-1)throw new Error("--target must follow a --source entry.");e[f].targetDir=n,t+=1;continue}if(s.has(i)){t+=1;continue}i.startsWith("--")||(e.push({repo:i}),f=e.length-1)}return e},C=async(r,h)=>{const l=r.command,e=r.options,f=r.args;if(l==="add"){const{addSources:s}=await import("./chunks/add.mjs"),{runSync:t}=await import("./chunks/sync.mjs").then(function(d){return d.s}),i=P(h);if(i.length===0)throw new Error("Usage: docs-cache add [--source <repo> --target <dir>] <repo...>");const n=await s({configPath:e.config,entries:i});if(e.offline?e.json||o.line(`${c.warn} Offline: skipped sync`):await t({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,sourceFilter:n.sources.map(d=>d.id),timeoutMs:e.timeoutMs}),e.json)a.stdout.write(`${JSON.stringify(n,null,2)}
|
|
30
|
+
`);else{for(const d of n.sources){const v=d.repo.replace(/^https?:\/\//,"").replace(/\.git$/,""),j=d.targetDir?` ${g.dim("->")} ${g.magenta(d.targetDir)}`:"";o.item(c.success,d.id,`${g.blue(v)}${j}`)}n.skipped?.length&&o.line(`${c.warn} Skipped ${n.skipped.length} existing source${n.skipped.length===1?"":"s"}: ${n.skipped.join(", ")}`),o.line(`${c.info} Updated ${g.gray(y.relative(a.cwd(),n.configPath)||"docs.config.json")}`),n.gitignoreUpdated&&n.gitignorePath&&o.line(`${c.info} Updated ${g.gray(o.path(n.gitignorePath))}`)}return}if(l==="remove"){const{removeSources:s}=await import("./chunks/remove.mjs"),{pruneCache:t}=await import("./chunks/prune.mjs");if(f.length===0)throw new Error("Usage: docs-cache remove <id...>");const i=await s({configPath:e.config,ids:f});if(e.json)a.stdout.write(`${JSON.stringify(i,null,2)}
|
|
31
|
+
`);else{if(i.removed.length>0&&o.line(`${c.success} Removed ${i.removed.length} source${i.removed.length===1?"":"s"}: ${i.removed.join(", ")}`),i.missing.length>0&&o.line(`${c.warn} Missing ${i.missing.length} source${i.missing.length===1?"":"s"}: ${i.missing.join(", ")}`),i.targetsRemoved.length>0){const n=i.targetsRemoved.map(d=>`${d.id} -> ${o.path(d.targetDir)}`).join(", ");o.line(`${c.success} Removed ${i.targetsRemoved.length} target${i.targetsRemoved.length===1?"":"s"}: ${n}`)}o.line(`${c.info} Updated ${g.gray(y.relative(a.cwd(),i.configPath)||"docs.config.json")}`)}e.prune&&await t({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});return}if(l==="status"){const{getStatus:s,printStatus:t}=await import("./chunks/status.mjs"),i=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?a.stdout.write(`${JSON.stringify(i,null,2)}
|
|
32
|
+
`):t(i);return}if(l==="clean"){const{cleanCache:s}=await import("./chunks/clean.mjs"),t=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?a.stdout.write(`${JSON.stringify(t,null,2)}
|
|
33
|
+
`):t.removed?o.line(`${c.success} Removed cache at ${o.path(t.cacheDir)}`):o.line(`${c.info} Cache already missing at ${o.path(t.cacheDir)}`);return}if(l==="clean-cache"){const{cleanGitCache:s}=await import("./chunks/clean-git-cache.mjs"),t=await s();if(e.json)a.stdout.write(`${JSON.stringify(t,null,2)}
|
|
34
|
+
`);else if(t.removed){const i=t.bytesFreed!==void 0?`${(t.bytesFreed/1024/1024).toFixed(2)} MB`:"unknown size",n=t.repoCount!==void 0?` (${t.repoCount} cached repositor${t.repoCount===1?"y":"ies"})`:"";o.line(`${c.success} Cleared global git cache${n}: ${i} freed`),o.line(`${c.info} Cache location: ${o.path(t.cacheDir)}`)}else o.line(`${c.info} Global git cache already empty at ${o.path(t.cacheDir)}`);return}if(l==="prune"){const{pruneCache:s}=await import("./chunks/prune.mjs"),t=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?a.stdout.write(`${JSON.stringify(t,null,2)}
|
|
35
|
+
`):t.removed.length===0?o.line(`${c.info} No cache entries to prune.`):o.line(`${c.success} Pruned ${t.removed.length} cache entr${t.removed.length===1?"y":"ies"}: ${t.removed.join(", ")}`);return}if(l==="sync"){const{printSyncPlan:s,runSync:t}=await import("./chunks/sync.mjs").then(function(n){return n.s}),i=await t({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,timeoutMs:e.timeoutMs});e.json?a.stdout.write(`${JSON.stringify(i,null,2)}
|
|
36
|
+
`):s(i);return}if(l==="verify"){const{printVerify:s,verifyCache:t}=await import("./chunks/verify.mjs").then(function(n){return n.a}),i=await t({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?a.stdout.write(`${JSON.stringify(i,null,2)}
|
|
37
|
+
`):s(i),i.results.some(n=>!n.ok)&&a.exit(u.FatalError);return}if(l==="init"){const{initConfig:s}=await import("./chunks/init.mjs");if(e.config)throw new Error("Init does not accept --config. Use the project root.");const t=await s({cacheDirOverride:e.cacheDir,json:e.json});e.json?a.stdout.write(`${JSON.stringify(t,null,2)}
|
|
38
|
+
`):(o.line(`${c.success} Wrote ${g.gray(o.path(t.configPath))}`),t.gitignoreUpdated&&t.gitignorePath&&o.line(`${c.info} Updated ${g.gray(o.path(t.gitignorePath))}`));return}o.line(`${p} ${l}: not implemented yet.`)};async function x(){try{a.on("uncaughtException",w),a.on("unhandledRejection",w);const r=O(),h=r.rawArgs;D(r.options.silent),r.help&&(m(),a.exit(u.Success)),r.command||(m(),a.exit(u.InvalidArgument)),r.command!=="add"&&r.command!=="remove"&&r.positionals.length>0&&($(`${p}: unexpected arguments.`),m(),a.exit(u.InvalidArgument)),r.command!=="add"&&r.options.targetDir&&($(`${p}: --target-dir is only valid for add.`),m(),a.exit(u.InvalidArgument)),await C(r.parsed,r.rawArgs)}catch(r){w(r)}}function w(r){const h=r.message||String(r);$(h),a.exit(u.FatalError)}export{p as CLI_NAME,x as main};
|
|
39
39
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/lock.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{readFile as m,writeFile as d}from"node:fs/promises";import c from"node:path";const u="docs.
|
|
1
|
+
import{readFile as m,writeFile as d}from"node:fs/promises";import c from"node:path";const u="docs-lock.json",a=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),i=(e,o)=>{if(typeof e!="string"||e.length===0)throw new Error(`${o} must be a non-empty string.`);return e},w=(e,o)=>{if(typeof e!="number"||Number.isNaN(e))throw new Error(`${o} must be a number.`);return e},l=(e,o)=>{const n=w(e,o);if(n<0)throw new Error(`${o} must be zero or greater.`);return n},f=e=>{if(!a(e))throw new Error("Lock file must be a JSON object.");if(e.version!==1)throw new Error("Lock file version must be 1.");const o=i(e.generatedAt,"generatedAt"),n=i(e.toolVersion,"toolVersion");if(!a(e.sources))throw new Error("sources must be an object.");const t={};for(const[r,s]of Object.entries(e.sources)){if(!a(s))throw new Error(`sources.${r} must be an object.`);t[r]={repo:i(s.repo,`sources.${r}.repo`),ref:i(s.ref,`sources.${r}.ref`),resolvedCommit:i(s.resolvedCommit,`sources.${r}.resolvedCommit`),bytes:l(s.bytes,`sources.${r}.bytes`),fileCount:l(s.fileCount,`sources.${r}.fileCount`),manifestSha256:i(s.manifestSha256,`sources.${r}.manifestSha256`),rulesSha256:s.rulesSha256===void 0?void 0:i(s.rulesSha256,`sources.${r}.rulesSha256`),updatedAt:i(s.updatedAt,`sources.${r}.updatedAt`)}}return{version:1,generatedAt:o,toolVersion:n,sources:t}},h=e=>c.resolve(c.dirname(e),u),b=async e=>{let o;try{o=await m(e,"utf8")}catch(t){const r=t instanceof Error?t.message:String(t);throw new Error(`Failed to read lock file at ${e}: ${r}`)}let n;try{n=JSON.parse(o)}catch(t){const r=t instanceof Error?t.message:String(t);throw new Error(`Invalid JSON in ${e}: ${r}`)}return f(n)},$=async(e,o)=>{const n=`${JSON.stringify(o,null,2)}
|
|
2
2
|
`;await d(e,n,"utf8")};export{u as DEFAULT_LOCK_FILENAME,b as readLock,h as resolveLockPath,f as validateLock,$ as writeLock};
|
|
3
3
|
//# sourceMappingURL=lock.mjs.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{writeFile as I,readFile as T,access as z}from"node:fs/promises";import p from"node:path";import{z as t}from"zod";import{r as L}from"./docs-cache.Oi01HUbh.mjs";const b=t.enum(["symlink","copy"]),M=t.enum(["materialize"]),F=t.enum(["tree","compressed"]),U=t.object({type:t.enum(["commit","manifest"]),value:t.string().nullable()}).strict(),_=t.object({ref:t.string().min(1),mode:M,include:t.array(t.string().min(1)).min(1),exclude:t.array(t.string().min(1)).optional(),targetMode:b.optional(),required:t.boolean(),maxBytes:t.number().min(1),maxFiles:t.number().min(1).optional(),allowHosts:t.array(t.string().min(1)).min(1),toc:t.union([t.boolean(),F]).optional(),unwrapSingleRootDir:t.boolean().optional()}).strict(),P=t.object({id:t.string().min(1),repo:t.string().min(1),targetDir:t.string().min(1).optional(),targetMode:b.optional(),ref:t.string().min(1).optional(),mode:M.optional(),include:t.array(t.string().min(1)).optional(),exclude:t.array(t.string().min(1)).optional(),required:t.boolean().optional(),maxBytes:t.number().min(1).optional(),maxFiles:t.number().min(1).optional(),integrity:U.optional(),toc:t.union([t.boolean(),F]).optional(),unwrapSingleRootDir:t.boolean().optional()}).strict(),J=t.object({$schema:t.string().min(1).optional(),cacheDir:t.string().min(1).optional(),targetMode:b.optional(),defaults:_.partial().optional(),sources:t.array(P)}).strict(),G=/^[a-zA-Z0-9_-]+$/,V=new Set([".","..","CON","PRN","AUX","NUL","COM1","LPT1"]),j=(e,r)=>{if(typeof e!="string"||e.length===0)throw new Error(`${r} must be a non-empty string.`);if(e.length>200)throw new Error(`${r} exceeds maximum length of 200.`);if(!G.test(e))throw new Error(`${r} must contain only alphanumeric characters, hyphens, and underscores.`);if(V.has(e.toUpperCase()))throw new Error(`${r} uses reserved name '${e}'.`);return e},A="docs.config.json",v=".docs",C="package.json",X=process.platform==="win32"?"copy":"symlink",f={cacheDir:v,defaults:{ref:"HEAD",mode:"materialize",include:["**/*.{md,mdx,markdown,mkd,txt,rst,adoc,asciidoc}"],exclude:[],targetMode:X,required:!0,maxBytes:2e8,allowHosts:["github.com","gitlab.com"],toc:!0,unwrapSingleRootDir:!1},sources:[]},Z=(e,r)=>!e||!r?e===r:e.length!==r.length?!1:e.every((o,n)=>o===r[n]),R=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),k=(e,r)=>{const o={};for(const[n,i]of Object.entries(e)){const u=r[n];if(Array.isArray(i)&&Array.isArray(u)){Z(i,u)||(o[n]=i);continue}if(R(i)&&R(u)){const a=k(i,u);Object.keys(a).length>0&&(o[n]=a);continue}i!==u&&(o[n]=i)}return o},K=e=>{const r={...f,$schema:e.$schema,defaults:{...f.defaults,...e.targetMode?{targetMode:e.targetMode}:void 0}},o=k(e,r),n={$schema:o.$schema,cacheDir:o.cacheDir,targetMode:o.targetMode,defaults:o.defaults,sources:e.sources};return(!n.defaults||Object.keys(n.defaults).length===0)&&delete n.defaults,n},w=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),m=(e,r)=>{if(typeof e!="string"||e.length===0)throw new Error(`${r} must be a non-empty string.`);return e},y=(e,r)=>{if(typeof e!="boolean")throw new Error(`${r} must be a boolean.`);return e},Q=(e,r)=>{if(typeof e!="number"||Number.isNaN(e))throw new Error(`${r} must be a number.`);return e},h=(e,r)=>{const o=Q(e,r);if(o<1)throw new Error(`${r} must be greater than zero.`);return o},g=(e,r)=>{if(!Array.isArray(e)||e.length===0)throw new Error(`${r} must be a non-empty array of strings.`);for(const o of e)if(typeof o!="string"||o.length===0)throw new Error(`${r} must contain non-empty strings.`);return e},q=(e,r)=>{const o=m(e,r);if(o!=="symlink"&&o!=="copy")throw new Error(`${r} must be "symlink" or "copy".`);return o},B=(e,r)=>{if(e!=="materialize")throw new Error(`${r} must be "materialize".`);return e},W=(e,r)=>{if(!w(e))throw new Error(`${r} must be an object.`);const o=e.type;if(o!=="commit"&&o!=="manifest")throw new Error(`${r}.type must be "commit" or "manifest".`);const n=e.value;if(typeof n!="string"&&n!==null)throw new Error(`${r}.value must be a string or null.`);return{type:o,value:n}},N=e=>{if(!w(e))throw new Error("Config must be a JSON object.");const r=J.safeParse(e);if(!r.success){const s=r.error.issues.map(c=>`${c.path.join(".")||"config"} ${c.message}`).join("; ");throw new Error(`Config does not match schema: ${s}.`)}const o=r.data,n=o.cacheDir?m(o.cacheDir,"cacheDir"):v,i=o.defaults,u=o.targetMode!==void 0?q(o.targetMode,"targetMode"):void 0,a=f.defaults;let d=a;if(i!==void 0){if(!w(i))throw new Error("defaults must be an object.");d={ref:i.ref!==void 0?m(i.ref,"defaults.ref"):a.ref,mode:i.mode!==void 0?B(i.mode,"defaults.mode"):a.mode,include:i.include!==void 0?g(i.include,"defaults.include"):a.include,exclude:i.exclude!==void 0?g(i.exclude,"defaults.exclude"):a.exclude,targetMode:i.targetMode!==void 0?q(i.targetMode,"defaults.targetMode"):u??a.targetMode,required:i.required!==void 0?y(i.required,"defaults.required"):a.required,maxBytes:i.maxBytes!==void 0?h(i.maxBytes,"defaults.maxBytes"):a.maxBytes,maxFiles:i.maxFiles!==void 0?h(i.maxFiles,"defaults.maxFiles"):a.maxFiles,allowHosts:i.allowHosts!==void 0?g(i.allowHosts,"defaults.allowHosts"):a.allowHosts,toc:i.toc!==void 0?i.toc:a.toc,unwrapSingleRootDir:i.unwrapSingleRootDir!==void 0?y(i.unwrapSingleRootDir,"defaults.unwrapSingleRootDir"):a.unwrapSingleRootDir}}else u!==void 0&&(d={...a,targetMode:u});const E=o.sources.map((s,c)=>{if(!w(s))throw new Error(`sources[${c}] must be an object.`);const l={id:j(s.id,`sources[${c}].id`),repo:m(s.repo,`sources[${c}].repo`)};if(s.targetDir!==void 0&&(l.targetDir=m(s.targetDir,`sources[${c}].targetDir`)),s.targetMode!==void 0){const x=m(s.targetMode,`sources[${c}].targetMode`);if(x!=="symlink"&&x!=="copy")throw new Error(`sources[${c}].targetMode must be "symlink" or "copy".`);l.targetMode=x}return s.ref!==void 0&&(l.ref=m(s.ref,`sources[${c}].ref`)),s.mode!==void 0&&(l.mode=B(s.mode,`sources[${c}].mode`)),s.include!==void 0&&(l.include=g(s.include,`sources[${c}].include`)),s.exclude!==void 0&&(l.exclude=g(s.exclude,`sources[${c}].exclude`)),s.required!==void 0&&(l.required=y(s.required,`sources[${c}].required`)),s.maxBytes!==void 0&&(l.maxBytes=h(s.maxBytes,`sources[${c}].maxBytes`)),s.maxFiles!==void 0&&(l.maxFiles=h(s.maxFiles,`sources[${c}].maxFiles`)),s.integrity!==void 0&&(l.integrity=W(s.integrity,`sources[${c}].integrity`)),s.toc!==void 0&&(l.toc=s.toc),s.unwrapSingleRootDir!==void 0&&(l.unwrapSingleRootDir=y(s.unwrapSingleRootDir,`sources[${c}].unwrapSingleRootDir`)),l}),S=new Set,$=[];for(const s of E)S.has(s.id)&&$.push(s.id),S.add(s.id);if($.length>0)throw new Error(`Duplicate source IDs found: ${$.join(", ")}. Each source must have a unique ID.`);return{cacheDir:n,targetMode:u,defaults:d,sources:E}},Y=e=>{const r=e.defaults??f.defaults;return e.sources.map(o=>({id:o.id,repo:o.repo,targetDir:o.targetDir,targetMode:o.targetMode??r.targetMode,ref:o.ref??r.ref,mode:o.mode??r.mode,include:o.include??r.include,exclude:o.exclude??r.exclude,required:o.required??r.required,maxBytes:o.maxBytes??r.maxBytes,maxFiles:o.maxFiles??r.maxFiles,integrity:o.integrity,toc:o.toc??r.toc,unwrapSingleRootDir:o.unwrapSingleRootDir??r.unwrapSingleRootDir}))},O=e=>e?p.resolve(e):p.resolve(process.cwd(),A),ee=()=>p.resolve(process.cwd(),C),H=async e=>{try{return await z(e),!0}catch{return!1}},D=async(e,r)=>{let o;try{o=await T(e,"utf8")}catch(a){const d=a instanceof Error?a.message:String(a);throw new Error(`Failed to read config at ${e}: ${d}`)}let n;try{n=JSON.parse(o)}catch(a){const d=a instanceof Error?a.message:String(a);throw new Error(`Invalid JSON in ${e}: ${d}`)}const i=r==="package"?n?.["docs-cache"]:n;if(r==="package"&&i===void 0)throw new Error(`Missing docs-cache config in ${e}.`);const u=N(i);for(const a of u.sources)a.targetDir&&L(e,a.targetDir);return{config:u,resolvedPath:e,sources:Y(u)}},re=async(e,r)=>{const o=`${JSON.stringify(r,null,2)}
|
|
2
|
+
`;await I(e,o,"utf8")},oe=async e=>{const r=O(e),o=p.basename(r)===C;if(e)return D(r,o?"package":"config");if(await H(r))return D(r,"config");const n=ee();if(await H(n))try{return await D(n,"package")}catch{}throw new Error(`No docs.config.json found at ${r} and no docs-cache config in ${n}.`)};export{f as D,j as a,v as b,A as c,oe as l,O as r,K as s,N as v,re as w};
|
|
3
|
+
//# sourceMappingURL=docs-cache.B9Pjydwx.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{readFile as o,writeFile as f,access as P}from"node:fs/promises";import a from"node:path";import{t as p}from"./docs-cache.
|
|
1
|
+
import{readFile as o,writeFile as f,access as P}from"node:fs/promises";import a from"node:path";import{t as p}from"./docs-cache.Oi01HUbh.mjs";const g=async n=>{try{return await P(n),!0}catch{return!1}},l=n=>{const r=n.trim();if(!r||r.startsWith("#")||r.startsWith("!"))return"";let t=r.replace(/^\//,"");return t=t.replace(/^\.\//,""),t=t.replace(/\/+$/,""),p(t)},d=(n,r)=>{const t=a.isAbsolute(r)?a.resolve(r):a.resolve(n,r),e=a.relative(n,t);return e===".."||e.startsWith(`..${a.sep}`)||a.isAbsolute(e)?null:e.length===0?".":e},u=async(n,r)=>{const t=a.resolve(n,".gitignore"),e=d(n,r);if(!e)return{gitignorePath:t,entry:null,hasEntry:!1};const i=l(e);if(!i)return{gitignorePath:t,entry:null,hasEntry:!1};let s="";await g(t)&&(s=await o(t,"utf8"));const h=s.split(/\r?\n/),c=new Set(h.map(y=>l(y)).filter(Boolean));return{gitignorePath:t,entry:`${i}/`,hasEntry:c.has(i)}},w=async(n,r)=>{const t=await u(n,r);if(!t.entry)return{updated:!1,gitignorePath:t.gitignorePath,entry:null};if(t.hasEntry)return{updated:!1,gitignorePath:t.gitignorePath,entry:t.entry};let e="";await g(t.gitignorePath)&&(e=await o(t.gitignorePath,"utf8"));const i=e.length===0||e.endsWith(`
|
|
2
2
|
`)?"":`
|
|
3
3
|
`,s=`${e}${i}${t.entry}
|
|
4
4
|
`;return await f(t.gitignorePath,s,"utf8"),{updated:!0,gitignorePath:t.gitignorePath,entry:t.entry}};export{w as e,u as g};
|
|
5
|
-
//# sourceMappingURL=docs-cache.
|
|
5
|
+
//# sourceMappingURL=docs-cache.DH8jN6rl.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import p from"node:process";import d from"cac";import i from"node:path";import s from"picocolors";var a=(o=>(o[o.Success=0]="Success",o[o.FatalError=1]="FatalError",o[o.InvalidArgument=9]="InvalidArgument",o))(a||{});const m=["add","remove","sync","status","clean","clean-cache","prune","verify","init"],
|
|
1
|
+
import p from"node:process";import d from"cac";import i from"node:path";import s from"picocolors";var a=(o=>(o[o.Success=0]="Success",o[o.FatalError=1]="FatalError",o[o.InvalidArgument=9]="InvalidArgument",o))(a||{});const m=["add","remove","sync","status","clean","clean-cache","prune","verify","init"],g=(o=p.argv)=>{try{const r=d("docs-cache");r.option("--source <repo>","Source repo (add only)").option("--target <dir>","Target directory for source (add only)").option("--config <path>","Path to config file").option("--cache-dir <path>","Override cache directory").option("--offline","Disable network access").option("--fail-on-miss","Fail when required sources are missing").option("--lock-only","Update lock without materializing files").option("--prune","Prune cache on remove").option("--target-dir <path>","Target directory for add").option("--concurrency <n>","Concurrency limit").option("--json","Output JSON").option("--timeout-ms <n>","Network timeout in milliseconds").option("--silent","Suppress non-error output").help();const e=r.parse(o,{run:!1}),t=e.args[0];if(t&&!m.includes(t))throw new Error(`Unknown command '${t}'.`);const n={config:e.options.config,cacheDir:e.options.cacheDir,offline:!!e.options.offline,failOnMiss:!!e.options.failOnMiss,lockOnly:!!e.options.lockOnly,prune:!!e.options.prune,targetDir:e.options.targetDir,concurrency:e.options.concurrency?Number(e.options.concurrency):void 0,json:!!e.options.json,timeoutMs:e.options.timeoutMs?Number(e.options.timeoutMs):void 0,silent:!!e.options.silent};if(n.concurrency!==void 0&&n.concurrency<1)throw new Error("--concurrency must be a positive number.");if(n.timeoutMs!==void 0&&n.timeoutMs<1)throw new Error("--timeout-ms must be a positive number.");const l=o.slice(2);return{command:t??null,options:n,positionals:e.args.slice(1),rawArgs:l,help:!!e.options.help,parsed:{command:t??null,args:e.args.slice(1),options:n}}}catch(r){const e=r instanceof Error?r.message:String(r);console.error(e),p.exit(a.InvalidArgument)}},h="TOC.md",u=o=>o.replace(/\\/g,"/"),f=(o,r)=>{const e=i.dirname(i.resolve(o)),t=i.resolve(e,r),n=i.relative(e,t);if(n===".."||n.startsWith(`..${i.sep}`)||i.isAbsolute(n))throw new Error(`targetDir '${r}' escapes project directory. Must be within ${e}.`);if(u(n).split("/").filter(Boolean).includes(".git"))throw new Error("targetDir cannot be within .git directory.");return t},y=(o,r,e)=>{if(e)return i.resolve(e);const t=i.dirname(o);return i.resolve(t,r)},w=(o,r)=>{i.join(o,"repos");const e=i.join(o,r);return{cacheDir:o,sourceDir:e}},v={error:s.red("\u2716"),success:s.green("\u2714"),info:s.blue("\u2139"),warn:s.yellow("\u26A0")};let c=!1;const b=o=>{c=o},E={path:o=>{const r=i.relative(process.cwd(),o),e=r.length<o.length?r:o;return u(e)},hash:o=>o?o.slice(0,7):"-",pad:(o,r)=>o.padEnd(r),line:(o="")=>{c||process.stdout.write(`${o}
|
|
2
2
|
`)},header:(o,r)=>{c||process.stdout.write(`${s.blue("\u2139")} ${o.padEnd(10)} ${r}
|
|
3
3
|
`)},item:(o,r,e)=>{if(c)return;const t=s.bold(r),n=e?s.gray(e):"";process.stdout.write(` ${o} ${t} ${n}
|
|
4
4
|
`)},step:(o,r,e)=>{if(c)return;const t=s.cyan("\u2192");process.stdout.write(` ${t} ${o} ${s.bold(r)}${e?` ${s.dim(e)}`:""}
|
|
5
|
-
`)}};export{
|
|
6
|
-
//# sourceMappingURL=docs-cache.
|
|
5
|
+
`)}};export{h as D,a as E,v as a,y as b,w as g,g as p,f as r,b as s,u as t,E as u};
|
|
6
|
+
//# sourceMappingURL=docs-cache.Oi01HUbh.mjs.map
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "docs-cache",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.1",
|
|
6
6
|
"description": "CLI for deterministic local caching of external documentation for agents and tools",
|
|
7
7
|
"author": "Frederik Bosch",
|
|
8
8
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@clack/prompts": "^1.0.0",
|
|
44
44
|
"cac": "^6.7.14",
|
|
45
|
+
"execa": "^9.6.1",
|
|
45
46
|
"fast-glob": "^3.3.2",
|
|
46
47
|
"picocolors": "^1.1.1",
|
|
47
48
|
"picomatch": "^2.3.1",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
}
|
|
69
70
|
],
|
|
70
71
|
"simple-git-hooks": {
|
|
71
|
-
"pre-commit": "pnpm lint-staged"
|
|
72
|
+
"pre-commit": "pnpm lint-staged && pnpm typecheck"
|
|
72
73
|
},
|
|
73
74
|
"lint-staged": {
|
|
74
75
|
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{writeFile as I,readFile as T,access as z}from"node:fs/promises";import h from"node:path";import{z as i}from"zod";import{r as L}from"./docs-cache.DDgb7yxQ.mjs";const b=i.enum(["symlink","copy"]),M=i.enum(["materialize"]),D=i.enum(["tree","compressed"]),U=i.object({type:i.enum(["commit","manifest"]),value:i.string().nullable()}).strict(),_=i.object({ref:i.string().min(1),mode:M,include:i.array(i.string().min(1)).min(1),targetMode:b.optional(),depth:i.number().min(1),required:i.boolean(),maxBytes:i.number().min(1),maxFiles:i.number().min(1).optional(),allowHosts:i.array(i.string().min(1)).min(1),toc:i.union([i.boolean(),D]).optional()}).strict(),P=i.object({id:i.string().min(1),repo:i.string().min(1),targetDir:i.string().min(1).optional(),targetMode:b.optional(),ref:i.string().min(1).optional(),mode:M.optional(),depth:i.number().min(1).optional(),include:i.array(i.string().min(1)).optional(),exclude:i.array(i.string().min(1)).optional(),required:i.boolean().optional(),maxBytes:i.number().min(1).optional(),maxFiles:i.number().min(1).optional(),integrity:U.optional(),toc:i.union([i.boolean(),D]).optional()}).strict(),J=i.object({$schema:i.string().min(1).optional(),cacheDir:i.string().min(1).optional(),targetMode:b.optional(),defaults:_.partial().optional(),sources:i.array(P)}).strict(),R=/^[a-zA-Z0-9_-]+$/,G=new Set([".","..","CON","PRN","AUX","NUL","COM1","LPT1"]),F=(e,t)=>{if(typeof e!="string"||e.length===0)throw new Error(`${t} must be a non-empty string.`);if(e.length>200)throw new Error(`${t} exceeds maximum length of 200.`);if(!R.test(e))throw new Error(`${t} must contain only alphanumeric characters, hyphens, and underscores.`);if(G.has(e.toUpperCase()))throw new Error(`${t} uses reserved name '${e}'.`);return e},j="docs.config.json",v=".docs",A="package.json",V=process.platform==="win32"?"copy":"symlink",g={cacheDir:v,defaults:{ref:"HEAD",mode:"materialize",include:["**/*.{md,mdx,markdown,mkd,txt,rst,adoc,asciidoc}"],targetMode:V,depth:1,required:!0,maxBytes:2e8,allowHosts:["github.com","gitlab.com"],toc:!0},sources:[]},X=(e,t)=>!e||!t?e===t:e.length!==t.length?!1:e.every((r,o)=>r===t[o]),S=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),C=(e,t)=>{const r={};for(const[o,d]of Object.entries(e)){const n=t[o];if(Array.isArray(d)&&Array.isArray(n)){X(d,n)||(r[o]=d);continue}if(S(d)&&S(n)){const c=C(d,n);Object.keys(c).length>0&&(r[o]=c);continue}d!==n&&(r[o]=d)}return r},Z=e=>{const t={...g,$schema:e.$schema,defaults:{...g.defaults,...e.targetMode?{targetMode:e.targetMode}:void 0}},r=C(e,t),o={$schema:r.$schema,cacheDir:r.cacheDir,targetMode:r.targetMode,defaults:r.defaults,sources:e.sources};return(!o.defaults||Object.keys(o.defaults).length===0)&&delete o.defaults,o},p=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),l=(e,t)=>{if(typeof e!="string"||e.length===0)throw new Error(`${t} must be a non-empty string.`);return e},k=(e,t)=>{if(typeof e!="boolean")throw new Error(`${t} must be a boolean.`);return e},K=(e,t)=>{if(typeof e!="number"||Number.isNaN(e))throw new Error(`${t} must be a number.`);return e},f=(e,t)=>{const r=K(e,t);if(r<1)throw new Error(`${t} must be greater than zero.`);return r},y=(e,t)=>{if(!Array.isArray(e)||e.length===0)throw new Error(`${t} must be a non-empty array of strings.`);for(const r of e)if(typeof r!="string"||r.length===0)throw new Error(`${t} must contain non-empty strings.`);return e},q=(e,t)=>{const r=l(e,t);if(r!=="symlink"&&r!=="copy")throw new Error(`${t} must be "symlink" or "copy".`);return r},B=(e,t)=>{if(e!=="materialize")throw new Error(`${t} must be "materialize".`);return e},Q=(e,t)=>{if(!p(e))throw new Error(`${t} must be an object.`);const r=e.type;if(r!=="commit"&&r!=="manifest")throw new Error(`${t}.type must be "commit" or "manifest".`);const o=e.value;if(typeof o!="string"&&o!==null)throw new Error(`${t}.value must be a string or null.`);return{type:r,value:o}},N=e=>{if(!p(e))throw new Error("Config must be a JSON object.");const t=J.safeParse(e);if(!t.success){const s=t.error.issues.map(a=>`${a.path.join(".")||"config"} ${a.message}`).join("; ");throw new Error(`Config does not match schema: ${s}.`)}const r=e.cacheDir?l(e.cacheDir,"cacheDir"):v,o=e.defaults,d=e.targetMode!==void 0?q(e.targetMode,"targetMode"):void 0,n=g.defaults;let c=n;if(o!==void 0){if(!p(o))throw new Error("defaults must be an object.");c={ref:o.ref!==void 0?l(o.ref,"defaults.ref"):n.ref,mode:o.mode!==void 0?B(o.mode,"defaults.mode"):n.mode,include:o.include!==void 0?y(o.include,"defaults.include"):n.include,targetMode:o.targetMode!==void 0?q(o.targetMode,"defaults.targetMode"):d??n.targetMode,depth:o.depth!==void 0?f(o.depth,"defaults.depth"):n.depth,required:o.required!==void 0?k(o.required,"defaults.required"):n.required,maxBytes:o.maxBytes!==void 0?f(o.maxBytes,"defaults.maxBytes"):n.maxBytes,maxFiles:o.maxFiles!==void 0?f(o.maxFiles,"defaults.maxFiles"):n.maxFiles,allowHosts:o.allowHosts!==void 0?y(o.allowHosts,"defaults.allowHosts"):n.allowHosts,toc:o.toc!==void 0?o.toc:n.toc}}else d!==void 0&&(c={...n,targetMode:d});if(!Array.isArray(e.sources))throw new Error("sources must be an array.");const m=e.sources.map((s,a)=>{if(!p(s))throw new Error(`sources[${a}] must be an object.`);const u={id:F(s.id,`sources[${a}].id`),repo:l(s.repo,`sources[${a}].repo`)};if(s.targetDir!==void 0&&(u.targetDir=l(s.targetDir,`sources[${a}].targetDir`)),s.targetMode!==void 0){const $=l(s.targetMode,`sources[${a}].targetMode`);if($!=="symlink"&&$!=="copy")throw new Error(`sources[${a}].targetMode must be "symlink" or "copy".`);u.targetMode=$}return s.ref!==void 0&&(u.ref=l(s.ref,`sources[${a}].ref`)),s.mode!==void 0&&(u.mode=B(s.mode,`sources[${a}].mode`)),s.depth!==void 0&&(u.depth=f(s.depth,`sources[${a}].depth`)),s.include!==void 0&&(u.include=y(s.include,`sources[${a}].include`)),s.exclude!==void 0&&(u.exclude=y(s.exclude,`sources[${a}].exclude`)),s.required!==void 0&&(u.required=k(s.required,`sources[${a}].required`)),s.maxBytes!==void 0&&(u.maxBytes=f(s.maxBytes,`sources[${a}].maxBytes`)),s.maxFiles!==void 0&&(u.maxFiles=f(s.maxFiles,`sources[${a}].maxFiles`)),s.integrity!==void 0&&(u.integrity=Q(s.integrity,`sources[${a}].integrity`)),s.toc!==void 0&&(u.toc=s.toc),u}),E=new Set,w=[];for(const s of m)E.has(s.id)&&w.push(s.id),E.add(s.id);if(w.length>0)throw new Error(`Duplicate source IDs found: ${w.join(", ")}. Each source must have a unique ID.`);return{cacheDir:r,targetMode:d,defaults:c,sources:m}},W=e=>{const t=e.defaults??g.defaults;return e.sources.map(r=>({id:r.id,repo:r.repo,targetDir:r.targetDir,targetMode:r.targetMode??t.targetMode,ref:r.ref??t.ref,mode:r.mode??t.mode,depth:r.depth??t.depth,include:r.include??t.include,exclude:r.exclude,required:r.required??t.required,maxBytes:r.maxBytes??t.maxBytes,maxFiles:r.maxFiles??t.maxFiles,integrity:r.integrity,toc:r.toc??t.toc}))},O=e=>e?h.resolve(e):h.resolve(process.cwd(),j),Y=()=>h.resolve(process.cwd(),A),H=async e=>{try{return await z(e),!0}catch{return!1}},x=async(e,t)=>{let r;try{r=await T(e,"utf8")}catch(c){const m=c instanceof Error?c.message:String(c);throw new Error(`Failed to read config at ${e}: ${m}`)}let o;try{o=JSON.parse(r)}catch(c){const m=c instanceof Error?c.message:String(c);throw new Error(`Invalid JSON in ${e}: ${m}`)}const d=t==="package"?o?.["docs-cache"]:o;if(t==="package"&&d===void 0)throw new Error(`Missing docs-cache config in ${e}.`);const n=N(d);for(const c of n.sources)c.targetDir&&L(e,c.targetDir);return{config:n,resolvedPath:e,sources:W(n)}},ee=async(e,t)=>{const r=`${JSON.stringify(t,null,2)}
|
|
2
|
-
`;await I(e,r,"utf8")},te=async e=>{const t=O(e),r=h.basename(t)===A;if(e)return x(t,r?"package":"config");if(await H(t))return x(t,"config");const o=Y();if(await H(o))try{return await x(o,"package")}catch{}throw new Error(`No docs.config.json found at ${t} and no docs-cache config in ${o}.`)};export{g as D,F as a,v as b,j as c,te as l,O as r,Z as s,N as v,ee as w};
|
|
3
|
-
//# sourceMappingURL=docs-cache.BWEcxcrg.mjs.map
|