Skip to content

Commit 7736a3d

Browse files
angeloashmoreclaude
andcommitted
feat: add docs command for browsing Prismic documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e66976d commit 7736a3d

4 files changed

Lines changed: 209 additions & 1 deletion

File tree

src/commands/docs-list.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { CommandError, createCommand, type CommandConfig } from "../lib/command";
2+
import { stringify } from "../lib/json";
3+
4+
const DOCS_INDEX_URL = new URL("https://prismic.io/docs/api/index/");
5+
6+
const config = {
7+
name: "prismic docs list",
8+
description: `
9+
List available documentation pages.
10+
11+
With a path argument, list the anchors within that page.
12+
`,
13+
positionals: {
14+
path: {
15+
description: "Documentation path to list anchors for",
16+
required: false,
17+
},
18+
},
19+
options: {
20+
json: { type: "boolean", description: "Output as JSON" },
21+
},
22+
} satisfies CommandConfig;
23+
24+
type IndexPage = {
25+
path: string;
26+
title: string;
27+
description: string;
28+
};
29+
30+
type IndexPageWithAnchors = IndexPage & {
31+
anchors: { slug: string; excerpt: string }[];
32+
};
33+
34+
export default createCommand(config, async ({ positionals, values }) => {
35+
const [path] = positionals;
36+
const { json } = values;
37+
38+
if (path) {
39+
const url = new URL(path, DOCS_INDEX_URL);
40+
const response = await fetch(url);
41+
42+
if (!response.ok) {
43+
if (response.status === 404) {
44+
throw new CommandError(`Documentation page not found: ${path}`);
45+
}
46+
throw new CommandError(`Failed to fetch documentation index: ${response.statusText}`);
47+
}
48+
49+
const entry: IndexPageWithAnchors = await response.json();
50+
entry.anchors.sort((a, b) => a.slug.localeCompare(b.slug));
51+
52+
if (json) {
53+
console.info(stringify(entry));
54+
return;
55+
}
56+
57+
if (entry.anchors.length === 0) {
58+
console.info("(no anchors)");
59+
return;
60+
}
61+
62+
for (const anchor of entry.anchors) {
63+
console.info(`${path}#${anchor.slug}: ${anchor.excerpt}`);
64+
}
65+
} else {
66+
const response = await fetch(DOCS_INDEX_URL);
67+
68+
if (!response.ok) {
69+
throw new CommandError(`Failed to fetch documentation index: ${response.statusText}`);
70+
}
71+
72+
const pages: IndexPage[] = await response.json();
73+
pages.sort((a, b) => a.path.localeCompare(b.path));
74+
75+
if (json) {
76+
console.info(stringify(pages));
77+
return;
78+
}
79+
80+
if (pages.length === 0) {
81+
console.info("No documentation pages found.");
82+
return;
83+
}
84+
85+
for (const page of pages) {
86+
console.info(`${page.path}: ${page.title}${page.description}`);
87+
}
88+
}
89+
});

src/commands/docs-view.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { CommandError, createCommand, type CommandConfig } from "../lib/command";
2+
import { stringify } from "../lib/json";
3+
4+
const DOCS_BASE_URL = new URL("https://prismic.io/docs/");
5+
6+
const config = {
7+
name: "prismic docs view",
8+
description: `
9+
View a documentation page as Markdown.
10+
11+
Append #anchor to the path to view only the section under that heading.
12+
`,
13+
positionals: {
14+
path: {
15+
description: "Documentation path, optionally with #anchor (e.g., setup#install)",
16+
required: true,
17+
},
18+
},
19+
options: {
20+
json: { type: "boolean", description: "Output as JSON" },
21+
},
22+
} satisfies CommandConfig;
23+
24+
export default createCommand(config, async ({ positionals, values }) => {
25+
const [rawPath] = positionals;
26+
const { json } = values;
27+
28+
const hashIndex = rawPath.indexOf("#");
29+
const path = hashIndex >= 0 ? rawPath.slice(0, hashIndex) : rawPath;
30+
const anchor = hashIndex >= 0 ? rawPath.slice(hashIndex + 1) : undefined;
31+
32+
const url = new URL(path, DOCS_BASE_URL);
33+
const response = await fetch(url, {
34+
headers: { Accept: "text/markdown" },
35+
});
36+
37+
if (!response.ok) {
38+
throw new CommandError(`Failed to fetch documentation page: ${response.statusText}`);
39+
}
40+
41+
let markdown = await response.text();
42+
43+
if (anchor) {
44+
const section = extractSection(markdown, anchor);
45+
if (!section) {
46+
throw new CommandError(`Anchor not found: #${anchor}`);
47+
}
48+
markdown = section;
49+
}
50+
51+
if (json) {
52+
console.info(stringify({ path, anchor, content: markdown }));
53+
return;
54+
}
55+
56+
console.info(markdown);
57+
});
58+
59+
function extractSection(markdown: string, anchor: string): string | undefined {
60+
const lines = markdown.split("\n");
61+
let startIndex = -1;
62+
let headingLevel = 0;
63+
64+
for (let i = 0; i < lines.length; i++) {
65+
const match = lines[i].match(/^(#{1,6})\s+(.*)/);
66+
if (!match) {
67+
continue;
68+
}
69+
70+
const level = match[1].length;
71+
const text = match[2];
72+
73+
if (startIndex >= 0 && level <= headingLevel) {
74+
return lines.slice(startIndex, i).join("\n").trimEnd();
75+
}
76+
77+
if (kebabCase(text) === anchor) {
78+
startIndex = i;
79+
headingLevel = level;
80+
}
81+
}
82+
83+
if (startIndex >= 0) {
84+
return lines.slice(startIndex).join("\n").trimEnd();
85+
}
86+
87+
return undefined;
88+
}
89+
90+
function kebabCase(text: string): string {
91+
return text
92+
.toLowerCase()
93+
.replace(/[^a-z0-9]+/g, "-")
94+
.replace(/^-|-$/g, "");
95+
}

src/commands/docs.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createCommandRouter } from "../lib/command";
2+
3+
import docsList from "./docs-list";
4+
import docsView from "./docs-view";
5+
6+
export default createCommandRouter({
7+
name: "prismic docs",
8+
description: "Browse Prismic documentation.",
9+
commands: {
10+
list: {
11+
handler: docsList,
12+
description: "List available documentation pages",
13+
},
14+
view: {
15+
handler: docsView,
16+
description: "View a documentation page",
17+
},
18+
},
19+
});

src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import packageJson from "../package.json" with { type: "json" };
66
import { getAdapter, NoSupportedFrameworkError } from "./adapters";
77
import { getHost, refreshToken } from "./auth";
88
import { getProfile } from "./clients/user";
9+
import docs from "./commands/docs";
910
import gen from "./commands/gen";
1011
import init from "./commands/init";
1112
import locale from "./commands/locale";
@@ -37,7 +38,7 @@ import {
3738
import { dedent } from "./lib/string";
3839
import { safeGetRepositoryName, TypeBuilderRequiredError } from "./project";
3940

40-
const UNTRACKED_COMMANDS = ["login", "logout", "whoami", "sync"];
41+
const UNTRACKED_COMMANDS = ["login", "logout", "whoami", "sync", "docs"];
4142
const SKIP_REFRESH_COMMANDS = ["login", "logout"];
4243

4344
const router = createCommandRouter({
@@ -48,6 +49,10 @@ const router = createCommandRouter({
4849
handler: init,
4950
description: "Initialize a Prismic project",
5051
},
52+
docs: {
53+
handler: docs,
54+
description: "Browse Prismic documentation",
55+
},
5156
gen: {
5257
handler: gen,
5358
description: "Generate files from local models",

0 commit comments

Comments
 (0)