Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pnpm-debug.log*
npm-debug.log*
yarn-debug.log*
docs.config.json
docs.lock
docs-lock.json
Comment thread
fbosch marked this conversation as resolved.
Outdated
coverage
TODO.md
.docs/
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pnpm.
## Cache layout

- Materialized sources live at `.docs/<id>/`.
- Lock file lives next to `docs.config.json` as `docs.lock`.
- Lock file lives next to `docs.config.json` as `docs-lock.json`.

## CLI architecture

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Documentation is cached in a gitignored location, exposed to agent and tool targ
## Features

- **Local only**: Cache lives in the directory `.docs` (or a custom location) and _should_ be gitignored.
- **Deterministic**: `docs.lock` pins commits and file metadata.
- **Deterministic**: `docs-lock.json` pins commits and file metadata.
- **Fast**: Local cache avoids network roundtrips after sync.
- **Flexible**: Cache full repos or just the subdirectories you need.

Expand Down
3 changes: 2 additions & 1 deletion scripts/benchmarks/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
verifyCache,
} from "../../dist/api.mjs";
import {
DEFAULT_LOCK_FILENAME,
readLock,
resolveLockPath,
validateLock,
Expand Down Expand Up @@ -286,7 +287,7 @@ const main = async () => {
() => {
const tempLock = path.join(
benchRoot,
`docs.lock.${Math.random().toString(36).slice(2)}`,
`${DEFAULT_LOCK_FILENAME}.${Math.random().toString(36).slice(2)}`,
);
return writeLock(tempLock, lockData);
},
Expand Down
1 change: 1 addition & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { loadConfig } from "./config";
export { redactRepoUrl } from "./git/redact";
export { enforceHostAllowlist, parseLsRemote } from "./git/resolve-remote";
export { initConfig } from "./init";
export { DEFAULT_LOCK_FILENAME } from "./lock";
export { pruneCache } from "./prune";
export { removeSources } from "./remove";
export { resolveRepoInput } from "./resolve-repo";
Expand Down
2 changes: 1 addition & 1 deletion src/lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface DocsCacheLock {
sources: Record<string, DocsCacheLockSource>;
}

export const DEFAULT_LOCK_FILENAME = "docs.lock";
export const DEFAULT_LOCK_FILENAME = "docs-lock.json";
Comment thread
fbosch marked this conversation as resolved.

const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
Expand Down
1 change: 0 additions & 1 deletion src/paths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "node:path";

export const DEFAULT_LOCK_FILENAME = "docs.lock";
export const DEFAULT_TOC_FILENAME = "TOC.md";

export const toPosixPath = (value: string) => value.replace(/\\/g, "/");
Expand Down
4 changes: 2 additions & 2 deletions src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { access } from "node:fs/promises";
import pc from "picocolors";
import { symbols, ui } from "./cli/ui";
import { DEFAULT_CACHE_DIR, loadConfig } from "./config";
import { readLock, resolveLockPath } from "./lock";
import { DEFAULT_LOCK_FILENAME, readLock, resolveLockPath } from "./lock";
import { getCacheLayout, resolveCacheDir } from "./paths";

type StatusOptions = {
Expand Down Expand Up @@ -81,7 +81,7 @@ export const printStatus = (status: Awaited<ReturnType<typeof getStatus>>) => {
: pc.yellow("missing");

ui.header("Cache", `${relCache} (${cacheState})`);
ui.header("Lock", `docs.lock (${lockState})`);
ui.header("Lock", `${DEFAULT_LOCK_FILENAME} (${lockState})`);

if (status.sources.length === 0) {
ui.line();
Expand Down
10 changes: 5 additions & 5 deletions tests/edge-cases.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

import { loadConfig } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, loadConfig } from "../dist/api.mjs";

const writeConfig = async (data) => {
const tmpRoot = path.join(
Expand Down Expand Up @@ -221,7 +221,7 @@ test("lock file with invalid version", async () => {
`docs-cache-lock-ver-${Date.now().toString(36)}`,
);
await mkdir(tmpRoot, { recursive: true });
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

const invalidLock = {
version: 2,
Expand All @@ -248,7 +248,7 @@ test("lock file with missing required fields", async () => {
`docs-cache-lock-miss-${Date.now().toString(36)}`,
);
await mkdir(tmpRoot, { recursive: true });
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

const invalidLock = {
version: 1,
Expand All @@ -271,7 +271,7 @@ test("lock file with negative bytes", async () => {
`docs-cache-lock-neg-${Date.now().toString(36)}`,
);
await mkdir(tmpRoot, { recursive: true });
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

const invalidLock = {
version: 1,
Expand Down Expand Up @@ -306,7 +306,7 @@ test("lock file with corrupted JSON", async () => {
`docs-cache-lock-corrupt-${Date.now().toString(36)}`,
);
await mkdir(tmpRoot, { recursive: true });
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

await writeFile(lockPath, '{"version": 1, invalid', "utf8");

Expand Down
17 changes: 0 additions & 17 deletions tests/fixtures/docs.lock

This file was deleted.

6 changes: 6 additions & 0 deletions tests/fixtures/empty.docs-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"version": 1,
"generatedAt": "2026-01-30T12:00:00+01:00",
"toolVersion": "0.1.0",
"sources": {}
}
6 changes: 0 additions & 6 deletions tests/fixtures/empty.docs.lock

This file was deleted.

12 changes: 9 additions & 3 deletions tests/integration-real-repos.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

import { runSync } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, runSync } from "../dist/api.mjs";

const shouldRun = () => process.env.DOCS_CACHE_INTEGRATION === "1";

Expand Down Expand Up @@ -48,7 +48,10 @@ test("integration syncs a real repository", async (t) => {
offline: false,
failOnMiss: false,
});
const lockRaw = await readFile(path.join(tmpRoot, "docs.lock"), "utf8");
const lockRaw = await readFile(
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
"utf8",
);
const lock = JSON.parse(lockRaw);
assert.ok(lock.sources.gitignore);
} finally {
Expand Down Expand Up @@ -103,7 +106,10 @@ test("integration clears partial clone cache before sync", async (t) => {
offline: false,
failOnMiss: false,
});
const lockRaw = await readFile(path.join(tmpRoot, "docs.lock"), "utf8");
const lockRaw = await readFile(
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
"utf8",
);
const lock = JSON.parse(lockRaw);
assert.ok(lock.sources.gitignore);
const configRaw = await readFile(
Expand Down
5 changes: 4 additions & 1 deletion tests/lock.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

const fixturePath = new URL("./fixtures/docs.lock", import.meta.url);
const distPath = new URL("../dist/lock.mjs", import.meta.url);

const loadLockModule = async () => {
Expand All @@ -22,6 +21,10 @@ test("lock fixture is valid", async (t) => {
t.skip("lock module not built yet");
return;
}
const fixturePath = new URL(
`./fixtures/${module.DEFAULT_LOCK_FILENAME}`,
import.meta.url,
);
Comment thread
fbosch marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const raw = await readFile(fixturePath, "utf8");
const parsed = JSON.parse(raw.toString());
const lock = module.validateLock(parsed);
Expand Down
9 changes: 6 additions & 3 deletions tests/sync-materialize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

import { runSync } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, runSync } from "../dist/api.mjs";

const exists = async (target) => {
try {
Expand Down Expand Up @@ -73,7 +73,10 @@ test("sync materializes via mocked fetch", async () => {
);

assert.equal(await exists(path.join(cacheDir, "local")), true);
const lockRaw = await readFile(path.join(tmpRoot, "docs.lock"), "utf8");
const lockRaw = await readFile(
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
"utf8",
);
const lock = JSON.parse(lockRaw);
assert.equal(lock.sources.local.resolvedCommit, "abc123");
assert.equal(lock.sources.local.fileCount, 1);
Expand Down Expand Up @@ -105,7 +108,7 @@ test("sync re-materializes when docs missing even if commit unchanged", async ()
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");

await writeFile(
path.join(tmpRoot, "docs.lock"),
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
JSON.stringify({
version: 1,
generatedAt: new Date().toISOString(),
Expand Down
6 changes: 3 additions & 3 deletions tests/sync-offline-fail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

import { runSync } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, runSync } from "../dist/api.mjs";

test("sync fails on missing required sources when failOnMiss true", async () => {
const tmpRoot = path.join(
Expand Down Expand Up @@ -58,7 +58,7 @@ test("sync offline uses lock entries without resolving remotes", async () => {
await mkdir(tmpRoot, { recursive: true });
const cacheDir = path.join(tmpRoot, ".docs");
const configPath = path.join(tmpRoot, "docs.config.json");
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

const config = {
$schema:
Expand Down Expand Up @@ -128,7 +128,7 @@ test("sync offline fails when lock exists but cache missing", async () => {
await mkdir(tmpRoot, { recursive: true });
const cacheDir = path.join(tmpRoot, ".docs");
const configPath = path.join(tmpRoot, "docs.config.json");
const lockPath = path.join(tmpRoot, "docs.lock");
const lockPath = path.join(tmpRoot, DEFAULT_LOCK_FILENAME);

const config = {
$schema:
Expand Down
4 changes: 2 additions & 2 deletions tests/sync-output.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import assert from "node:assert/strict";
import path from "node:path";
import { test } from "node:test";

import { printSyncPlan } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, printSyncPlan } from "../dist/api.mjs";

test("printSyncPlan outputs summary and short hashes", () => {
const cwd = process.cwd();
const plan = {
configPath: path.join(cwd, "docs.config.json"),
cacheDir: path.join(cwd, ".docs"),
lockPath: path.join(cwd, "docs.lock"),
lockPath: path.join(cwd, DEFAULT_LOCK_FILENAME),
lockExists: true,
results: [
{
Expand Down
7 changes: 5 additions & 2 deletions tests/sync-tool-version.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";

import { runSync } from "../dist/api.mjs";
import { DEFAULT_LOCK_FILENAME, runSync } from "../dist/api.mjs";

test("sync writes lock toolVersion from package.json", async () => {
const tmpRoot = path.join(
Expand Down Expand Up @@ -43,7 +43,10 @@ test("sync writes lock toolVersion from package.json", async () => {
failOnMiss: false,
});

const lockRaw = await readFile(path.join(tmpRoot, "docs.lock"), "utf8");
const lockRaw = await readFile(
path.join(tmpRoot, DEFAULT_LOCK_FILENAME),
"utf8",
);
const lock = JSON.parse(lockRaw);
const pkgRaw = await readFile(
path.resolve(process.cwd(), "package.json"),
Expand Down
Loading