Skip to content

Commit 96c1239

Browse files
Copilotfbosch
andauthored
feat: Replace index.json feature with per-source TOC.md
* Initial plan * Replace index feature with TOC feature Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Update documentation and tests for TOC feature Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Fix code review feedback: avoid array mutation and improve comments Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Remove global TOC, make config.toc a default setting for sources Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Remove duplicate DEFAULT_TOC_FILENAME and unused tocPath property Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Change TOC default to true Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Fix init flow to properly handle TOC default being true Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * Remove TOC.md when toc setting is disabled Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com> * feat(toc): replace flat index with file tree * docs: README --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fbosch <6979916+fbosch@users.noreply.github.com>
1 parent 9303f96 commit 96c1239

15 files changed

Lines changed: 689 additions & 208 deletions

README.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,40 @@ npx docs-cache clean
7373

7474
### Options
7575

76-
| Field | Type | Description |
77-
| ---------- | ------- | ---------------------------------------- |
78-
| `cacheDir` | string | Directory for cache, defaults to `.docs` |
79-
| `index` | boolean | Write `index.json` summary file |
80-
| `sources` | array | List of repositories to sync |
81-
| `defaults` | object | Default settings for all sources |
76+
| Field | Type | Description |
77+
| ---------- | ------- | ---------------------------------------------------- |
78+
| `cacheDir` | string | Directory for cache, defaults to `.docs` |
79+
| `sources` | array | List of repositories to sync |
80+
| `defaults` | object | Default settings for all sources |
81+
82+
**Default Options:**
83+
84+
All fields in `defaults` apply to all sources unless overridden per-source:
85+
86+
- `ref`: Branch, tag, or commit (default: `"HEAD"`)
87+
- `mode`: Cache mode (default: `"materialize"`)
88+
- `include`: Glob patterns to copy (default: `["**/*.{md,mdx,markdown,mkd,txt,rst,adoc,asciidoc}"]`)
89+
- `targetMode`: `"symlink"` or `"copy"` (default: `"symlink"` on Unix, `"copy"` on Windows)
90+
- `depth`: Git clone depth (default: `1`)
91+
- `required`: Whether missing sources should fail (default: `true`)
92+
- `maxBytes`: Maximum total bytes to materialize (default: `200000000`)
93+
- `maxFiles`: Maximum total files to materialize (optional)
94+
- `allowHosts`: Allowed Git hosts (default: `["github.com", "gitlab.com"]`)
95+
- `toc`: Generate per-source `TOC.md` listing all documentation files (default: `true`)
8296

8397
**Source Options:**
8498

85-
- `repo`: Git URL
86-
- `ref`: Branch, tag, or commit
87-
- `include`: Glob patterns to copy, defaults to `"**/*.{md,mdx,markdown,mkd,txt,rst,adoc,asciidoc}"`,
99+
- `repo`: Git URL (required)
100+
- `id`: Unique identifier for the source (required)
101+
- `ref`: Branch, tag, or commit (overrides default)
102+
- `include`: Glob patterns to copy (overrides default)
88103
- `exclude`: Glob patterns to skip
89104
- `targetDir`: Optional path where files should be symlinked/copied to, outside `.docs`
90-
- `targetMode`: Defaults to `symlink` on Unix and `copy` on Windows
91-
- `required`: Whether missing sources should fail in offline/strict runs
92-
- `maxBytes`: Maximum total bytes to materialize for the source
93-
- `maxFiles`: Maximum total files to materialize for the source
105+
- `targetMode`: `"symlink"` or `"copy"` (overrides default)
106+
- `required`: Whether missing sources should fail (overrides default)
107+
- `maxBytes`: Maximum total bytes to materialize (overrides default)
108+
- `maxFiles`: Maximum total files to materialize (overrides default)
109+
- `toc`: Generate per-source `TOC.md` listing all documentation files (overrides default)
94110

95111
> **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`.
96112

docs.config.schema.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
"type": "string",
1515
"enum": ["symlink", "copy"]
1616
},
17-
"index": {
18-
"type": "boolean"
19-
},
2017
"defaults": {
2118
"type": "object",
2219
"properties": {
@@ -62,6 +59,9 @@
6259
"type": "string",
6360
"minLength": 1
6461
}
62+
},
63+
"toc": {
64+
"type": "boolean"
6565
}
6666
},
6767
"additionalProperties": false
@@ -144,6 +144,9 @@
144144
},
145145
"required": ["type", "value"],
146146
"additionalProperties": false
147+
},
148+
"toc": {
149+
"type": "boolean"
147150
}
148151
},
149152
"required": ["id", "repo"],

src/add.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,6 @@ export const addSources = async (params: {
128128
if (rawConfig?.cacheDir) {
129129
nextConfig.cacheDir = rawConfig.cacheDir;
130130
}
131-
if (rawConfig?.index !== undefined) {
132-
nextConfig.index = rawConfig.index;
133-
}
134131
if (rawConfig?.defaults) {
135132
nextConfig.defaults = rawConfig.defaults;
136133
}

src/config-schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const DefaultsSchema = z
2020
maxBytes: z.number().min(1),
2121
maxFiles: z.number().min(1).optional(),
2222
allowHosts: z.array(z.string().min(1)).min(1),
23+
toc: z.boolean().optional(),
2324
})
2425
.strict();
2526

@@ -38,6 +39,7 @@ export const SourceSchema = z
3839
maxBytes: z.number().min(1).optional(),
3940
maxFiles: z.number().min(1).optional(),
4041
integrity: IntegritySchema.optional(),
42+
toc: z.boolean().optional(),
4143
})
4244
.strict();
4345

@@ -46,7 +48,6 @@ export const ConfigSchema = z
4648
$schema: z.string().min(1).optional(),
4749
cacheDir: z.string().min(1).optional(),
4850
targetMode: TargetModeSchema.optional(),
49-
index: z.boolean().optional(),
5051
defaults: DefaultsSchema.partial().optional(),
5152
sources: z.array(SourceSchema),
5253
})

src/config.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface DocsCacheDefaults {
2323
maxBytes: number;
2424
maxFiles?: number;
2525
allowHosts: string[];
26+
toc?: boolean;
2627
}
2728

2829
export interface DocsCacheSource {
@@ -39,13 +40,13 @@ export interface DocsCacheSource {
3940
maxBytes?: number;
4041
maxFiles?: number;
4142
integrity?: DocsCacheIntegrity;
43+
toc?: boolean;
4244
}
4345

4446
export interface DocsCacheConfig {
4547
$schema?: string;
4648
cacheDir?: string;
4749
targetMode?: "symlink" | "copy";
48-
index?: boolean;
4950
defaults?: Partial<DocsCacheDefaults>;
5051
sources: DocsCacheSource[];
5152
}
@@ -64,6 +65,7 @@ export interface DocsCacheResolvedSource {
6465
maxBytes: number;
6566
maxFiles?: number;
6667
integrity?: DocsCacheIntegrity;
68+
toc?: boolean;
6769
}
6870

6971
export const DEFAULT_CONFIG_FILENAME = "docs.config.json";
@@ -72,7 +74,6 @@ const PACKAGE_JSON_FILENAME = "package.json";
7274
const DEFAULT_TARGET_MODE = process.platform === "win32" ? "copy" : "symlink";
7375
export const DEFAULT_CONFIG: DocsCacheConfig = {
7476
cacheDir: DEFAULT_CACHE_DIR,
75-
index: false,
7677
defaults: {
7778
ref: "HEAD",
7879
mode: "materialize",
@@ -82,6 +83,7 @@ export const DEFAULT_CONFIG: DocsCacheConfig = {
8283
required: true,
8384
maxBytes: 200000000,
8485
allowHosts: ["github.com", "gitlab.com"],
86+
toc: true,
8587
},
8688
sources: [],
8789
};
@@ -144,7 +146,6 @@ export const stripDefaultConfigValues = (
144146
const next: DocsCacheConfig = {
145147
$schema: pruned.$schema as DocsCacheConfig["$schema"],
146148
cacheDir: pruned.cacheDir as DocsCacheConfig["cacheDir"],
147-
index: pruned.index as DocsCacheConfig["index"],
148149
targetMode: pruned.targetMode as DocsCacheConfig["targetMode"],
149150
defaults: pruned.defaults as DocsCacheConfig["defaults"],
150151
sources: config.sources,
@@ -247,10 +248,6 @@ export const validateConfig = (input: unknown): DocsCacheConfig => {
247248
const cacheDir = input.cacheDir
248249
? assertString(input.cacheDir, "cacheDir")
249250
: DEFAULT_CACHE_DIR;
250-
const index =
251-
input.index !== undefined
252-
? assertBoolean(input.index, "index")
253-
: (DEFAULT_CONFIG.index ?? false);
254251

255252
const defaultsInput = input.defaults;
256253
const targetModeOverride =
@@ -300,6 +297,10 @@ export const validateConfig = (input: unknown): DocsCacheConfig => {
300297
defaultsInput.allowHosts !== undefined
301298
? assertStringArray(defaultsInput.allowHosts, "defaults.allowHosts")
302299
: defaultValues.allowHosts,
300+
toc:
301+
defaultsInput.toc !== undefined
302+
? assertBoolean(defaultsInput.toc, "defaults.toc")
303+
: defaultValues.toc,
303304
};
304305
} else if (targetModeOverride !== undefined) {
305306
defaults = {
@@ -386,6 +387,9 @@ export const validateConfig = (input: unknown): DocsCacheConfig => {
386387
`sources[${index}].integrity`,
387388
);
388389
}
390+
if (entry.toc !== undefined) {
391+
source.toc = assertBoolean(entry.toc, `sources[${index}].toc`);
392+
}
389393
return source;
390394
});
391395

@@ -407,7 +411,6 @@ export const validateConfig = (input: unknown): DocsCacheConfig => {
407411
return {
408412
cacheDir,
409413
targetMode: targetModeOverride,
410-
index,
411414
defaults,
412415
sources,
413416
};
@@ -432,6 +435,7 @@ export const resolveSources = (
432435
maxBytes: source.maxBytes ?? defaults.maxBytes,
433436
maxFiles: source.maxFiles ?? defaults.maxFiles,
434437
integrity: source.integrity,
438+
toc: source.toc ?? defaults.toc,
435439
}));
436440
};
437441

src/index.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

src/init.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ export const initConfig = async (
9393
throw new Error("Init cancelled.");
9494
}
9595
const cacheDirValue = cacheDirAnswer || DEFAULT_CACHE_DIR;
96-
const indexAnswer = await confirm({
96+
const tocAnswer = await confirm({
9797
message:
98-
"Generate index.json (summary of cached sources + paths for tools)",
99-
initialValue: false,
98+
"Generate TOC.md (table of contents with links to all documentation)",
99+
initialValue: true,
100100
});
101-
if (isCancel(indexAnswer)) {
101+
if (isCancel(tocAnswer)) {
102102
throw new Error("Init cancelled.");
103103
}
104104
const gitignoreStatus = await getGitignoreStatus(cwd, cacheDirValue);
@@ -117,12 +117,12 @@ export const initConfig = async (
117117
const answers = {
118118
configPath,
119119
cacheDir: cacheDirAnswer,
120-
index: indexAnswer,
120+
toc: tocAnswer,
121121
gitignore: gitignoreAnswer,
122122
} as {
123123
configPath: string;
124124
cacheDir: string;
125-
index: boolean;
125+
toc: boolean;
126126
gitignore: boolean;
127127
};
128128

@@ -144,8 +144,9 @@ export const initConfig = async (
144144
if (resolvedCacheDir !== DEFAULT_CACHE_DIR) {
145145
baseConfig.cacheDir = resolvedCacheDir;
146146
}
147-
if (answers.index) {
148-
baseConfig.index = true;
147+
// Since TOC defaults to true, only set it explicitly if user chose false
148+
if (!answers.toc) {
149+
baseConfig.defaults = { toc: false };
149150
}
150151
pkg["docs-cache"] = stripDefaultConfigValues(baseConfig);
151152
await writeFile(
@@ -178,8 +179,9 @@ export const initConfig = async (
178179
if (resolvedCacheDir !== DEFAULT_CACHE_DIR) {
179180
config.cacheDir = resolvedCacheDir;
180181
}
181-
if (answers.index) {
182-
config.index = true;
182+
// Since TOC defaults to true, only set it explicitly if user chose false
183+
if (!answers.toc) {
184+
config.defaults = { toc: false };
183185
}
184186

185187
await writeConfig(resolvedConfigPath, config);

src/paths.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from "node:path";
22

33
export const DEFAULT_LOCK_FILENAME = "docs.lock";
4-
export const DEFAULT_INDEX_FILENAME = "index.json";
4+
export const DEFAULT_TOC_FILENAME = "TOC.md";
55

66
export const toPosixPath = (value: string) => value.replace(/\\/g, "/");
77

@@ -40,10 +40,8 @@ export const resolveCacheDir = (
4040
export const getCacheLayout = (cacheDir: string, sourceId: string) => {
4141
const _reposDir = path.join(cacheDir, "repos");
4242
const sourceDir = path.join(cacheDir, sourceId);
43-
const indexPath = path.join(cacheDir, DEFAULT_INDEX_FILENAME);
4443
return {
4544
cacheDir,
4645
sourceDir,
47-
indexPath,
4846
};
4947
};

src/remove.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,6 @@ export const removeSources = async (params: {
137137
if (rawConfig?.cacheDir) {
138138
nextConfig.cacheDir = rawConfig.cacheDir;
139139
}
140-
if (rawConfig?.index !== undefined) {
141-
nextConfig.index = rawConfig.index;
142-
}
143140
if (rawConfig?.defaults) {
144141
nextConfig.defaults = rawConfig.defaults;
145142
}

0 commit comments

Comments
 (0)