docs-cache 0.4.3 → 0.5.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.
Files changed (90) hide show
  1. package/dist/cli.mjs +13 -13
  2. package/dist/esm/api.d.ts +14 -0
  3. package/dist/esm/api.mjs +14 -0
  4. package/dist/esm/cache/cache-layout.d.ts +1 -0
  5. package/dist/esm/cache/cache-layout.mjs +12 -0
  6. package/dist/esm/cache/lock.d.ts +21 -0
  7. package/dist/esm/cache/lock.mjs +91 -0
  8. package/dist/esm/cache/manifest.d.ts +11 -0
  9. package/dist/esm/cache/manifest.mjs +68 -0
  10. package/dist/esm/cache/materialize.d.ts +26 -0
  11. package/dist/esm/cache/materialize.mjs +442 -0
  12. package/dist/esm/cache/targets.d.ts +19 -0
  13. package/dist/esm/cache/targets.mjs +66 -0
  14. package/dist/esm/cache/toc.d.ts +12 -0
  15. package/dist/esm/cache/toc.mjs +167 -0
  16. package/dist/esm/cli/exit-code.d.ts +11 -0
  17. package/dist/esm/cli/exit-code.mjs +5 -0
  18. package/dist/esm/cli/index.d.ts +5 -0
  19. package/dist/esm/cli/index.mjs +345 -0
  20. package/dist/esm/cli/live-output.d.ts +12 -0
  21. package/dist/esm/cli/live-output.mjs +30 -0
  22. package/dist/esm/cli/parse-args.d.ts +13 -0
  23. package/dist/esm/cli/parse-args.mjs +295 -0
  24. package/dist/esm/cli/run.d.ts +1 -0
  25. package/dist/esm/cli/run.mjs +2 -0
  26. package/dist/esm/cli/task-reporter.d.ts +32 -0
  27. package/dist/esm/cli/task-reporter.mjs +122 -0
  28. package/dist/esm/cli/types.d.ts +51 -0
  29. package/dist/esm/cli/types.mjs +0 -0
  30. package/dist/esm/cli/ui.d.ts +21 -0
  31. package/dist/esm/cli/ui.mjs +64 -0
  32. package/dist/esm/commands/add.d.ts +20 -0
  33. package/dist/esm/commands/add.mjs +81 -0
  34. package/dist/esm/commands/clean-git-cache.d.ts +10 -0
  35. package/dist/esm/commands/clean-git-cache.mjs +48 -0
  36. package/dist/esm/commands/clean.d.ts +10 -0
  37. package/dist/esm/commands/clean.mjs +27 -0
  38. package/dist/esm/commands/init.d.ts +19 -0
  39. package/dist/esm/commands/init.mjs +179 -0
  40. package/dist/esm/commands/prune.d.ts +11 -0
  41. package/dist/esm/commands/prune.mjs +52 -0
  42. package/dist/esm/commands/remove.d.ts +12 -0
  43. package/dist/esm/commands/remove.mjs +87 -0
  44. package/dist/esm/commands/status.d.ts +16 -0
  45. package/dist/esm/commands/status.mjs +78 -0
  46. package/dist/esm/commands/sync.d.ts +33 -0
  47. package/dist/esm/commands/sync.mjs +730 -0
  48. package/dist/esm/commands/verify.d.ts +11 -0
  49. package/dist/esm/commands/verify.mjs +120 -0
  50. package/dist/esm/config/index.d.ts +15 -0
  51. package/dist/esm/config/index.mjs +196 -0
  52. package/dist/esm/config/io.d.ts +30 -0
  53. package/dist/esm/config/io.mjs +112 -0
  54. package/dist/esm/config/schema.d.ts +171 -0
  55. package/dist/esm/config/schema.mjs +69 -0
  56. package/dist/esm/errors.d.ts +3 -0
  57. package/dist/esm/errors.mjs +2 -0
  58. package/dist/esm/git/cache-dir.d.ts +16 -0
  59. package/dist/esm/git/cache-dir.mjs +23 -0
  60. package/dist/esm/git/fetch-source.d.ts +19 -0
  61. package/dist/esm/git/fetch-source.mjs +477 -0
  62. package/dist/esm/git/redact.d.ts +1 -0
  63. package/dist/esm/git/redact.mjs +4 -0
  64. package/dist/esm/git/resolve-remote.d.ts +15 -0
  65. package/dist/esm/git/resolve-remote.mjs +87 -0
  66. package/dist/esm/git/resolve-repo.d.ts +5 -0
  67. package/dist/esm/git/resolve-repo.mjs +52 -0
  68. package/dist/esm/gitignore.d.ts +18 -0
  69. package/dist/esm/gitignore.mjs +80 -0
  70. package/dist/esm/paths.d.ts +8 -0
  71. package/dist/esm/paths.mjs +34 -0
  72. package/dist/esm/source-id.d.ts +1 -0
  73. package/dist/esm/source-id.mjs +29 -0
  74. package/dist/esm/types/sync.d.ts +25 -0
  75. package/dist/esm/types/sync.mjs +0 -0
  76. package/package.json +138 -91
  77. package/dist/chunks/add.mjs +0 -3
  78. package/dist/chunks/clean-git-cache.mjs +0 -2
  79. package/dist/chunks/clean.mjs +0 -2
  80. package/dist/chunks/init.mjs +0 -3
  81. package/dist/chunks/prune.mjs +0 -2
  82. package/dist/chunks/remove.mjs +0 -3
  83. package/dist/chunks/status.mjs +0 -2
  84. package/dist/chunks/sync.mjs +0 -9
  85. package/dist/chunks/verify.mjs +0 -2
  86. package/dist/shared/docs-cache.BOr9BnyP.mjs +0 -5
  87. package/dist/shared/docs-cache.BSvQNKuf.mjs +0 -2
  88. package/dist/shared/docs-cache.CQiaFDb_.mjs +0 -7
  89. package/dist/shared/docs-cache.CaOcl4OS.mjs +0 -3
  90. package/dist/shared/docs-cache.kK1DPQIQ.mjs +0 -2
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import m from"node:path";import r from"node:process";import d from"picocolors";import{p as w,s as y,a as j,E as g,b as a,u as n}from"./shared/docs-cache.CQiaFDb_.mjs";import"cac";const f="docs-cache",O=`
2
- Usage: ${f} <command> [options]
1
+ import p from"node:path";import r from"node:process";import d from"picocolors";import{ExitCode as l}from"#cli/exit-code";import{parseArgs as w}from"#cli/parse-args";import{setSilentMode as y,setVerboseMode as v,symbols as a,ui as o}from"#cli/ui";const h="docs-cache",j=`
2
+ Usage: ${h} <command> [options]
3
3
 
4
4
  Commands:
5
5
  add Add sources to the config (supports github:org/repo#ref)
@@ -29,15 +29,15 @@ Add options:
29
29
  --target <dir>
30
30
  --target-dir <path>
31
31
  --id <id>
32
- `,u=()=>{r.stdout.write(O.trimStart())},$=o=>{r.stderr.write(`${a.error} ${o}
33
- `)},D=async o=>{const l=o.command,e=o.options;if(l==="add"){const{addSources:c}=await import("./chunks/add.mjs"),{runSync:i}=await import("./chunks/sync.mjs").then(function(s){return s.s});if(o.entries.length===0)throw new Error("Usage: docs-cache add [--source <repo> --target <dir>] <repo...>");const t=await c({configPath:e.config,entries:o.entries});if(e.offline?e.json||n.line(`${a.warn} Offline: skipped sync`):await i({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,sourceFilter:t.sources.map(s=>s.id),timeoutMs:e.timeoutMs,verbose:e.verbose}),e.json)r.stdout.write(`${JSON.stringify(t,null,2)}
34
- `);else{for(const s of t.sources){const h=s.repo.replace(/^https?:\/\//,"").replace(/\.git$/,""),v=s.targetDir?` ${d.dim("->")} ${d.magenta(s.targetDir)}`:"";n.item(a.success,s.id,`${d.blue(h)}${v}`)}t.skipped?.length&&n.line(`${a.warn} Skipped ${t.skipped.length} existing source${t.skipped.length===1?"":"s"}: ${t.skipped.join(", ")}`),n.line(`${a.info} Updated ${d.gray(m.relative(r.cwd(),t.configPath)||"docs.config.json")}`),t.gitignoreUpdated&&t.gitignorePath&&n.line(`${a.info} Updated ${d.gray(n.path(t.gitignorePath))}`)}return}if(l==="remove"){const{removeSources:c}=await import("./chunks/remove.mjs"),{pruneCache:i}=await import("./chunks/prune.mjs");if(o.ids.length===0)throw new Error("Usage: docs-cache remove <id...>");const t=await c({configPath:e.config,ids:o.ids});if(e.json)r.stdout.write(`${JSON.stringify(t,null,2)}
35
- `);else{if(t.removed.length>0&&n.line(`${a.success} Removed ${t.removed.length} source${t.removed.length===1?"":"s"}: ${t.removed.join(", ")}`),t.missing.length>0&&n.line(`${a.warn} Missing ${t.missing.length} source${t.missing.length===1?"":"s"}: ${t.missing.join(", ")}`),t.targetsRemoved.length>0){const s=t.targetsRemoved.map(h=>`${h.id} -> ${n.path(h.targetDir)}`).join(", ");n.line(`${a.success} Removed ${t.targetsRemoved.length} target${t.targetsRemoved.length===1?"":"s"}: ${s}`)}n.line(`${a.info} Updated ${d.gray(m.relative(r.cwd(),t.configPath)||"docs.config.json")}`)}e.prune&&await i({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});return}if(l==="status"){const{getStatus:c,printStatus:i}=await import("./chunks/status.mjs"),t=await c({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(t,null,2)}
36
- `):i(t);return}if(l==="clean"){const{cleanCache:c}=await import("./chunks/clean.mjs"),i=await c({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(i,null,2)}
37
- `):i.removed?n.line(`${a.success} Removed cache at ${n.path(i.cacheDir)}`):n.line(`${a.info} Cache already missing at ${n.path(i.cacheDir)}`);return}if(l==="clean-cache"){const{cleanGitCache:c}=await import("./chunks/clean-git-cache.mjs"),i=await c();if(e.json)r.stdout.write(`${JSON.stringify(i,null,2)}
38
- `);else if(i.removed){const t=i.bytesFreed!==void 0?`${(i.bytesFreed/1024/1024).toFixed(2)} MB`:"unknown size",s=i.repoCount!==void 0?` (${i.repoCount} cached repositor${i.repoCount===1?"y":"ies"})`:"";n.line(`${a.success} Cleared global git cache${s}: ${t} freed`),n.line(`${a.info} Cache location: ${n.path(i.cacheDir)}`)}else n.line(`${a.info} Global git cache already empty at ${n.path(i.cacheDir)}`);return}if(l==="prune"){const{pruneCache:c}=await import("./chunks/prune.mjs"),i=await c({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(i,null,2)}
39
- `):i.removed.length===0?n.line(`${a.info} No cache entries to prune.`):n.line(`${a.success} Pruned ${i.removed.length} cache entr${i.removed.length===1?"y":"ies"}: ${i.removed.join(", ")}`);return}if(l==="sync"){const{printSyncPlan:c,runSync:i}=await import("./chunks/sync.mjs").then(function(s){return s.s}),t=await i({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,timeoutMs:e.timeoutMs,verbose:e.verbose});e.json?r.stdout.write(`${JSON.stringify(t,null,2)}
40
- `):c(t);return}if(l==="verify"){const{printVerify:c,verifyCache:i}=await import("./chunks/verify.mjs").then(function(s){return s.a}),t=await i({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(t,null,2)}
41
- `):c(t),t.results.some(s=>!s.ok)&&r.exit(g.FatalError);return}if(l==="init"){const{initConfig:c}=await import("./chunks/init.mjs");if(e.config)throw new Error("Init does not accept --config. Use the project root.");const i=await c({cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(i,null,2)}
42
- `):(n.line(`${a.success} Wrote ${d.gray(n.path(i.configPath))}`),i.gitignoreUpdated&&i.gitignorePath&&n.line(`${a.info} Updated ${d.gray(n.path(i.gitignorePath))}`));return}n.line(`${f} ${l}: not implemented yet.`)};async function S(){try{r.on("uncaughtException",p),r.on("unhandledRejection",p);const o=w();y(o.options.silent),j(o.options.verbose),o.help&&(u(),r.exit(g.Success)),o.command||(u(),r.exit(g.InvalidArgument)),o.command!=="add"&&o.command!=="remove"&&o.positionals.length>0&&($(`${f}: unexpected arguments.`),u(),r.exit(g.InvalidArgument)),await D(o.parsed)}catch(o){p(o)}}function p(o){const l=o.message||String(o);$(l),r.exit(g.FatalError)}export{f as CLI_NAME,S as main};
32
+ `,f=()=>{r.stdout.write(j.trimStart())},m=i=>{r.stderr.write(`${a.error} ${i}
33
+ `)},O=async i=>{const e=i.options,{addSources:s}=await import("#commands/add"),{runSync:n}=await import("#commands/sync");if(i.entries.length===0)throw new Error("Usage: docs-cache add [--source <repo> --target <dir>] <repo...>");const t=await s({configPath:e.config,entries:i.entries});if(e.offline?e.json||o.line(`${a.warn} Offline: skipped sync`):await n({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,sourceFilter:t.sources.map(c=>c.id),timeoutMs:e.timeoutMs,verbose:e.verbose}),e.json){r.stdout.write(`${JSON.stringify(t,null,2)}
34
+ `);return}for(const c of t.sources){const g=c.repo.replace(/^https?:\/\//,"").replace(/\.git$/,""),$=c.targetDir?` ${d.dim("->")} ${d.magenta(c.targetDir)}`:"";o.item(a.success,c.id,`${d.blue(g)}${$}`)}t.skipped?.length&&o.line(`${a.warn} Skipped ${t.skipped.length} existing source${t.skipped.length===1?"":"s"}: ${t.skipped.join(", ")}`),o.line(`${a.info} Updated ${d.gray(p.relative(r.cwd(),t.configPath)||"docs.config.json")}`),t.gitignoreUpdated&&t.gitignorePath&&o.line(`${a.info} Updated ${d.gray(o.path(t.gitignorePath))}`)},D=async i=>{const e=i.options,{removeSources:s}=await import("#commands/remove"),{pruneCache:n}=await import("#commands/prune");if(i.ids.length===0)throw new Error("Usage: docs-cache remove <id...>");const t=await s({configPath:e.config,ids:i.ids});if(e.json){r.stdout.write(`${JSON.stringify(t,null,2)}
35
+ `);return}if(t.removed.length>0&&o.line(`${a.success} Removed ${t.removed.length} source${t.removed.length===1?"":"s"}: ${t.removed.join(", ")}`),t.missing.length>0&&o.line(`${a.warn} Missing ${t.missing.length} source${t.missing.length===1?"":"s"}: ${t.missing.join(", ")}`),t.targetsRemoved.length>0){const c=t.targetsRemoved.map(g=>`${g.id} -> ${o.path(g.targetDir)}`).join(", ");o.line(`${a.success} Removed ${t.targetsRemoved.length} target${t.targetsRemoved.length===1?"":"s"}: ${c}`)}o.line(`${a.info} Updated ${d.gray(p.relative(r.cwd(),t.configPath)||"docs.config.json")}`),e.prune&&await n({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json})},S=async i=>{const e=i.options,{getStatus:s,printStatus:n}=await import("#commands/status"),t=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});if(e.json){r.stdout.write(`${JSON.stringify(t,null,2)}
36
+ `);return}n(t)},P=async i=>{const e=i.options,{cleanCache:s}=await import("#commands/clean"),n=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});if(e.json){r.stdout.write(`${JSON.stringify(n,null,2)}
37
+ `);return}if(n.removed){o.line(`${a.success} Removed cache at ${o.path(n.cacheDir)}`);return}o.line(`${a.info} Cache already missing at ${o.path(n.cacheDir)}`)},C=async i=>{const e=i.options,{cleanGitCache:s}=await import("#commands/clean-git-cache"),n=await s();if(e.json){r.stdout.write(`${JSON.stringify(n,null,2)}
38
+ `);return}if(!n.removed){o.line(`${a.info} Global git cache already empty at ${o.path(n.cacheDir)}`);return}const t=n.bytesFreed!==void 0?`${(n.bytesFreed/1024/1024).toFixed(2)} MB`:"unknown size",c=n.repoCount!==void 0?` (${n.repoCount} cached repositor${n.repoCount===1?"y":"ies"})`:"";o.line(`${a.success} Cleared global git cache${c}: ${t} freed`),o.line(`${a.info} Cache location: ${o.path(n.cacheDir)}`)},b=async i=>{const e=i.options,{pruneCache:s}=await import("#commands/prune"),n=await s({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});if(e.json){r.stdout.write(`${JSON.stringify(n,null,2)}
39
+ `);return}if(n.removed.length===0){o.line(`${a.info} No cache entries to prune.`);return}o.line(`${a.success} Pruned ${n.removed.length} cache entr${n.removed.length===1?"y":"ies"}: ${n.removed.join(", ")}`)},k=async i=>{const e=i.options,{printSyncPlan:s,runSync:n}=await import("#commands/sync"),t=await n({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json,lockOnly:e.lockOnly,offline:e.offline,failOnMiss:e.failOnMiss,timeoutMs:e.timeoutMs,verbose:e.verbose});if(e.json){r.stdout.write(`${JSON.stringify(t,null,2)}
40
+ `);return}s(t)},M=async i=>{const e=i.options,{printVerify:s,verifyCache:n}=await import("#commands/verify"),t=await n({configPath:e.config,cacheDirOverride:e.cacheDir,json:e.json});e.json?r.stdout.write(`${JSON.stringify(t,null,2)}
41
+ `):s(t),t.results.some(c=>!c.ok)&&r.exit(l.FatalError)},x=async i=>{const e=i.options,{initConfig:s}=await import("#commands/init");if(e.config)throw new Error("Init does not accept --config. Use the project root.");const n=await s({cacheDirOverride:e.cacheDir,json:e.json});if(e.json){r.stdout.write(`${JSON.stringify(n,null,2)}
42
+ `);return}o.line(`${a.success} Wrote ${d.gray(o.path(n.configPath))}`),n.gitignoreUpdated&&n.gitignorePath&&o.line(`${a.info} Updated ${d.gray(o.path(n.gitignorePath))}`)},N=async i=>{switch(i.command){case"add":await O(i);return;case"remove":await D(i);return;case"status":await S(i);return;case"clean":await P(i);return;case"clean-cache":await C(i);return;case"prune":await b(i);return;case"sync":await k(i);return;case"verify":await M(i);return;case"init":await x(i);return;default:o.line(`${h} ${i.command}: not implemented yet.`)}};async function R(){try{r.on("uncaughtException",u),r.on("unhandledRejection",u);const i=w();y(i.options.silent),v(i.options.verbose),i.help&&(f(),r.exit(l.Success)),i.command||(f(),r.exit(l.InvalidArgument)),i.command!=="add"&&i.command!=="remove"&&i.positionals.length>0&&(m(`${h}: unexpected arguments.`),f(),r.exit(l.InvalidArgument)),await N(i.parsed)}catch(i){u(i)}}function u(i){const e=i instanceof Error?i.message:String(i);m(e),r.exit(l.FatalError)}export{h as CLI_NAME,R as main};
43
43
  //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1,14 @@
1
+ export { DEFAULT_LOCK_FILENAME } from "#cache/lock";
2
+ export { applyTargetDir } from "#cache/targets";
3
+ export { parseArgs } from "#cli/parse-args";
4
+ export { cleanCache } from "#commands/clean";
5
+ export { cleanGitCache } from "#commands/clean-git-cache";
6
+ export { initConfig } from "#commands/init";
7
+ export { pruneCache } from "#commands/prune";
8
+ export { removeSources } from "#commands/remove";
9
+ export { printSyncPlan, runSync } from "#commands/sync";
10
+ export { verifyCache } from "#commands/verify";
11
+ export { loadConfig } from "#config";
12
+ export { redactRepoUrl } from "#git/redact";
13
+ export { enforceHostAllowlist, parseLsRemote } from "#git/resolve-remote";
14
+ export { resolveRepoInput } from "#git/resolve-repo";
@@ -0,0 +1,14 @@
1
+ export { DEFAULT_LOCK_FILENAME } from "#cache/lock";
2
+ export { applyTargetDir } from "#cache/targets";
3
+ export { parseArgs } from "#cli/parse-args";
4
+ export { cleanCache } from "#commands/clean";
5
+ export { cleanGitCache } from "#commands/clean-git-cache";
6
+ export { initConfig } from "#commands/init";
7
+ export { pruneCache } from "#commands/prune";
8
+ export { removeSources } from "#commands/remove";
9
+ export { printSyncPlan, runSync } from "#commands/sync";
10
+ export { verifyCache } from "#commands/verify";
11
+ export { loadConfig } from "#config";
12
+ export { redactRepoUrl } from "#git/redact";
13
+ export { enforceHostAllowlist, parseLsRemote } from "#git/resolve-remote";
14
+ export { resolveRepoInput } from "#git/resolve-repo";
@@ -0,0 +1 @@
1
+ export declare const ensureCacheLayout: (cacheDir: string, sourceIds: string[]) => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { getCacheLayout } from "#core/paths";
3
+ export const ensureCacheLayout = async (cacheDir, sourceIds) => {
4
+ const base = getCacheLayout(cacheDir, "tmp");
5
+ await mkdir(base.cacheDir, { recursive: true });
6
+ await Promise.all(
7
+ sourceIds.map(async (sourceId) => {
8
+ const layout = getCacheLayout(cacheDir, sourceId);
9
+ await mkdir(layout.sourceDir, { recursive: true });
10
+ })
11
+ );
12
+ };
@@ -0,0 +1,21 @@
1
+ export interface DocsCacheLockSource {
2
+ repo: string;
3
+ ref: string;
4
+ resolvedCommit: string;
5
+ bytes: number;
6
+ fileCount: number;
7
+ manifestSha256: string;
8
+ rulesSha256?: string;
9
+ updatedAt: string;
10
+ }
11
+ export interface DocsCacheLock {
12
+ version: 1;
13
+ generatedAt: string;
14
+ toolVersion: string;
15
+ sources: Record<string, DocsCacheLockSource>;
16
+ }
17
+ export declare const DEFAULT_LOCK_FILENAME = "docs-lock.json";
18
+ export declare const validateLock: (input: unknown) => DocsCacheLock;
19
+ export declare const resolveLockPath: (configPath: string) => any;
20
+ export declare const readLock: (lockPath: string) => Promise<DocsCacheLock>;
21
+ export declare const writeLock: (lockPath: string, lock: DocsCacheLock) => Promise<void>;
@@ -0,0 +1,91 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const DEFAULT_LOCK_FILENAME = "docs-lock.json";
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ const assertString = (value, label) => {
6
+ if (typeof value !== "string" || value.length === 0) {
7
+ throw new Error(`${label} must be a non-empty string.`);
8
+ }
9
+ return value;
10
+ };
11
+ const assertNumber = (value, label) => {
12
+ if (typeof value !== "number" || Number.isNaN(value)) {
13
+ throw new Error(`${label} must be a number.`);
14
+ }
15
+ return value;
16
+ };
17
+ const assertPositiveNumber = (value, label) => {
18
+ const numberValue = assertNumber(value, label);
19
+ if (numberValue < 0) {
20
+ throw new Error(`${label} must be zero or greater.`);
21
+ }
22
+ return numberValue;
23
+ };
24
+ export const validateLock = (input) => {
25
+ if (!isRecord(input)) {
26
+ throw new Error("Lock file must be a JSON object.");
27
+ }
28
+ const version = input.version;
29
+ if (version !== 1) {
30
+ throw new Error("Lock file version must be 1.");
31
+ }
32
+ const generatedAt = assertString(input.generatedAt, "generatedAt");
33
+ const toolVersion = assertString(input.toolVersion, "toolVersion");
34
+ if (!isRecord(input.sources)) {
35
+ throw new Error("sources must be an object.");
36
+ }
37
+ const sources = {};
38
+ for (const [key, value] of Object.entries(input.sources)) {
39
+ if (!isRecord(value)) {
40
+ throw new Error(`sources.${key} must be an object.`);
41
+ }
42
+ sources[key] = {
43
+ repo: assertString(value.repo, `sources.${key}.repo`),
44
+ ref: assertString(value.ref, `sources.${key}.ref`),
45
+ resolvedCommit: assertString(
46
+ value.resolvedCommit,
47
+ `sources.${key}.resolvedCommit`
48
+ ),
49
+ bytes: assertPositiveNumber(value.bytes, `sources.${key}.bytes`),
50
+ fileCount: assertPositiveNumber(
51
+ value.fileCount,
52
+ `sources.${key}.fileCount`
53
+ ),
54
+ manifestSha256: assertString(
55
+ value.manifestSha256,
56
+ `sources.${key}.manifestSha256`
57
+ ),
58
+ rulesSha256: value.rulesSha256 === void 0 ? void 0 : assertString(value.rulesSha256, `sources.${key}.rulesSha256`),
59
+ updatedAt: assertString(value.updatedAt, `sources.${key}.updatedAt`)
60
+ };
61
+ }
62
+ return {
63
+ version: 1,
64
+ generatedAt,
65
+ toolVersion,
66
+ sources
67
+ };
68
+ };
69
+ export const resolveLockPath = (configPath) => path.resolve(path.dirname(configPath), DEFAULT_LOCK_FILENAME);
70
+ export const readLock = async (lockPath) => {
71
+ let raw;
72
+ try {
73
+ raw = await readFile(lockPath, "utf8");
74
+ } catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ throw new Error(`Failed to read lock file at ${lockPath}: ${message}`);
77
+ }
78
+ let parsed;
79
+ try {
80
+ parsed = JSON.parse(raw);
81
+ } catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ throw new Error(`Invalid JSON in ${lockPath}: ${message}`);
84
+ }
85
+ return validateLock(parsed);
86
+ };
87
+ export const writeLock = async (lockPath, lock) => {
88
+ const data = `${JSON.stringify(lock, null, 2)}
89
+ `;
90
+ await writeFile(lockPath, data, "utf8");
91
+ };
@@ -0,0 +1,11 @@
1
+ export type ManifestEntry = {
2
+ path: string;
3
+ size: number;
4
+ };
5
+ export declare const MANIFEST_FILENAME = ".manifest.jsonl";
6
+ export declare const readManifest: (sourceDir: string) => Promise<{
7
+ manifestPath: any;
8
+ entries: ManifestEntry[];
9
+ }>;
10
+ export declare const streamManifestEntries: (sourceDir: string) => AsyncGenerator<ManifestEntry>;
11
+ export declare const hasManifestEntries: (sourceDir: string) => Promise<boolean>;
@@ -0,0 +1,68 @@
1
+ import { createReadStream } from "node:fs";
2
+ import path from "node:path";
3
+ import readline from "node:readline";
4
+ const parseManifestEntry = (value) => {
5
+ if (!value || typeof value !== "object") {
6
+ throw new Error("Manifest entry must be an object.");
7
+ }
8
+ const record = value;
9
+ if (typeof record.path !== "string" || record.path.length === 0) {
10
+ throw new Error("Manifest entry path must be a non-empty string.");
11
+ }
12
+ if (typeof record.size !== "number" || Number.isNaN(record.size)) {
13
+ throw new Error("Manifest entry size must be a number.");
14
+ }
15
+ if (record.size < 0) {
16
+ throw new Error("Manifest entry size must be zero or greater.");
17
+ }
18
+ return { path: record.path, size: record.size };
19
+ };
20
+ export const MANIFEST_FILENAME = ".manifest.jsonl";
21
+ export const readManifest = async (sourceDir) => {
22
+ const manifestPath = path.join(sourceDir, MANIFEST_FILENAME);
23
+ const entries = [];
24
+ const stream = createReadStream(manifestPath, { encoding: "utf8" });
25
+ const lines = readline.createInterface({
26
+ input: stream,
27
+ crlfDelay: Infinity
28
+ });
29
+ try {
30
+ for await (const line of lines) {
31
+ const trimmed = line.trim();
32
+ if (!trimmed) {
33
+ continue;
34
+ }
35
+ entries.push(parseManifestEntry(JSON.parse(trimmed)));
36
+ }
37
+ return { manifestPath, entries };
38
+ } finally {
39
+ lines.close();
40
+ stream.destroy();
41
+ }
42
+ };
43
+ export const streamManifestEntries = async function* (sourceDir) {
44
+ const manifestPath = path.join(sourceDir, MANIFEST_FILENAME);
45
+ const stream = createReadStream(manifestPath, { encoding: "utf8" });
46
+ const lines = readline.createInterface({
47
+ input: stream,
48
+ crlfDelay: Infinity
49
+ });
50
+ try {
51
+ for await (const line of lines) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed) {
54
+ continue;
55
+ }
56
+ yield parseManifestEntry(JSON.parse(trimmed));
57
+ }
58
+ } finally {
59
+ lines.close();
60
+ stream.destroy();
61
+ }
62
+ };
63
+ export const hasManifestEntries = async (sourceDir) => {
64
+ for await (const _entry of streamManifestEntries(sourceDir)) {
65
+ return true;
66
+ }
67
+ return false;
68
+ };
@@ -0,0 +1,26 @@
1
+ type MaterializeParams = {
2
+ sourceId: string;
3
+ repoDir: string;
4
+ cacheDir: string;
5
+ include: string[];
6
+ exclude?: string[];
7
+ maxBytes: number;
8
+ maxFiles?: number;
9
+ ignoreHidden?: boolean;
10
+ unwrapSingleRootDir?: boolean;
11
+ json?: boolean;
12
+ progressLogger?: (message: string) => void;
13
+ progressThrottleMs?: number;
14
+ };
15
+ type ManifestStats = {
16
+ bytes: number;
17
+ fileCount: number;
18
+ manifestSha256: string;
19
+ };
20
+ export declare const materializeSource: (params: MaterializeParams) => Promise<{
21
+ bytes: number;
22
+ fileCount: number;
23
+ manifestSha256: string;
24
+ }>;
25
+ export declare const computeManifestHash: (params: MaterializeParams) => Promise<ManifestStats>;
26
+ export {};