docs-cache 0.3.0 → 0.3.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.
@@ -1,9 +1,9 @@
1
- import{createHash as _,randomBytes as ye}from"node:crypto";import{rm as x,mkdtemp as N,mkdir as b,writeFile as q,access as A,rename as B,open as U,lstat as Se,symlink as ve,cp as De,readFile as L}from"node:fs/promises";import g from"node:path";import P from"picocolors";import{g as $e,t as z,r as G,D as Ce,u as C,a as E,b as xe}from"../shared/docs-cache.DDgb7yxQ.mjs";import{a as Y,l as Me,D as Pe,b as Ee}from"../shared/docs-cache.BWEcxcrg.mjs";import{execFile as K}from"node:child_process";import Oe,{tmpdir as V}from"node:os";import{pathToFileURL as Te}from"node:url";import{promisify as Q}from"node:util";import{e as ke,r as Z}from"../shared/docs-cache.kK1DPQIQ.mjs";import{writeLock as Fe,resolveLockPath as be,readLock as Ie}from"../lock.mjs";import{M as W,v as ee}from"./verify.mjs";import{createWriteStream as te,createReadStream as Re,constants as re}from"node:fs";import{pipeline as je}from"node:stream/promises";import oe from"fast-glob";const _e=/^(https?:\/\/)([^@]+)@/i,R=e=>e.replace(_e,"$1***@"),Ae=Q(K),Le=3e4,He=new Set(["file:","ftp:","data:","javascript:"]),Ne=e=>{try{const r=new URL(e);if(He.has(r.protocol))throw new Error(`Blocked protocol '${r.protocol}' in repo URL '${R(e)}'.`)}catch(r){if(r instanceof TypeError)return;throw r}},Be=e=>{if(Ne(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}},se=(e,r)=>{const t=Be(e);if(!t)throw new Error(`Unsupported repo URL '${R(e)}'. Use HTTPS or SSH.`);const s=t.toLowerCase();if(!r.map(o=>o.toLowerCase()).includes(s))throw new Error(`Host '${t}' is not in allowHosts for '${R(e)}'.`)},ie=e=>{const r=e.trim().split(`
2
- `).filter(Boolean);return r.length===0?null:r[0].split(/\s+/)[0]||null},Ue=async e=>{se(e.repo,e.allowHosts);const{stdout:r}=await Ae("git",["ls-remote",e.repo,e.ref],{timeout:e.timeoutMs??Le,maxBuffer:1024*1024}),t=ie(r);if(!t)throw new Error(`Unable to resolve ref '${e.ref}' for ${R(e.repo)}.`);return{repo:e.repo,ref:e.ref,resolvedCommit:t}},ae=Q(K),ne=12e4,T=async(e,r)=>{const t=["-c","core.hooksPath=/dev/null","-c","submodule.recurse=false","-c","protocol.ext.allow=never"];r?.allowFileProtocol?t.push("-c","protocol.file.allow=always"):t.push("-c","protocol.file.allow=never"),await ae("git",[...t,...e],{cwd:r?.cwd,timeout:r?.timeoutMs??ne,maxBuffer:10*1024*1024,env:{PATH:process.env.PATH,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"}}})},ze=e=>_("sha256").update(e).digest("hex").substring(0,16),Ge=e=>{const r=ze(e);return g.join(Z(),r)},Ye=async e=>{try{return await T(["rev-parse","--git-dir"],{cwd:e}),!0}catch{return!1}},We=async(e,r,t,s)=>{const o=g.join(t,"archive.tar");await T(["archive","--remote",e,"--format=tar","--output",o,r],{timeoutMs:s}),await ae("tar",["-xf",o,"-C",t],{timeout:s??ne,maxBuffer:1024*1024}),await x(o,{force:!0})},ce=e=>{if(!e||e.length===0)return!1;for(const r of e)if(!r||r.includes("**"))return!1;return!0},le=e=>{if(!e)return[];const r=e.map(t=>{const s=t.replace(/\\/g,"/"),o=s.indexOf("*");return(o===-1?s:s.slice(0,o)).replace(/\/+$|\/$/,"")});return Array.from(new Set(r.filter(t=>t.length>0)))},ue=async(e,r)=>{const t=/^[0-9a-f]{7,40}$/i.test(e.ref),s=ce(e.include),o=["clone","--no-checkout","--filter=blob:none","--depth",String(e.depth),"--recurse-submodules=no","--no-tags"];if(s&&o.push("--sparse"),t||(o.push("--single-branch"),e.ref!=="HEAD"&&o.push("--branch",e.ref)),o.push(e.repo,r),await T(o,{timeoutMs:e.timeoutMs}),s){const i=le(e.include);i.length>0&&await T(["-C",r,"sparse-checkout","set",...i],{timeoutMs:e.timeoutMs})}await T(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs})},Je=async(e,r)=>{const t=Ge(e.repo),s=await ke(t),o=/^[0-9a-f]{7,40}$/i.test(e.ref),i=ce(e.include),c=Z();if(await b(c,{recursive:!0}),s&&await Ye(t))try{const u=["fetch","origin"];if(o)u.push("--depth",String(e.depth));else{const D=e.ref==="HEAD"?"HEAD":`${e.ref}:refs/remotes/origin/${e.ref}`;u.push(D,"--depth",String(e.depth))}await T(["-C",t,...u],{timeoutMs:e.timeoutMs})}catch{await x(t,{recursive:!0,force:!0}),await ue(e,t)}else s&&await x(t,{recursive:!0,force:!0}),await ue(e,t);await b(r,{recursive:!0});const a=["clone","--no-checkout","--filter=blob:none","--depth",String(e.depth),"--recurse-submodules=no","--no-tags"];i&&a.push("--sparse"),o||(a.push("--single-branch"),e.ref!=="HEAD"&&a.push("--branch",e.ref));const n=Te(t).href;if(a.push(n,r),await T(a,{timeoutMs:e.timeoutMs,allowFileProtocol:!0}),i){const u=le(e.include);u.length>0&&await T(["-C",r,"sparse-checkout","set",...u],{timeoutMs:e.timeoutMs})}await T(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs})},Xe=async e=>{const r=await N(g.join(V(),`docs-cache-${e.sourceId}-`));try{return await We(e.repo,e.resolvedCommit,r,e.timeoutMs),r}catch(t){throw await x(r,{recursive:!0,force:!0}),t}},qe=async e=>{Y(e.sourceId,"sourceId");try{const r=await Xe(e);return{repoDir:r,cleanup:async()=>{await x(r,{recursive:!0,force:!0})}}}catch{const r=await N(g.join(V(),`docs-cache-${e.sourceId}-`));try{return await Je(e,r),{repoDir:r,cleanup:async()=>{await x(r,{recursive:!0,force:!0})}}}catch(t){throw await x(r,{recursive:!0,force:!0}),t}}},H=e=>z(e),J=Number(process.env.DOCS_CACHE_STREAM_THRESHOLD_MB??"2"),Ke=Number.isFinite(J)&&J>0?Math.floor(J*1024*1024):1024*1024,Ve=(e,r)=>{const t=g.resolve(e);if(!g.resolve(r).startsWith(t+g.sep))throw new Error(`Path traversal detected: ${r}`)},fe=async e=>{try{return await U(e,re.O_RDONLY|re.O_NOFOLLOW)}catch(r){const t=r.code;if(t==="ELOOP")return null;if(t==="EINVAL"||t==="ENOSYS"||t==="ENOTSUP")return(await Se(e)).isSymbolicLink()?null:await U(e,"r");throw r}},Qe=async(e,r=5e3)=>{const t=Date.now();for(;Date.now()-t<r;)try{const s=await U(e,"wx");return{release:async()=>{await s.close(),await x(e,{force:!0})}}}catch(s){if(s.code!=="EEXIST")throw s;await new Promise(o=>setTimeout(o,100))}throw new Error(`Failed to acquire lock for ${e}.`)},Ze=async e=>{Y(e.sourceId,"sourceId");const r=$e(e.cacheDir,e.sourceId);await b(e.cacheDir,{recursive:!0});const t=await N(g.join(e.cacheDir,`.tmp-${e.sourceId}-`));let s=null;const o=async()=>{const i=s;!i||i.closed||i.destroyed||await new Promise(c=>{const a=()=>{i.off("close",n),i.off("error",u),c()},n=()=>a(),u=()=>a();i.once("close",n),i.once("error",u);try{i.end()}catch{a()}})};try{const i=(await oe(e.include,{cwd:e.repoDir,ignore:[".git/**",...e.exclude??[]],dot:!0,onlyFiles:!0,followSymbolicLinks:!1})).map(f=>({relativePath:f,normalized:H(f)})).sort((f,h)=>f.normalized.localeCompare(h.normalized)),c=new Set;for(const{relativePath:f}of i)c.add(g.dirname(f));await Promise.all(Array.from(c,f=>b(g.join(t,f),{recursive:!0})));let a=0,n=0;const u=Math.max(1,Math.min(i.length,Math.max(8,Math.min(128,Oe.cpus().length*8)))),D=g.join(t,W),w=te(D,{encoding:"utf8"});s=w;const $=_("sha256"),S=async f=>new Promise((h,m)=>{const p=l=>{w.off("drain",d),m(l)},d=()=>{w.off("error",p),h()};w.once("error",p),w.write(f)?(w.off("error",p),h()):w.once("drain",d)});for(let f=0;f<i.length;f+=u){const h=i.slice(f,f+u),m=await Promise.all(h.map(async p=>{const d=g.join(e.repoDir,p.relativePath),l=await fe(d);if(!l)return null;try{const M=await l.stat();if(!M.isFile())return null;const O=g.join(t,p.relativePath);if(Ve(t,O),M.size>=Ke){const I=Re(d,{fd:l.fd,autoClose:!1}),k=te(O);await je(I,k)}else{const I=await l.readFile();await q(O,I)}return{path:p.normalized,size:M.size}}finally{await l.close()}}));for(const p of m){if(!p)continue;if(e.maxFiles!==void 0&&n+1>e.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${e.maxFiles}).`);if(a+=p.size,a>e.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${e.maxBytes}).`);const d=`${JSON.stringify(p)}
3
- `;$.update(d),await S(d),n+=1}}await new Promise((f,h)=>{w.end(()=>f()),w.once("error",h)});const y=$.digest("hex"),v=async f=>{try{return await A(f),!0}catch{return!1}};return await(async(f,h)=>{const m=await Qe(`${h}.lock`);try{const p=await v(h),d=`${h}.bak-${ye(8).toString("hex")}`;p&&await B(h,d);try{await B(f,h)}catch(l){if(p)try{await B(d,h)}catch(M){const O=M instanceof Error?M.message:String(M);process.stderr.write(`Warning: Failed to restore backup: ${O}
4
- `)}throw l}p&&await x(d,{recursive:!0,force:!0})}finally{await m.release()}})(t,r.sourceDir),{bytes:a,fileCount:n,manifestSha256:y}}catch(i){try{await o()}catch{}throw await x(t,{recursive:!0,force:!0}),i}},et=async e=>{Y(e.sourceId,"sourceId");const r=await oe(e.include,{cwd:e.repoDir,ignore:[".git/**",...e.exclude??[]],dot:!0,onlyFiles:!0,followSymbolicLinks:!1});r.sort((i,c)=>H(i).localeCompare(H(c)));let t=0,s=0;const o=_("sha256");for(const i of r){const c=H(i),a=g.join(e.repoDir,i),n=await fe(a);if(n)try{const u=await n.stat();if(!u.isFile())continue;if(e.maxFiles!==void 0&&s+1>e.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${e.maxFiles}).`);if(t+=u.size,t>e.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${e.maxBytes}).`);const D=`${JSON.stringify({path:c,size:u.size})}
5
- `;o.update(D),s+=1}finally{await n.close()}}return{bytes:t,fileCount:s,manifestSha256:o.digest("hex")}},tt=async(e,r)=>{await r.rm(e,{recursive:!0,force:!0})},X=async e=>{const r=e.deps??{cp:De,mkdir:b,rm:x,symlink:ve,stderr:process.stderr},t=g.dirname(e.targetDir);await r.mkdir(t,{recursive:!0}),await tt(e.targetDir,r);const s=process.platform==="win32"?"copy":"symlink";if((e.mode??s)==="copy"){await r.cp(e.sourceDir,e.targetDir,{recursive:!0});return}const o=process.platform==="win32"?"junction":"dir";try{await r.symlink(e.sourceDir,e.targetDir,o)}catch(i){const c=i.code;if(c&&new Set(["EPERM","EACCES","ENOTSUP","EINVAL"]).has(c)){if(e.explicitTargetMode){const a=i instanceof Error?i.message:String(i);r.stderr.write(`Warning: Failed to create symlink at ${e.targetDir}. Falling back to copy. ${a}
6
- `)}await r.cp(e.sourceDir,e.targetDir,{recursive:!0});return}throw i}},rt=e=>{const r={dirs:new Map,files:[]};for(const t of e){const s=t.split("/").filter(Boolean);if(s.length===0)continue;let o=r;for(const c of s.slice(0,-1)){let a=o.dirs.get(c);a||(a={dirs:new Map,files:[]},o.dirs.set(c,a)),o=a}const i=s[s.length-1];o.files.push({name:i,path:t})}return r},me=(e,r,t)=>{const s=" ".repeat(r),o=Array.from(e.dirs.keys()).sort(),i=[...e.files].sort((c,a)=>c.name.localeCompare(a.name));for(const c of o){t.push(`${s}- ${c}/`);const a=e.dirs.get(c);a&&me(a,r+1,t)}for(const c of i)t.push(`${s}- [${c.name}](./${c.path})`)},ot=(e,r,t)=>{const s=[...e].sort((a,n)=>a.localeCompare(n)),o=new Map;for(const a of s){const n=a.lastIndexOf("/"),u=n===-1?"":a.substring(0,n),D=n===-1?a:a.substring(n+1),w=o.get(u);w?w.push(D):o.set(u,[D])}const i=Array.from(o.keys()).sort(),c=[];c.push(`[${t}]`);for(const a of i){const n=o.get(a);if(!n)continue;const u=n.join(",");a===""?c.push(`root:{${u}}`):c.push(`${a}:{${u}}`)}r.push(c.join("|"))},st=(e,r="compressed")=>{const t=[];if(r==="tree"){t.push(`# ${e.id} - Documentation`),t.push(""),t.push("## Files"),t.push("");const s=rt(e.files);me(s,0,t)}else{const s=`${e.id} Docs Index`;ot(e.files,t,s)}return t.push(""),t.join(`
7
- `)},it=async e=>{const r=g.join(e,".manifest.jsonl");try{const t=await L(r,"utf8"),s=[];for(const o of t.split(`
8
- `))if(o.trim()){const i=JSON.parse(o);i.path&&s.push(i.path)}return s}catch{return[]}},at=async e=>{const r=new Map(e.sources.map(s=>[s.id,s])),t=new Map((e.results??[]).map(s=>[s.id,s]));for(const[s,o]of Object.entries(e.lock.sources)){const i=r.get(s);i?.targetDir&&z(G(e.configPath,i.targetDir));const c=g.join(e.cacheDir,s);try{await A(c)}catch{continue}const a=await it(c),n={id:s,repo:o.repo,ref:o.ref,resolvedCommit:o.resolvedCommit,fileCount:o.fileCount,cachePath:z(g.join(e.cacheDir,s)),files:a},u=i?.toc,D=u!==!1;let w="compressed";typeof u=="string"&&(w=u);const $=g.join(c,Ce);if(D){if(t.get(s)?.status==="up-to-date")try{await A($);continue}catch{}const S=st(n,w);await q($,S,"utf8")}else try{await x($,{force:!0})}catch{}}},nt=e=>{if(e<1024)return`${e} B`;const r=["KB","MB","GB","TB"];let t=e,s=-1;for(;t>=1024&&s<r.length-1;)t/=1024,s+=1;return`${t.toFixed(1)} ${r[s]}`},j=async e=>{try{return await A(e),!0}catch{return!1}},de=async(e,r)=>{const t=g.join(e,r);return await j(t)?await j(g.join(t,W)):!1},he=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()},ct=e=>{const r={include:he(e.include),exclude:he(e.exclude)},t=_("sha256");return t.update(JSON.stringify(r)),t.digest("hex")},pe=async(e,r={})=>{const{config:t,resolvedPath:s,sources:o}=await Me(e.configPath),i=t.defaults??Pe.defaults,c=xe(s,t.cacheDir??Ee,e.cacheDirOverride),a=be(s),n=await j(a);let u=null;n&&(u=await Ie(a));const D=r.resolveRemoteCommit??Ue,w=e.sourceFilter?.length?o.filter(S=>e.sourceFilter?.includes(S.id)):o,$=await Promise.all(w.map(async S=>{const y=u?.sources?.[S.id],v=S.include??i.include,f=S.exclude,h=ct({include:v,exclude:f});if(e.offline){const l=await de(c,S.id);return{id:S.id,repo:y?.repo??S.repo,ref:y?.ref??S.ref??i.ref,resolvedCommit:y?.resolvedCommit??"offline",lockCommit:y?.resolvedCommit??null,lockRulesSha256:y?.rulesSha256,status:y&&l?"up-to-date":"missing",bytes:y?.bytes,fileCount:y?.fileCount,manifestSha256:y?.manifestSha256,rulesSha256:h}}const m=await D({repo:S.repo,ref:S.ref,allowHosts:i.allowHosts,timeoutMs:e.timeoutMs}),p=y?.resolvedCommit===m.resolvedCommit&&y?.rulesSha256===h,d=y?p?"up-to-date":"changed":"missing";return{id:S.id,repo:m.repo,ref:m.ref,resolvedCommit:m.resolvedCommit,lockCommit:y?.resolvedCommit??null,lockRulesSha256:y?.rulesSha256,status:d,bytes:y?.bytes,fileCount:y?.fileCount,manifestSha256:y?.manifestSha256,rulesSha256:h}}));return{config:t,configPath:s,cacheDir:c,lockPath:a,lockExists:n,lockData:u,results:$,sources:w,defaults:i}},lt=async()=>{const e=g.resolve(process.cwd(),"package.json");try{const r=await L(e,"utf8"),t=JSON.parse(r.toString());return typeof t.version=="string"?t.version:"0.0.0"}catch{}try{const r=await L(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 L(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"}},ut=async(e,r)=>{const t=await lt(),s=new Date().toISOString(),o={...r?.sources??{}};for(const i of e.results){const c=o[i.id];o[i.id]={repo:i.repo,ref:i.ref,resolvedCommit:i.resolvedCommit,bytes:i.bytes??c?.bytes??0,fileCount:i.fileCount??c?.fileCount??0,manifestSha256:i.manifestSha256??c?.manifestSha256??i.resolvedCommit,rulesSha256:i.rulesSha256??c?.rulesSha256,updatedAt:s}}return{version:1,generatedAt:s,toolVersion:t,sources:o}},we=async(e,r={})=>{const t=process.hrtime.bigint();let s=0;const o=await pe(e,r);await b(o.cacheDir,{recursive:!0});const i=o.lockData,c=o.results.filter(n=>{const u=o.sources.find(D=>D.id===n.id);return n.status==="missing"&&(u?.required??!0)});if(e.failOnMiss&&c.length>0)throw new Error(`Missing required source(s): ${c.map(n=>n.id).join(", ")}.`);if(!e.lockOnly){const n=o.defaults,u=r.fetchSource??qe,D=r.materializeSource??Ze,w=new Map,$=async(v,f)=>{const h=v?.length?o.results.filter(m=>v.includes(m.id)):o.results;return(await Promise.all(h.map(async m=>{const p=o.sources.find(l=>l.id===m.id);if(!p)return null;if(f)return{result:m,source:p};let d=w.get(m.id);return d===void 0&&(d=await de(o.cacheDir,m.id),w.set(m.id,d)),m.status!=="up-to-date"||!d?{result:m,source:p}:null}))).filter(Boolean)},S=async()=>{await Promise.all(o.sources.map(async v=>{if(!v.targetDir)return;const f=G(o.configPath,v.targetDir);await j(f)||await X({sourceDir:g.join(o.cacheDir,v.id),targetDir:f,mode:v.targetMode??n.targetMode,explicitTargetMode:v.targetMode!==void 0})}))},y=async v=>{const f=e.concurrency??4;let h=0;const m=async()=>{const p=v[h];if(!p||!p.source)return;h+=1;const{result:d,source:l}=p,M=o.lockData?.sources?.[l.id];e.json||C.step("Fetching",l.id);const O=await u({sourceId:l.id,repo:l.repo,ref:l.ref,resolvedCommit:d.resolvedCommit,cacheDir:o.cacheDir,depth:l.depth??n.depth,include:l.include??n.include,timeoutMs:e.timeoutMs});try{const I=g.join(o.cacheDir,l.id,W);if(d.status!=="up-to-date"&&M?.manifestSha256&&await j(I)){const F=await et({sourceId:l.id,repoDir:O.repoDir,cacheDir:o.cacheDir,include:l.include??n.include,exclude:l.exclude,maxBytes:l.maxBytes??n.maxBytes,maxFiles:l.maxFiles??n.maxFiles});if(F.manifestSha256===M.manifestSha256){d.bytes=F.bytes,d.fileCount=F.fileCount,d.manifestSha256=F.manifestSha256,d.status="up-to-date",e.json||C.item(E.success,l.id,"no content changes"),await m();return}}const k=await D({sourceId:l.id,repoDir:O.repoDir,cacheDir:o.cacheDir,include:l.include??n.include,exclude:l.exclude,maxBytes:l.maxBytes??n.maxBytes,maxFiles:l.maxFiles??n.maxFiles});if(l.targetDir){const F=G(o.configPath,l.targetDir);await X({sourceDir:g.join(o.cacheDir,l.id),targetDir:F,mode:l.targetMode??n.targetMode,explicitTargetMode:l.targetMode!==void 0})}d.bytes=k.bytes,d.fileCount=k.fileCount,d.manifestSha256=k.manifestSha256,e.json||C.item(E.success,l.id,`synced ${k.fileCount} files`)}finally{await O.cleanup()}await m()};await Promise.all(Array.from({length:Math.min(f,v.length)},m))};if(e.offline)await S();else{const v=await $();await y(v),await S()}if(!e.offline){const v=(await ee({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(f=>!f.ok);if(v.length>0){const f=await $(v.map(m=>m.id),!0);f.length>0&&(await y(f),await S());const h=(await ee({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(m=>!m.ok);if(h.length>0&&(s+=1,!e.json)){const m=h.map(p=>`${p.id} (${p.issues.join("; ")})`).join(", ");C.line(`${E.warn} Verify failed for ${h.length} source(s): ${m}`)}}}}const a=await ut(o,i);if(await Fe(o.lockPath,a),!e.json){const n=Number(process.hrtime.bigint()-t)/1e6,u=o.results.reduce((w,$)=>w+($.bytes??0),0),D=o.results.reduce((w,$)=>w+($.fileCount??0),0);C.line(`${E.info} Completed in ${n.toFixed(0)}ms \xB7 ${nt(u)} \xB7 ${D} files${s?` \xB7 ${s} warning${s===1?"":"s"}`:""}`)}return await at({cacheDir:o.cacheDir,configPath:o.configPath,lock:a,sources:o.sources,results:o.results}),o.lockExists=!0,o},ge=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){C.line(`${E.info} No sources to sync.`);return}C.line(`${E.info} ${e.results.length} sources (${r.upToDate} up-to-date, ${r.changed} changed, ${r.missing} missing)`);for(const t of e.results){const s=C.hash(t.resolvedCommit),o=C.hash(t.lockCommit),i=!!t.lockRulesSha256&&!!t.rulesSha256&&t.lockRulesSha256!==t.rulesSha256;if(t.status==="up-to-date"){C.item(E.success,t.id,`${P.dim("up-to-date")} ${P.gray(s)}`);continue}if(t.status==="changed"){if(t.lockCommit===t.resolvedCommit&&i){C.item(E.warn,t.id,`${P.dim("rules changed")} ${P.gray(s)}`);continue}C.item(E.warn,t.id,`${P.dim("changed")} ${P.gray(o)} ${P.dim("->")} ${P.gray(s)}`);continue}C.item(E.warn,t.id,`${P.dim("missing")} ${P.gray(s)}`)}},ft={__proto__:null,getSyncPlan:pe,printSyncPlan:ge,runSync:we};export{X as a,ge as b,we as c,se as e,ie as p,R as r,ft as s};
1
+ import{createHash as A,randomBytes as ve}from"node:crypto";import{mkdtemp as B,rm as O,mkdir as R,readFile as j,writeFile as V,access as N,rename as U,open as z,lstat as De,symlink as $e,cp as Ce}from"node:fs/promises";import w from"node:path";import x from"picocolors";import{g as Pe,t as Y,r as G,D as xe,u as C,a as M,b as Me}from"../shared/docs-cache.DDgb7yxQ.mjs";import{a as W,l as Ee,D as Te,b as Oe}from"../shared/docs-cache.BWEcxcrg.mjs";import{execFile as Q}from"node:child_process";import ke,{tmpdir as Z}from"node:os";import{pathToFileURL as Fe}from"node:url";import{promisify as ee}from"node:util";import{e as be,r as te}from"../shared/docs-cache.kK1DPQIQ.mjs";import{writeLock as Re,resolveLockPath as Ie,readLock as je}from"../lock.mjs";import{M as J,v as re}from"./verify.mjs";import{createWriteStream as oe,createReadStream as _e,constants as se}from"node:fs";import{pipeline as Le}from"node:stream/promises";import ie from"fast-glob";const Ae=/^(https?:\/\/)([^@]+)@/i,_=e=>e.replace(Ae,"$1***@"),Ne=ee(Q),He=3e4,Be=new Set(["file:","ftp:","data:","javascript:"]),Ue=e=>{try{const r=new URL(e);if(Be.has(r.protocol))throw new Error(`Blocked protocol '${r.protocol}' in repo URL '${_(e)}'.`)}catch(r){if(r instanceof TypeError)return;throw r}},ze=e=>{if(Ue(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}},ae=(e,r)=>{const t=ze(e);if(!t)throw new Error(`Unsupported repo URL '${_(e)}'. Use HTTPS or SSH.`);const s=t.toLowerCase();if(!r.map(o=>o.toLowerCase()).includes(s))throw new Error(`Host '${t}' is not in allowHosts for '${_(e)}'.`)},ne=e=>{const r=e.trim().split(`
2
+ `).filter(Boolean);return r.length===0?null:r[0].split(/\s+/)[0]||null},Ye=async e=>{ae(e.repo,e.allowHosts);const{stdout:r}=await Ne("git",["ls-remote",e.repo,e.ref],{timeout:e.timeoutMs??He,maxBuffer:1024*1024}),t=ne(r);if(!t)throw new Error(`Unable to resolve ref '${e.ref}' for ${_(e.repo)}.`);return{repo:e.repo,ref:e.ref,resolvedCommit:t}},ce=ee(Q),le=12e4,Ge=3,We=100,T=async(e,r)=>{const t=["-c","core.hooksPath=/dev/null","-c","submodule.recurse=false","-c","protocol.ext.allow=never"];r?.allowFileProtocol?t.push("-c","protocol.file.allow=always"):t.push("-c","protocol.file.allow=never"),await ce("git",[...t,...e],{cwd:r?.cwd,timeout:r?.timeoutMs??le,maxBuffer:10*1024*1024,env:{PATH:process.env.PATH,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"}}})},k=async(e,r=Ge)=>{for(let t=0;t<=r;t+=1)try{await O(e,{recursive:!0,force:!0});return}catch(s){const o=s.code;if(o!=="ENOTEMPTY"&&o!=="EBUSY"&&o!=="EPERM"||t===r)throw s;await new Promise(i=>setTimeout(i,We*(t+1)))}},Je=e=>A("sha256").update(e).digest("hex").substring(0,16),Xe=e=>{const r=Je(e);return w.join(te(),r)},qe=async e=>{try{return await T(["rev-parse","--git-dir"],{cwd:e}),!0}catch{return!1}},ue=async e=>{try{const r=w.join(e,".git","config"),t=(await j(r,"utf8")).toLowerCase();return t.includes("partialclone")||t.includes("promisor")||t.includes("partialclonefilter")}catch{return!1}},Ke=async(e,r,t,s)=>{const o=w.join(t,"archive.tar");await T(["archive","--remote",e,"--format=tar","--output",o,r],{timeoutMs:s}),await ce("tar",["-xf",o,"-C",t],{timeout:s??le,maxBuffer:1024*1024}),await O(o,{force:!0})},fe=e=>{if(!e||e.length===0)return!1;for(const r of e)if(!r||r.includes("**"))return!1;return!0},me=e=>{if(!e)return[];const r=e.map(t=>{const s=t.replace(/\\/g,"/"),o=s.indexOf("*");return(o===-1?s:s.slice(0,o)).replace(/\/+$|\/$/,"")});return Array.from(new Set(r.filter(t=>t.length>0)))},X=async(e,r)=>{const t=/^[0-9a-f]{7,40}$/i.test(e.ref),s=fe(e.include),o=["clone","--no-checkout","--depth",String(e.depth),"--recurse-submodules=no","--no-tags"];if(s&&o.push("--sparse"),t||(o.push("--single-branch"),e.ref!=="HEAD"&&o.push("--branch",e.ref)),o.push(e.repo,r),await T(o,{timeoutMs:e.timeoutMs}),s){const i=me(e.include);i.length>0&&await T(["-C",r,"sparse-checkout","set",...i],{timeoutMs:e.timeoutMs})}await T(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs})},Ve=async(e,r)=>{const t=Xe(e.repo),s=await be(t),o=/^[0-9a-f]{7,40}$/i.test(e.ref),i=fe(e.include),c=te();if(await R(c,{recursive:!0}),s&&await qe(t))if(await ue(t))await k(t),await X(e,t);else try{const u=["fetch","origin"];if(o)u.push("--depth",String(e.depth));else{const D=e.ref==="HEAD"?"HEAD":`${e.ref}:refs/remotes/origin/${e.ref}`;u.push(D,"--depth",String(e.depth))}await T(["-C",t,...u],{timeoutMs:e.timeoutMs})}catch{await k(t),await X(e,t)}else s&&await k(t),await X(e,t);await R(r,{recursive:!0});const a=["clone","--no-checkout","--depth",String(e.depth),"--recurse-submodules=no","--no-tags"];await ue(t)&&a.splice(2,0,"--filter=blob:none"),i&&a.push("--sparse"),o||(a.push("--single-branch"),e.ref!=="HEAD"&&a.push("--branch",e.ref));const n=Fe(t).href;if(a.push(n,r),await T(a,{timeoutMs:e.timeoutMs,allowFileProtocol:!0}),i){const u=me(e.include);u.length>0&&await T(["-C",r,"sparse-checkout","set",...u],{timeoutMs:e.timeoutMs,allowFileProtocol:!0})}await T(["-C",r,"checkout","--quiet","--detach",e.resolvedCommit],{timeoutMs:e.timeoutMs,allowFileProtocol:!0})},Qe=async e=>{const r=await B(w.join(Z(),`docs-cache-${e.sourceId}-`));try{return await Ke(e.repo,e.resolvedCommit,r,e.timeoutMs),r}catch(t){throw await k(r),t}},Ze=async e=>{W(e.sourceId,"sourceId");try{const r=await Qe(e);return{repoDir:r,cleanup:async()=>{await k(r)}}}catch{const r=await B(w.join(Z(),`docs-cache-${e.sourceId}-`));try{return await Ve(e,r),{repoDir:r,cleanup:async()=>{await k(r)}}}catch(t){throw await k(r),t}}},H=e=>Y(e),q=Number(process.env.DOCS_CACHE_STREAM_THRESHOLD_MB??"2"),et=Number.isFinite(q)&&q>0?Math.floor(q*1024*1024):1024*1024,tt=(e,r)=>{const t=w.resolve(e);if(!w.resolve(r).startsWith(t+w.sep))throw new Error(`Path traversal detected: ${r}`)},de=async e=>{try{return await z(e,se.O_RDONLY|se.O_NOFOLLOW)}catch(r){const t=r.code;if(t==="ELOOP")return null;if(t==="EINVAL"||t==="ENOSYS"||t==="ENOTSUP")return(await De(e)).isSymbolicLink()?null:await z(e,"r");throw r}},rt=async(e,r=5e3)=>{const t=Date.now();for(;Date.now()-t<r;)try{const s=await z(e,"wx");return{release:async()=>{await s.close(),await O(e,{force:!0})}}}catch(s){if(s.code!=="EEXIST")throw s;await new Promise(o=>setTimeout(o,100))}throw new Error(`Failed to acquire lock for ${e}.`)},ot=async e=>{W(e.sourceId,"sourceId");const r=Pe(e.cacheDir,e.sourceId);await R(e.cacheDir,{recursive:!0});const t=await B(w.join(e.cacheDir,`.tmp-${e.sourceId}-`));let s=null;const o=async()=>{const i=s;!i||i.closed||i.destroyed||await new Promise(c=>{const a=()=>{i.off("close",n),i.off("error",u),c()},n=()=>a(),u=()=>a();i.once("close",n),i.once("error",u);try{i.end()}catch{a()}})};try{const i=(await ie(e.include,{cwd:e.repoDir,ignore:[".git/**",...e.exclude??[]],dot:!0,onlyFiles:!0,followSymbolicLinks:!1})).map(f=>({relativePath:f,normalized:H(f)})).sort((f,h)=>f.normalized.localeCompare(h.normalized)),c=new Set;for(const{relativePath:f}of i)c.add(w.dirname(f));await Promise.all(Array.from(c,f=>R(w.join(t,f),{recursive:!0})));let a=0,n=0;const u=Math.max(1,Math.min(i.length,Math.max(8,Math.min(128,ke.cpus().length*8)))),D=w.join(t,J),g=oe(D,{encoding:"utf8"});s=g;const $=A("sha256"),S=async f=>new Promise((h,m)=>{const p=l=>{g.off("drain",d),m(l)},d=()=>{g.off("error",p),h()};g.once("error",p),g.write(f)?(g.off("error",p),h()):g.once("drain",d)});for(let f=0;f<i.length;f+=u){const h=i.slice(f,f+u),m=await Promise.all(h.map(async p=>{const d=w.join(e.repoDir,p.relativePath),l=await de(d);if(!l)return null;try{const P=await l.stat();if(!P.isFile())return null;const E=w.join(t,p.relativePath);if(tt(t,E),P.size>=et){const I=_e(d,{fd:l.fd,autoClose:!1}),F=oe(E);await Le(I,F)}else{const I=await l.readFile();await V(E,I)}return{path:p.normalized,size:P.size}}finally{await l.close()}}));for(const p of m){if(!p)continue;if(e.maxFiles!==void 0&&n+1>e.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${e.maxFiles}).`);if(a+=p.size,a>e.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${e.maxBytes}).`);const d=`${JSON.stringify(p)}
3
+ `;$.update(d),await S(d),n+=1}}await new Promise((f,h)=>{g.end(()=>f()),g.once("error",h)});const y=$.digest("hex"),v=async f=>{try{return await N(f),!0}catch{return!1}};return await(async(f,h)=>{const m=await rt(`${h}.lock`);try{const p=await v(h),d=`${h}.bak-${ve(8).toString("hex")}`;p&&await U(h,d);try{await U(f,h)}catch(l){if(p)try{await U(d,h)}catch(P){const E=P instanceof Error?P.message:String(P);process.stderr.write(`Warning: Failed to restore backup: ${E}
4
+ `)}throw l}p&&await O(d,{recursive:!0,force:!0})}finally{await m.release()}})(t,r.sourceDir),{bytes:a,fileCount:n,manifestSha256:y}}catch(i){try{await o()}catch{}throw await O(t,{recursive:!0,force:!0}),i}},st=async e=>{W(e.sourceId,"sourceId");const r=await ie(e.include,{cwd:e.repoDir,ignore:[".git/**",...e.exclude??[]],dot:!0,onlyFiles:!0,followSymbolicLinks:!1});r.sort((i,c)=>H(i).localeCompare(H(c)));let t=0,s=0;const o=A("sha256");for(const i of r){const c=H(i),a=w.join(e.repoDir,i),n=await de(a);if(n)try{const u=await n.stat();if(!u.isFile())continue;if(e.maxFiles!==void 0&&s+1>e.maxFiles)throw new Error(`Materialized content exceeds maxFiles (${e.maxFiles}).`);if(t+=u.size,t>e.maxBytes)throw new Error(`Materialized content exceeds maxBytes (${e.maxBytes}).`);const D=`${JSON.stringify({path:c,size:u.size})}
5
+ `;o.update(D),s+=1}finally{await n.close()}}return{bytes:t,fileCount:s,manifestSha256:o.digest("hex")}},it=async(e,r)=>{await r.rm(e,{recursive:!0,force:!0})},K=async e=>{const r=e.deps??{cp:Ce,mkdir:R,rm:O,symlink:$e,stderr:process.stderr},t=w.dirname(e.targetDir);await r.mkdir(t,{recursive:!0}),await it(e.targetDir,r);const s=process.platform==="win32"?"copy":"symlink";if((e.mode??s)==="copy"){await r.cp(e.sourceDir,e.targetDir,{recursive:!0});return}const o=process.platform==="win32"?"junction":"dir";try{await r.symlink(e.sourceDir,e.targetDir,o)}catch(i){const c=i.code;if(c&&new Set(["EPERM","EACCES","ENOTSUP","EINVAL"]).has(c)){if(e.explicitTargetMode){const a=i instanceof Error?i.message:String(i);r.stderr.write(`Warning: Failed to create symlink at ${e.targetDir}. Falling back to copy. ${a}
6
+ `)}await r.cp(e.sourceDir,e.targetDir,{recursive:!0});return}throw i}},at=e=>{const r={dirs:new Map,files:[]};for(const t of e){const s=t.split("/").filter(Boolean);if(s.length===0)continue;let o=r;for(const c of s.slice(0,-1)){let a=o.dirs.get(c);a||(a={dirs:new Map,files:[]},o.dirs.set(c,a)),o=a}const i=s[s.length-1];o.files.push({name:i,path:t})}return r},he=(e,r,t)=>{const s=" ".repeat(r),o=Array.from(e.dirs.keys()).sort(),i=[...e.files].sort((c,a)=>c.name.localeCompare(a.name));for(const c of o){t.push(`${s}- ${c}/`);const a=e.dirs.get(c);a&&he(a,r+1,t)}for(const c of i)t.push(`${s}- [${c.name}](./${c.path})`)},nt=(e,r,t)=>{const s=[...e].sort((a,n)=>a.localeCompare(n)),o=new Map;for(const a of s){const n=a.lastIndexOf("/"),u=n===-1?"":a.substring(0,n),D=n===-1?a:a.substring(n+1),g=o.get(u);g?g.push(D):o.set(u,[D])}const i=Array.from(o.keys()).sort(),c=[];c.push(`[${t}]`);for(const a of i){const n=o.get(a);if(!n)continue;const u=n.join(",");a===""?c.push(`root:{${u}}`):c.push(`${a}:{${u}}`)}r.push(c.join("|"))},ct=(e,r="compressed")=>{const t=[];if(r==="tree"){t.push(`# ${e.id} - Documentation`),t.push(""),t.push("## Files"),t.push("");const s=at(e.files);he(s,0,t)}else{const s=`${e.id} Docs Index`;nt(e.files,t,s)}return t.push(""),t.join(`
7
+ `)},lt=async e=>{const r=w.join(e,".manifest.jsonl");try{const t=await j(r,"utf8"),s=[];for(const o of t.split(`
8
+ `))if(o.trim()){const i=JSON.parse(o);i.path&&s.push(i.path)}return s}catch{return[]}},ut=async e=>{const r=new Map(e.sources.map(s=>[s.id,s])),t=new Map((e.results??[]).map(s=>[s.id,s]));for(const[s,o]of Object.entries(e.lock.sources)){const i=r.get(s);i?.targetDir&&Y(G(e.configPath,i.targetDir));const c=w.join(e.cacheDir,s);try{await N(c)}catch{continue}const a=await lt(c),n={id:s,repo:o.repo,ref:o.ref,resolvedCommit:o.resolvedCommit,fileCount:o.fileCount,cachePath:Y(w.join(e.cacheDir,s)),files:a},u=i?.toc,D=u!==!1;let g="compressed";typeof u=="string"&&(g=u);const $=w.join(c,xe);if(D){if(t.get(s)?.status==="up-to-date")try{await N($);continue}catch{}const S=ct(n,g);await V($,S,"utf8")}else try{await O($,{force:!0})}catch{}}},ft=e=>{if(e<1024)return`${e} B`;const r=["KB","MB","GB","TB"];let t=e,s=-1;for(;t>=1024&&s<r.length-1;)t/=1024,s+=1;return`${t.toFixed(1)} ${r[s]}`},L=async e=>{try{return await N(e),!0}catch{return!1}},pe=async(e,r)=>{const t=w.join(e,r);return await L(t)?await L(w.join(t,J)):!1},we=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()},mt=e=>{const r={include:we(e.include),exclude:we(e.exclude)},t=A("sha256");return t.update(JSON.stringify(r)),t.digest("hex")},ge=async(e,r={})=>{const{config:t,resolvedPath:s,sources:o}=await Ee(e.configPath),i=t.defaults??Te.defaults,c=Me(s,t.cacheDir??Oe,e.cacheDirOverride),a=Ie(s),n=await L(a);let u=null;n&&(u=await je(a));const D=r.resolveRemoteCommit??Ye,g=e.sourceFilter?.length?o.filter(S=>e.sourceFilter?.includes(S.id)):o,$=await Promise.all(g.map(async S=>{const y=u?.sources?.[S.id],v=S.include??i.include,f=S.exclude,h=mt({include:v,exclude:f});if(e.offline){const l=await pe(c,S.id);return{id:S.id,repo:y?.repo??S.repo,ref:y?.ref??S.ref??i.ref,resolvedCommit:y?.resolvedCommit??"offline",lockCommit:y?.resolvedCommit??null,lockRulesSha256:y?.rulesSha256,status:y&&l?"up-to-date":"missing",bytes:y?.bytes,fileCount:y?.fileCount,manifestSha256:y?.manifestSha256,rulesSha256:h}}const m=await D({repo:S.repo,ref:S.ref,allowHosts:i.allowHosts,timeoutMs:e.timeoutMs}),p=y?.resolvedCommit===m.resolvedCommit&&y?.rulesSha256===h,d=y?p?"up-to-date":"changed":"missing";return{id:S.id,repo:m.repo,ref:m.ref,resolvedCommit:m.resolvedCommit,lockCommit:y?.resolvedCommit??null,lockRulesSha256:y?.rulesSha256,status:d,bytes:y?.bytes,fileCount:y?.fileCount,manifestSha256:y?.manifestSha256,rulesSha256:h}}));return{config:t,configPath:s,cacheDir:c,lockPath:a,lockExists:n,lockData:u,results:$,sources:g,defaults:i}},dt=async()=>{const e=w.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"}},ht=async(e,r)=>{const t=await dt(),s=new Date().toISOString(),o={...r?.sources??{}};for(const i of e.results){const c=o[i.id];o[i.id]={repo:i.repo,ref:i.ref,resolvedCommit:i.resolvedCommit,bytes:i.bytes??c?.bytes??0,fileCount:i.fileCount??c?.fileCount??0,manifestSha256:i.manifestSha256??c?.manifestSha256??i.resolvedCommit,rulesSha256:i.rulesSha256??c?.rulesSha256,updatedAt:s}}return{version:1,generatedAt:s,toolVersion:t,sources:o}},ye=async(e,r={})=>{const t=process.hrtime.bigint();let s=0;const o=await ge(e,r);await R(o.cacheDir,{recursive:!0});const i=o.lockData,c=o.results.filter(n=>{const u=o.sources.find(D=>D.id===n.id);return n.status==="missing"&&(u?.required??!0)});if(e.failOnMiss&&c.length>0)throw new Error(`Missing required source(s): ${c.map(n=>n.id).join(", ")}.`);if(!e.lockOnly){const n=o.defaults,u=r.fetchSource??Ze,D=r.materializeSource??ot,g=new Map,$=async(v,f)=>{const h=v?.length?o.results.filter(m=>v.includes(m.id)):o.results;return(await Promise.all(h.map(async m=>{const p=o.sources.find(l=>l.id===m.id);if(!p)return null;if(f)return{result:m,source:p};let d=g.get(m.id);return d===void 0&&(d=await pe(o.cacheDir,m.id),g.set(m.id,d)),m.status!=="up-to-date"||!d?{result:m,source:p}:null}))).filter(Boolean)},S=async()=>{await Promise.all(o.sources.map(async v=>{if(!v.targetDir)return;const f=G(o.configPath,v.targetDir);await L(f)||await K({sourceDir:w.join(o.cacheDir,v.id),targetDir:f,mode:v.targetMode??n.targetMode,explicitTargetMode:v.targetMode!==void 0})}))},y=async v=>{const f=e.concurrency??4;let h=0;const m=async()=>{const p=v[h];if(!p||!p.source)return;h+=1;const{result:d,source:l}=p,P=o.lockData?.sources?.[l.id];e.json||C.step("Fetching",l.id);const E=await u({sourceId:l.id,repo:l.repo,ref:l.ref,resolvedCommit:d.resolvedCommit,cacheDir:o.cacheDir,depth:l.depth??n.depth,include:l.include??n.include,timeoutMs:e.timeoutMs});try{const I=w.join(o.cacheDir,l.id,J);if(d.status!=="up-to-date"&&P?.manifestSha256&&await L(I)){const b=await st({sourceId:l.id,repoDir:E.repoDir,cacheDir:o.cacheDir,include:l.include??n.include,exclude:l.exclude,maxBytes:l.maxBytes??n.maxBytes,maxFiles:l.maxFiles??n.maxFiles});if(b.manifestSha256===P.manifestSha256){d.bytes=b.bytes,d.fileCount=b.fileCount,d.manifestSha256=b.manifestSha256,d.status="up-to-date",e.json||C.item(M.success,l.id,"no content changes"),await m();return}}const F=await D({sourceId:l.id,repoDir:E.repoDir,cacheDir:o.cacheDir,include:l.include??n.include,exclude:l.exclude,maxBytes:l.maxBytes??n.maxBytes,maxFiles:l.maxFiles??n.maxFiles});if(l.targetDir){const b=G(o.configPath,l.targetDir);await K({sourceDir:w.join(o.cacheDir,l.id),targetDir:b,mode:l.targetMode??n.targetMode,explicitTargetMode:l.targetMode!==void 0})}d.bytes=F.bytes,d.fileCount=F.fileCount,d.manifestSha256=F.manifestSha256,e.json||C.item(M.success,l.id,`synced ${F.fileCount} files`)}finally{await E.cleanup()}await m()};await Promise.all(Array.from({length:Math.min(f,v.length)},m))};if(e.offline)await S();else{const v=await $();await y(v),await S()}if(!e.offline){const v=(await re({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(f=>!f.ok);if(v.length>0){const f=await $(v.map(m=>m.id),!0);f.length>0&&(await y(f),await S());const h=(await re({configPath:o.configPath,cacheDirOverride:o.cacheDir})).results.filter(m=>!m.ok);if(h.length>0&&(s+=1,!e.json)){const m=h.map(p=>`${p.id} (${p.issues.join("; ")})`).join(", ");C.line(`${M.warn} Verify failed for ${h.length} source(s): ${m}`)}}}}const a=await ht(o,i);if(await Re(o.lockPath,a),!e.json){const n=Number(process.hrtime.bigint()-t)/1e6,u=o.results.reduce((g,$)=>g+($.bytes??0),0),D=o.results.reduce((g,$)=>g+($.fileCount??0),0);C.line(`${M.info} Completed in ${n.toFixed(0)}ms \xB7 ${ft(u)} \xB7 ${D} files${s?` \xB7 ${s} warning${s===1?"":"s"}`:""}`)}return await ut({cacheDir:o.cacheDir,configPath:o.configPath,lock:a,sources:o.sources,results:o.results}),o.lockExists=!0,o},Se=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){C.line(`${M.info} No sources to sync.`);return}C.line(`${M.info} ${e.results.length} sources (${r.upToDate} up-to-date, ${r.changed} changed, ${r.missing} missing)`);for(const t of e.results){const s=C.hash(t.resolvedCommit),o=C.hash(t.lockCommit),i=!!t.lockRulesSha256&&!!t.rulesSha256&&t.lockRulesSha256!==t.rulesSha256;if(t.status==="up-to-date"){C.item(M.success,t.id,`${x.dim("up-to-date")} ${x.gray(s)}`);continue}if(t.status==="changed"){if(t.lockCommit===t.resolvedCommit&&i){C.item(M.warn,t.id,`${x.dim("rules changed")} ${x.gray(s)}`);continue}C.item(M.warn,t.id,`${x.dim("changed")} ${x.gray(o)} ${x.dim("->")} ${x.gray(s)}`);continue}C.item(M.warn,t.id,`${x.dim("missing")} ${x.gray(s)}`)}},pt={__proto__:null,getSyncPlan:ge,printSyncPlan:Se,runSync:ye};export{K as a,Se as b,ye as c,ae as e,ne as p,_ as r,pt as s};
9
9
  //# sourceMappingURL=sync.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.3.0",
5
+ "version": "0.3.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",