@latent-space-labs/open-auto-doc 0.3.2 → 0.3.3

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.
package/dist/index.js CHANGED
@@ -555,6 +555,26 @@ async function createCiWorkflow(params) {
555
555
  "utf-8"
556
556
  );
557
557
  p4.log.success(`Created ${path5.relative(gitRoot, workflowPath)}`);
558
+ if (token) {
559
+ try {
560
+ const origin = execSync3("git remote get-url origin", {
561
+ cwd: gitRoot,
562
+ encoding: "utf-8",
563
+ stdio: ["pipe", "pipe", "pipe"]
564
+ }).trim();
565
+ const match = origin.match(/github\.com[:/]([^/]+\/[^/.]+?)(?:\.git)?$/);
566
+ if (match) {
567
+ const octokit = new Octokit3({ auth: token });
568
+ await verifySecretsInteractive(octokit, [match[1]]);
569
+ } else {
570
+ showSecretsInstructions(false);
571
+ }
572
+ } catch {
573
+ showSecretsInstructions(false);
574
+ }
575
+ } else {
576
+ showSecretsInstructions(false);
577
+ }
558
578
  return { workflowPath, branch };
559
579
  }
560
580
  async function createCiWorkflowsMultiRepo(params) {
@@ -609,8 +629,92 @@ async function createCiWorkflowsMultiRepo(params) {
609
629
  return null;
610
630
  }
611
631
  p4.log.success(`Created workflows in ${createdRepos.length}/${config.repos.length} repositories`);
632
+ await verifySecretsInteractive(octokit, createdRepos);
612
633
  return { repos: createdRepos, branch };
613
634
  }
635
+ var REQUIRED_SECRETS = ["ANTHROPIC_API_KEY", "DOCS_DEPLOY_TOKEN"];
636
+ async function checkSecret(octokit, owner, repo, secretName) {
637
+ try {
638
+ await octokit.rest.actions.getRepoSecret({ owner, repo, secret_name: secretName });
639
+ return true;
640
+ } catch {
641
+ return false;
642
+ }
643
+ }
644
+ async function verifySecretsInteractive(octokit, repos) {
645
+ const shouldVerify = await p4.confirm({
646
+ message: "Would you like to verify secrets now? (you can add them later)",
647
+ initialValue: true
648
+ });
649
+ if (p4.isCancel(shouldVerify) || !shouldVerify) {
650
+ showSecretsInstructions(repos.length > 1);
651
+ return;
652
+ }
653
+ p4.note(
654
+ [
655
+ "Each source repo needs two Actions secrets:",
656
+ "",
657
+ " ANTHROPIC_API_KEY \u2014 Your Anthropic API key",
658
+ " DOCS_DEPLOY_TOKEN \u2014 GitHub PAT with repo scope",
659
+ "",
660
+ "To create the PAT:",
661
+ " 1. Go to https://github.com/settings/tokens",
662
+ " 2. Generate new token (classic) with 'repo' scope",
663
+ " 3. Copy the token and add it as DOCS_DEPLOY_TOKEN"
664
+ ].join("\n"),
665
+ "Required GitHub Secrets"
666
+ );
667
+ const summary = {};
668
+ for (const fullName of repos) {
669
+ const [owner, repoName] = fullName.split("/");
670
+ p4.log.step(`Add secrets to ${fullName}`);
671
+ p4.log.message(` https://github.com/${fullName}/settings/secrets/actions`);
672
+ let allFound = false;
673
+ while (!allFound) {
674
+ await p4.text({
675
+ message: `Press enter when you've added the secrets to ${fullName}...`,
676
+ defaultValue: "",
677
+ placeholder: ""
678
+ });
679
+ const results = {};
680
+ for (const secret of REQUIRED_SECRETS) {
681
+ results[secret] = await checkSecret(octokit, owner, repoName, secret);
682
+ }
683
+ for (const secret of REQUIRED_SECRETS) {
684
+ if (results[secret]) {
685
+ p4.log.success(`${secret} \u2014 found`);
686
+ } else {
687
+ p4.log.warn(`${secret} \u2014 not found`);
688
+ }
689
+ }
690
+ summary[fullName] = results;
691
+ allFound = REQUIRED_SECRETS.every((s) => results[s]);
692
+ if (!allFound) {
693
+ const retry = await p4.confirm({
694
+ message: "Some secrets are missing. Would you like to try again?",
695
+ initialValue: true
696
+ });
697
+ if (p4.isCancel(retry) || !retry) break;
698
+ }
699
+ }
700
+ }
701
+ const lines = [];
702
+ let allGreen = true;
703
+ for (const fullName of repos) {
704
+ const parts = REQUIRED_SECRETS.map((s) => {
705
+ const ok = summary[fullName]?.[s] ?? false;
706
+ if (!ok) allGreen = false;
707
+ return ok ? `\u2713 ${s}` : `\u2717 ${s}`;
708
+ });
709
+ lines.push(`${fullName} \u2014 ${parts.join(" ")}`);
710
+ }
711
+ if (allGreen) {
712
+ p4.note(lines.join("\n"), "All secrets verified!");
713
+ } else {
714
+ p4.note(lines.join("\n"), "Secret status");
715
+ p4.log.warn("Some secrets are still missing \u2014 workflows will fail until they are added.");
716
+ }
717
+ }
614
718
  function showSecretsInstructions(multiRepo = false) {
615
719
  const repoNote = multiRepo ? "Add these secrets to EACH source repository:" : "Add these secrets to your GitHub repository:";
616
720
  p4.note(
@@ -2566,6 +2670,15 @@ function slugify22(name) {
2566
2670
  var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
2567
2671
  async function initCommand(options) {
2568
2672
  p5.intro("open-auto-doc \u2014 AI-powered documentation generator");
2673
+ const templateDir = resolveTemplateDir();
2674
+ if (!fs8.existsSync(path8.join(templateDir, "package.json"))) {
2675
+ p5.log.error(
2676
+ `Site template not found at: ${templateDir}
2677
+ This usually means the npm package was not built correctly.
2678
+ Try reinstalling: npm install -g @latent-space-labs/open-auto-doc`
2679
+ );
2680
+ process.exit(1);
2681
+ }
2569
2682
  let token = getGithubToken();
2570
2683
  if (!token) {
2571
2684
  p5.log.info("Let's connect your GitHub account.");
@@ -2693,8 +2806,6 @@ async function initCommand(options) {
2693
2806
  const genSpinner = p5.spinner();
2694
2807
  try {
2695
2808
  genSpinner.start("Scaffolding documentation site...");
2696
- const templateDir = resolveTemplateDir();
2697
- p5.log.info(`Using template from: ${templateDir}`);
2698
2809
  await scaffoldSite(outputDir, projectName, templateDir);
2699
2810
  genSpinner.stop("Site scaffolded");
2700
2811
  } catch (err) {
@@ -2776,23 +2887,25 @@ async function initCommand(options) {
2776
2887
  token,
2777
2888
  config
2778
2889
  });
2779
- if (ciResult) {
2780
- showSecretsInstructions(repos.length > 1);
2781
- }
2782
2890
  showVercelInstructions(deployResult.owner, deployResult.repoName);
2783
2891
  p5.outro(`Docs repo: https://github.com/${deployResult.owner}/${deployResult.repoName}`);
2784
2892
  }
2785
2893
  function resolveTemplateDir() {
2786
2894
  const candidates = [
2895
+ path8.resolve(__dirname2, "site-template"),
2896
+ // dist/site-template (npm global install)
2787
2897
  path8.resolve(__dirname2, "../../site-template"),
2898
+ // monorepo: packages/site-template
2788
2899
  path8.resolve(__dirname2, "../../../site-template"),
2900
+ // monorepo alt
2789
2901
  path8.resolve(__dirname2, "../../../../packages/site-template")
2902
+ // monorepo from nested dist
2790
2903
  ];
2791
2904
  for (const candidate of candidates) {
2792
2905
  const pkgPath = path8.join(candidate, "package.json");
2793
2906
  if (fs8.existsSync(pkgPath)) return candidate;
2794
2907
  }
2795
- return path8.resolve(__dirname2, "../../site-template");
2908
+ return path8.resolve(__dirname2, "site-template");
2796
2909
  }
2797
2910
  function cleanup(clones) {
2798
2911
  for (const clone of clones) {
@@ -3073,7 +3186,6 @@ async function setupCiCommand() {
3073
3186
  p8.cancel("Setup cancelled.");
3074
3187
  process.exit(0);
3075
3188
  }
3076
- showSecretsInstructions(isMultiRepo);
3077
3189
  if ("repos" in result) {
3078
3190
  p8.outro("Per-repo CI workflows created! Add the required secrets to each source repo.");
3079
3191
  } else {
@@ -3110,7 +3222,7 @@ async function logoutCommand() {
3110
3222
 
3111
3223
  // src/index.ts
3112
3224
  var program = new Command();
3113
- program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.3.2");
3225
+ program.name("open-auto-doc").description("Auto-generate beautiful documentation websites from GitHub repositories using AI").version("0.3.3");
3114
3226
  program.command("init", { isDefault: true }).description("Initialize and generate documentation for your repositories").option("-o, --output <dir>", "Output directory", "docs-site").action(initCommand);
3115
3227
  program.command("generate").description("Regenerate documentation using existing configuration").option("--incremental", "Only re-analyze changed files (uses cached results)").option("--force", "Force full regeneration (ignore cache)").option("--repo <name>", "Only analyze this repo (uses cache for others)").action(generateCommand);
3116
3228
  program.command("deploy").description("Create a GitHub repo for docs and push (connect to Vercel for auto-deploy)").option("-d, --dir <path>", "Docs site directory").action(deployCommand);
@@ -0,0 +1,6 @@
1
+ import { source } from "@/lib/source";
2
+ import { createFromSource } from "fumadocs-core/search/server";
3
+
4
+ export const { GET } = createFromSource(source, {
5
+ language: "english",
6
+ });
@@ -0,0 +1,47 @@
1
+ import { source } from "@/lib/source";
2
+ import {
3
+ DocsBody,
4
+ DocsDescription,
5
+ DocsPage,
6
+ DocsTitle,
7
+ } from "fumadocs-ui/layouts/docs/page";
8
+ import { notFound } from "next/navigation";
9
+ import { getMDXComponents } from "@/mdx-components";
10
+ import type { Metadata } from "next";
11
+
12
+ export default async function Page(props: {
13
+ params: Promise<{ slug?: string[] }>;
14
+ }) {
15
+ const params = await props.params;
16
+ const page = source.getPage(params.slug);
17
+ if (!page) notFound();
18
+
19
+ const MDX = page.data.body;
20
+
21
+ return (
22
+ <DocsPage toc={page.data.toc} full={page.data.full}>
23
+ <DocsTitle>{page.data.title}</DocsTitle>
24
+ <DocsDescription>{page.data.description}</DocsDescription>
25
+ <DocsBody>
26
+ <MDX components={getMDXComponents()} />
27
+ </DocsBody>
28
+ </DocsPage>
29
+ );
30
+ }
31
+
32
+ export async function generateStaticParams() {
33
+ return source.generateParams();
34
+ }
35
+
36
+ export async function generateMetadata(props: {
37
+ params: Promise<{ slug?: string[] }>;
38
+ }): Promise<Metadata> {
39
+ const params = await props.params;
40
+ const page = source.getPage(params.slug);
41
+ if (!page) notFound();
42
+
43
+ return {
44
+ title: page.data.title,
45
+ description: page.data.description,
46
+ };
47
+ }
@@ -0,0 +1,12 @@
1
+ import { source } from "@/lib/source";
2
+ import { DocsLayout } from "fumadocs-ui/layouts/docs";
3
+ import { baseOptions } from "@/lib/layout.shared";
4
+ import type { ReactNode } from "react";
5
+
6
+ export default function Layout({ children }: { children: ReactNode }) {
7
+ return (
8
+ <DocsLayout tree={source.getPageTree()} {...baseOptions()}>
9
+ {children}
10
+ </DocsLayout>
11
+ );
12
+ }
@@ -0,0 +1,3 @@
1
+ @import "tailwindcss";
2
+ @import "fumadocs-ui/css/neutral.css";
3
+ @import "fumadocs-ui/css/preset.css";
@@ -0,0 +1,21 @@
1
+ import { RootProvider } from "fumadocs-ui/provider/next";
2
+ import "./global.css";
3
+ import { Inter } from "next/font/google";
4
+ import type { ReactNode } from "react";
5
+
6
+ const inter = Inter({ subsets: ["latin"] });
7
+
8
+ export const metadata = {
9
+ title: "{{projectName}} Documentation",
10
+ description: "Auto-generated documentation for {{projectName}}",
11
+ };
12
+
13
+ export default function RootLayout({ children }: { children: ReactNode }) {
14
+ return (
15
+ <html lang="en" className={inter.className} suppressHydrationWarning>
16
+ <body className="flex min-h-screen flex-col">
17
+ <RootProvider>{children}</RootProvider>
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ export default function RootPage() {
4
+ redirect("/docs");
5
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+
5
+ export function Mermaid({ code }: { code: string }) {
6
+ const id = useId().replace(/:/g, "m");
7
+ const containerRef = useRef<HTMLDivElement>(null);
8
+ const [svg, setSvg] = useState<string>("");
9
+ const [error, setError] = useState<string>("");
10
+
11
+ useEffect(() => {
12
+ let cancelled = false;
13
+
14
+ async function render() {
15
+ try {
16
+ const mermaid = (await import("mermaid")).default;
17
+ mermaid.initialize({
18
+ startOnLoad: false,
19
+ theme: "neutral",
20
+ securityLevel: "loose",
21
+ });
22
+ const { svg: rendered } = await mermaid.render(
23
+ `mermaid-${id}`,
24
+ code,
25
+ );
26
+ if (!cancelled) setSvg(rendered);
27
+ } catch (err) {
28
+ if (!cancelled)
29
+ setError(err instanceof Error ? err.message : "Diagram error");
30
+ }
31
+ }
32
+
33
+ render();
34
+ return () => {
35
+ cancelled = true;
36
+ };
37
+ }, [code, id]);
38
+
39
+ if (error) {
40
+ return (
41
+ <pre className="rounded-lg border bg-fd-card p-4 text-sm text-fd-muted-foreground overflow-x-auto">
42
+ <code>{code}</code>
43
+ </pre>
44
+ );
45
+ }
46
+
47
+ if (!svg) {
48
+ return (
49
+ <div className="flex items-center justify-center rounded-lg border bg-fd-card p-8 text-sm text-fd-muted-foreground">
50
+ Loading diagram...
51
+ </div>
52
+ );
53
+ }
54
+
55
+ return (
56
+ <div
57
+ ref={containerRef}
58
+ className="my-4 flex justify-center overflow-x-auto rounded-lg border bg-fd-card p-4 [&_svg]:max-w-full"
59
+ dangerouslySetInnerHTML={{ __html: svg }}
60
+ />
61
+ );
62
+ }
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Welcome
3
+ description: Auto-generated documentation
4
+ ---
5
+
6
+ # Welcome
7
+
8
+ This documentation was auto-generated by [open-auto-doc](https://github.com/open-auto-doc).
9
+
10
+ Browse the sidebar to explore the documentation.
@@ -0,0 +1,3 @@
1
+ {
2
+ "pages": ["index"]
3
+ }
@@ -0,0 +1,9 @@
1
+ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
2
+
3
+ export function baseOptions(): BaseLayoutProps {
4
+ return {
5
+ nav: {
6
+ title: "{{projectName}}",
7
+ },
8
+ };
9
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Remark plugin that transforms ```mermaid code blocks into <Mermaid /> JSX components.
3
+ * This runs before Shiki so the mermaid blocks never get syntax-highlighted as code.
4
+ */
5
+ export function remarkMermaid() {
6
+ return (tree: any) => {
7
+ walk(tree);
8
+ };
9
+ }
10
+
11
+ function walk(node: any) {
12
+ if (!node.children) return;
13
+ for (let i = 0; i < node.children.length; i++) {
14
+ const child = node.children[i];
15
+ if (child.type === "code" && child.lang === "mermaid") {
16
+ node.children[i] = {
17
+ type: "mdxJsxFlowElement",
18
+ name: "Mermaid",
19
+ attributes: [
20
+ {
21
+ type: "mdxJsxAttribute",
22
+ name: "code",
23
+ value: child.value,
24
+ },
25
+ ],
26
+ children: [],
27
+ data: { _mdxExplicitJsx: true },
28
+ };
29
+ } else {
30
+ walk(child);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,7 @@
1
+ import { docs } from "fumadocs-mdx:collections/server";
2
+ import { loader } from "fumadocs-core/source";
3
+
4
+ export const source = loader({
5
+ baseUrl: "/docs",
6
+ source: docs.toFumadocsSource(),
7
+ });
@@ -0,0 +1,11 @@
1
+ import defaultMdxComponents from "fumadocs-ui/mdx";
2
+ import type { MDXComponents } from "mdx/types";
3
+ import { Mermaid } from "@/components/mermaid";
4
+
5
+ export function getMDXComponents(components?: MDXComponents): MDXComponents {
6
+ return {
7
+ ...defaultMdxComponents,
8
+ Mermaid,
9
+ ...components,
10
+ };
11
+ }
@@ -0,0 +1,10 @@
1
+ import { createMDX } from "fumadocs-mdx/next";
2
+
3
+ const withMDX = createMDX();
4
+
5
+ /** @type {import('next').NextConfig} */
6
+ const config = {
7
+ reactStrictMode: true,
8
+ };
9
+
10
+ export default withMDX(config);
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "{{projectName}}-docs",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "postinstall": "fumadocs-mdx"
10
+ },
11
+ "dependencies": {
12
+ "next": "^16.0.0",
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0",
15
+ "fumadocs-core": "^16.0.0",
16
+ "fumadocs-ui": "^16.0.0",
17
+ "fumadocs-mdx": "^14.0.0",
18
+ "mermaid": "^11.0.0",
19
+ "lucide-react": "^0.475.0",
20
+ "@types/mdx": "^2.0.13"
21
+ },
22
+ "devDependencies": {
23
+ "@tailwindcss/postcss": "^4.0.0",
24
+ "@types/node": "^22.12.0",
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0",
27
+ "postcss": "^8.5.0",
28
+ "tailwindcss": "^4.0.0",
29
+ "typescript": "^5.7.0"
30
+ }
31
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,12 @@
1
+ import { defineConfig, defineDocs } from "fumadocs-mdx/config";
2
+ import { remarkMermaid } from "./lib/remark-mermaid";
3
+
4
+ export const docs = defineDocs({
5
+ dir: "content/docs",
6
+ });
7
+
8
+ export default defineConfig({
9
+ mdxOptions: {
10
+ remarkPlugins: [remarkMermaid],
11
+ },
12
+ });
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "target": "ESNext",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "module": "esnext",
13
+ "moduleResolution": "bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "jsx": "react-jsx",
17
+ "incremental": true,
18
+ "paths": {
19
+ "@/*": ["./*"],
20
+ "fumadocs-mdx:collections/*": [".source/*"]
21
+ },
22
+ "plugins": [{ "name": "next" }]
23
+ },
24
+ "include": [
25
+ "next-env.d.ts",
26
+ "**/*.ts",
27
+ "**/*.tsx",
28
+ ".next/types/**/*.ts",
29
+ ".next/dev/types/**/*.ts"
30
+ ],
31
+ "exclude": ["node_modules"]
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latent-space-labs/open-auto-doc",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Auto-generate beautiful documentation websites from GitHub repositories using AI",
5
5
  "type": "module",
6
6
  "bin": {