Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ All fields in `defaults` apply to all sources unless overridden per-source.
| `targetMode` | How to link or copy from the cache to the destination. Default: `"symlink"` on Unix, `"copy"` on Windows. |
| `depth` | Git clone depth. Default: `1`. |
| `required` | Whether missing sources should fail. Default: `true`. |
| `maxBytes` | Maximum total bytes to materialize. Default: `200000000`. |
| `maxBytes` | Maximum total bytes to materialize. Default: `200000000` (200 MB). |
| `maxFiles` | Maximum total files to materialize. |
| `allowHosts` | Allowed Git hosts. Default: `["github.com", "gitlab.com"]`. |
| `toc` | Generate per-source `TOC.md` listing all documentation files. Default: `true`. |
| `timeoutMs` | Git operation timeout in milliseconds. Default: `120000` (2 minutes). |
Comment thread
fbosch marked this conversation as resolved.
Outdated

### Source options

Expand All @@ -123,6 +124,7 @@ All fields in `defaults` apply to all sources unless overridden per-source.
| `maxBytes` | Maximum total bytes to materialize. |
| `maxFiles` | Maximum total files to materialize. |
| `toc` | Generate per-source `TOC.md` listing all documentation files. |
| `timeoutMs` | Git operation timeout in milliseconds. |

> **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`.
Comment thread
fbosch marked this conversation as resolved.
Outdated

Expand Down
28 changes: 26 additions & 2 deletions docs.config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,19 @@
}
},
"toc": {
"type": "boolean"
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"enum": ["tree", "compressed"]
}
]
},
"tocFormat": {
"type": "string",
"enum": ["tree", "compressed"]
}
},
"additionalProperties": false
Expand Down Expand Up @@ -146,7 +158,19 @@
"additionalProperties": false
},
"toc": {
"type": "boolean"
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"enum": ["tree", "compressed"]
}
]
},
"tocFormat": {
"type": "string",
"enum": ["tree", "compressed"]
}
},
"required": ["id", "repo"],
Expand Down
1 change: 1 addition & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { cleanCache } from "./clean";
export { cleanGitCache } from "./clean-git-cache";
export { parseArgs } from "./cli/parse-args";
export { loadConfig } from "./config";
export { redactRepoUrl } from "./git/redact";
Expand Down
99 changes: 99 additions & 0 deletions src/clean-git-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { access, readdir, rm, stat } from "node:fs/promises";
import { homedir } from "node:os";
import path from "node:path";

// Get platform-specific cache directory
const getCacheBaseDir = (): string => {
const home = homedir();
switch (process.platform) {
case "darwin":
return path.join(home, "Library", "Caches");
case "win32":
return process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
default:
// Linux and other Unix-like systems (XDG Base Directory)
return process.env.XDG_CACHE_HOME || path.join(home, ".cache");
}
};

const resolveGitCacheDir = () =>
process.env.DOCS_CACHE_GIT_DIR ||
path.join(getCacheBaseDir(), "docs-cache-git");
Comment thread
fbosch marked this conversation as resolved.
Outdated

const exists = async (filePath: string): Promise<boolean> => {
try {
await access(filePath);
return true;
} catch {
return false;
}
};

const getDirSize = async (dirPath: string): Promise<number> => {
try {
const entries = await readdir(dirPath, { withFileTypes: true });
let totalSize = 0;

for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
totalSize += await getDirSize(fullPath);
} else {
const stats = await stat(fullPath);
totalSize += stats.size;
}
}

return totalSize;
} catch {
return 0;
}
};

const countCachedRepos = async (cacheDir: string): Promise<number> => {
try {
const entries = await readdir(cacheDir);
return entries.length;
} catch {
return 0;
}
};

export type CleanGitCacheResult = {
removed: boolean;
cacheDir: string;
repoCount?: number;
bytesFreed?: number;
};

export type CleanGitCacheOptions = {
json?: boolean;
};

export const cleanGitCache = async (
options: CleanGitCacheOptions = {},
): Promise<CleanGitCacheResult> => {
const cacheDir = resolveGitCacheDir();
const cacheExists = await exists(cacheDir);

if (!cacheExists) {
return {
removed: false,
cacheDir,
};
}

// Get stats before removal
const repoCount = await countCachedRepos(cacheDir);
const bytesFreed = await getDirSize(cacheDir);

// Remove the cache directory
await rm(cacheDir, { recursive: true, force: true });

return {
removed: true,
cacheDir,
repoCount,
bytesFreed,
};
};
42 changes: 34 additions & 8 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ const HELP_TEXT = `
Usage: ${CLI_NAME} <command> [options]

Commands:
add Add sources to the config (supports github:org/repo#ref)
remove Remove sources from the config and targets
sync Synchronize cache with config
status Show cache status
clean Remove cache
prune Remove unused data
verify Validate cache integrity
init Create a new config interactively
add Add sources to the config (supports github:org/repo#ref)
remove Remove sources from the config and targets
sync Synchronize cache with config
status Show cache status
clean Remove project cache
clean-cache Clear global git cache
prune Remove unused data
verify Validate cache integrity
init Create a new config interactively

Global options:
--source <repo> (add only)
Expand Down Expand Up @@ -235,6 +236,31 @@ const runCommand = async (
}
return;
}
if (command === "clean-cache") {
const { cleanGitCache } = await import("../clean-git-cache");
const result = await cleanGitCache({
json: options.json,
});
if (options.json) {
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
} else if (result.removed) {
const sizeInMB = result.bytesFreed
? `${(result.bytesFreed / 1024 / 1024).toFixed(2)} MB`
: "unknown size";
const repoLabel = result.repoCount
? ` (${result.repoCount} cached repositor${result.repoCount === 1 ? "y" : "ies"})`
: "";
Comment thread
fbosch marked this conversation as resolved.
Outdated
ui.line(
`${symbols.success} Cleared global git cache${repoLabel}: ${sizeInMB} freed`,
);
ui.line(`${symbols.info} Cache location: ${ui.path(result.cacheDir)}`);
} else {
ui.line(
`${symbols.info} Global git cache already empty at ${ui.path(result.cacheDir)}`,
);
}
return;
}
if (command === "prune") {
const { pruneCache } = await import("../prune");
const result = await pruneCache({
Expand Down
1 change: 1 addition & 0 deletions src/cli/parse-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const COMMANDS = [
"sync",
"status",
"clean",
"clean-cache",
"prune",
"verify",
"init",
Expand Down
7 changes: 5 additions & 2 deletions src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { z } from "zod";

export const TargetModeSchema = z.enum(["symlink", "copy"]);
export const CacheModeSchema = z.enum(["materialize"]);
export const TocFormatSchema = z.enum(["tree", "compressed"]);
export const IntegritySchema = z
.object({
type: z.enum(["commit", "manifest"]),
Expand All @@ -20,7 +21,8 @@ export const DefaultsSchema = z
maxBytes: z.number().min(1),
maxFiles: z.number().min(1).optional(),
allowHosts: z.array(z.string().min(1)).min(1),
toc: z.boolean().optional(),
toc: z.union([z.boolean(), TocFormatSchema]).optional(),
tocFormat: TocFormatSchema.optional(),
})
.strict();

Expand All @@ -39,7 +41,8 @@ export const SourceSchema = z
maxBytes: z.number().min(1).optional(),
maxFiles: z.number().min(1).optional(),
integrity: IntegritySchema.optional(),
toc: z.boolean().optional(),
toc: z.union([z.boolean(), TocFormatSchema]).optional(),
tocFormat: TocFormatSchema.optional(),
})
.strict();

Expand Down
Loading
Loading