doccupine 0.0.87 → 0.0.88
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 +98 -0
- package/dist/lib/structures.js +0 -2
- package/dist/lib/types.d.ts +1 -0
- package/dist/templates/app/robots.d.ts +1 -1
- package/dist/templates/app/robots.js +28 -1
- package/dist/templates/app/sitemap.d.ts +7 -0
- package/dist/templates/app/sitemap.js +56 -0
- package/dist/templates/env.example.d.ts +1 -1
- package/dist/templates/env.example.js +5 -1
- package/dist/templates/mdx/deployment-and-hosting.mdx.d.ts +1 -1
- package/dist/templates/mdx/deployment-and-hosting.mdx.js +19 -0
- package/dist/templates/mdx/globals.mdx.d.ts +1 -1
- package/dist/templates/mdx/globals.mdx.js +3 -1
- package/dist/templates/package.js +17 -17
- package/dist/templates/utils/config.d.ts +1 -1
- package/dist/templates/utils/config.js +1 -0
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { findAvailablePort, generateSlug, getFullSlug, escapeTemplateContent, }
|
|
|
13
13
|
import { generateMetadataBlock, generateRuntimeOnlyMetadataBlock, } from "./lib/metadata.js";
|
|
14
14
|
import { nextConfigTemplate } from "./templates/next.config.js";
|
|
15
15
|
import { proxyTemplate } from "./templates/proxy.js";
|
|
16
|
+
import { robotsTemplate } from "./templates/app/robots.js";
|
|
17
|
+
import { sitemapTemplate } from "./templates/app/sitemap.js";
|
|
16
18
|
export { generateSlug, getFullSlug, escapeTemplateContent, } from "./lib/utils.js";
|
|
17
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
20
|
const __dirname = path.dirname(__filename);
|
|
@@ -72,6 +74,7 @@ class MDXToNextJSGenerator {
|
|
|
72
74
|
console.log(chalk.white(" npm install && npm run dev"));
|
|
73
75
|
}
|
|
74
76
|
async createNextJSStructure() {
|
|
77
|
+
const siteUrl = await this.loadSiteUrl();
|
|
75
78
|
const structure = {
|
|
76
79
|
...appStructure,
|
|
77
80
|
"next.config.ts": nextConfigTemplate(this.analyticsConfig),
|
|
@@ -82,6 +85,7 @@ class MDXToNextJSGenerator {
|
|
|
82
85
|
"navigation.json": `[]\n`,
|
|
83
86
|
"sections.json": `[]\n`,
|
|
84
87
|
"theme.json": `{}\n`,
|
|
88
|
+
"app/robots.ts": robotsTemplate(siteUrl !== null),
|
|
85
89
|
"app/layout.tsx": this.generateRootLayout(),
|
|
86
90
|
};
|
|
87
91
|
for (const [filePath, content] of Object.entries(structure)) {
|
|
@@ -89,6 +93,7 @@ class MDXToNextJSGenerator {
|
|
|
89
93
|
await fs.ensureDir(path.dirname(fullPath));
|
|
90
94
|
await fs.writeFile(fullPath, String(await content), "utf8");
|
|
91
95
|
}
|
|
96
|
+
await this.updateSitemap();
|
|
92
97
|
}
|
|
93
98
|
async createStartingDocs() {
|
|
94
99
|
const structure = startingDocsStructure;
|
|
@@ -326,6 +331,10 @@ class MDXToNextJSGenerator {
|
|
|
326
331
|
if (fileName === "sections.json") {
|
|
327
332
|
await this.reloadSections();
|
|
328
333
|
}
|
|
334
|
+
if (fileName === "config.json") {
|
|
335
|
+
await this.updateSitemap();
|
|
336
|
+
await this.updateRobots();
|
|
337
|
+
}
|
|
329
338
|
}
|
|
330
339
|
catch (error) {
|
|
331
340
|
console.error(chalk.red(`❌ Error copying ${fileName}:`), error);
|
|
@@ -344,6 +353,10 @@ class MDXToNextJSGenerator {
|
|
|
344
353
|
if (fileName === "sections.json") {
|
|
345
354
|
await this.reloadSections();
|
|
346
355
|
}
|
|
356
|
+
if (fileName === "config.json") {
|
|
357
|
+
await this.updateSitemap();
|
|
358
|
+
await this.updateRobots();
|
|
359
|
+
}
|
|
347
360
|
}
|
|
348
361
|
catch (error) {
|
|
349
362
|
console.error(chalk.red(`❌ Error removing ${fileName}:`), error);
|
|
@@ -607,6 +620,22 @@ class MDXToNextJSGenerator {
|
|
|
607
620
|
const { data: frontmatter } = matter(content);
|
|
608
621
|
const { sectionSlug, pageSlug } = this.determineSectionForFile(file, frontmatter);
|
|
609
622
|
const fullSlug = getFullSlug(pageSlug, sectionSlug);
|
|
623
|
+
let lastModified;
|
|
624
|
+
if (frontmatter.date) {
|
|
625
|
+
const parsed = new Date(frontmatter.date);
|
|
626
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
627
|
+
lastModified = parsed.toISOString();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (!lastModified) {
|
|
631
|
+
try {
|
|
632
|
+
const stats = await fs.stat(fullPath);
|
|
633
|
+
lastModified = stats.mtime.toISOString();
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// ignore
|
|
637
|
+
}
|
|
638
|
+
}
|
|
610
639
|
return {
|
|
611
640
|
slug: fullSlug,
|
|
612
641
|
title: frontmatter.title || "Untitled",
|
|
@@ -617,6 +646,7 @@ class MDXToNextJSGenerator {
|
|
|
617
646
|
categoryOrder: frontmatter.categoryOrder || 0,
|
|
618
647
|
order: frontmatter.order || 0,
|
|
619
648
|
section: sectionSlug,
|
|
649
|
+
lastModified,
|
|
620
650
|
};
|
|
621
651
|
}
|
|
622
652
|
async buildAllPagesMeta() {
|
|
@@ -650,6 +680,7 @@ class MDXToNextJSGenerator {
|
|
|
650
680
|
}
|
|
651
681
|
await this.updatePagesIndex();
|
|
652
682
|
await this.updateRootLayout();
|
|
683
|
+
await this.updateSitemap();
|
|
653
684
|
await this.generateSectionIndexPages();
|
|
654
685
|
console.log(chalk.green(`✅ Generated page for: ${filePath}`));
|
|
655
686
|
await this.maybeUpdateSections();
|
|
@@ -673,6 +704,7 @@ class MDXToNextJSGenerator {
|
|
|
673
704
|
}
|
|
674
705
|
await this.updatePagesIndex();
|
|
675
706
|
await this.updateRootLayout();
|
|
707
|
+
await this.updateSitemap();
|
|
676
708
|
console.log(chalk.green(`✅ Removed page for: ${filePath}`));
|
|
677
709
|
await this.maybeUpdateSections();
|
|
678
710
|
}
|
|
@@ -845,6 +877,72 @@ export default function Page() {
|
|
|
845
877
|
const layoutContent = await this.generateRootLayout();
|
|
846
878
|
await fs.writeFile(path.join(this.outputDir, "app", "layout.tsx"), layoutContent, "utf8");
|
|
847
879
|
}
|
|
880
|
+
async loadSiteUrl() {
|
|
881
|
+
const configPath = path.join(this.rootDir, "config.json");
|
|
882
|
+
try {
|
|
883
|
+
if (await fs.pathExists(configPath)) {
|
|
884
|
+
const content = await fs.readFile(configPath, "utf8");
|
|
885
|
+
const parsed = JSON.parse(content);
|
|
886
|
+
if (typeof parsed.url === "string" && parsed.url.trim() !== "") {
|
|
887
|
+
return parsed.url.trim().replace(/\/$/, "");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
catch (error) {
|
|
892
|
+
console.warn(chalk.yellow("⚠️ Error reading config.json"), error);
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
buildSitemapEntries(pages) {
|
|
897
|
+
const sectionSlugs = new Set((this.sectionsConfig || [])
|
|
898
|
+
.map((s) => s.slug)
|
|
899
|
+
.filter((s) => typeof s === "string" && s !== ""));
|
|
900
|
+
const entries = pages.map((page) => {
|
|
901
|
+
let priority = 0.5;
|
|
902
|
+
if (page.slug === "") {
|
|
903
|
+
priority = 1.0;
|
|
904
|
+
}
|
|
905
|
+
else if (sectionSlugs.has(page.slug)) {
|
|
906
|
+
priority = 0.8;
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
slug: page.slug,
|
|
910
|
+
lastModified: page.lastModified,
|
|
911
|
+
changeFrequency: "weekly",
|
|
912
|
+
priority,
|
|
913
|
+
};
|
|
914
|
+
});
|
|
915
|
+
if (!entries.some((entry) => entry.slug === "")) {
|
|
916
|
+
entries.unshift({
|
|
917
|
+
slug: "",
|
|
918
|
+
changeFrequency: "weekly",
|
|
919
|
+
priority: 1.0,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
return entries;
|
|
923
|
+
}
|
|
924
|
+
async updateSitemap() {
|
|
925
|
+
const sitemapPath = path.join(this.outputDir, "app", "sitemap.ts");
|
|
926
|
+
const siteUrl = await this.loadSiteUrl();
|
|
927
|
+
if (!siteUrl) {
|
|
928
|
+
if (await fs.pathExists(sitemapPath)) {
|
|
929
|
+
await fs.remove(sitemapPath);
|
|
930
|
+
console.log(chalk.yellow("🗑️ Removed sitemap.ts (no site URL configured)"));
|
|
931
|
+
}
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const pages = await this.buildAllPagesMeta();
|
|
935
|
+
const entries = this.buildSitemapEntries(pages);
|
|
936
|
+
await fs.writeFile(sitemapPath, sitemapTemplate(entries), "utf8");
|
|
937
|
+
console.log(chalk.green(`🗺️ Generated sitemap.ts with ${entries.length} page(s) using ${siteUrl}`));
|
|
938
|
+
}
|
|
939
|
+
async updateRobots() {
|
|
940
|
+
const siteUrl = await this.loadSiteUrl();
|
|
941
|
+
await fs.writeFile(path.join(this.outputDir, "app", "robots.ts"), robotsTemplate(siteUrl !== null), "utf8");
|
|
942
|
+
console.log(chalk.green(siteUrl
|
|
943
|
+
? `🤖 Regenerated robots.ts with sitemap link`
|
|
944
|
+
: `🤖 Regenerated robots.ts (no sitemap link)`));
|
|
945
|
+
}
|
|
848
946
|
async stop() {
|
|
849
947
|
if (this.watcher) {
|
|
850
948
|
await this.watcher.close();
|
package/dist/lib/structures.js
CHANGED
|
@@ -10,7 +10,6 @@ import { ragRoutesTemplate } from "../templates/app/api/rag/route.js";
|
|
|
10
10
|
import { searchRoutesTemplate } from "../templates/app/api/search/route.js";
|
|
11
11
|
import { routesTemplate } from "../templates/app/api/theme/routes.js";
|
|
12
12
|
import { notFoundTemplate } from "../templates/app/not-found.js";
|
|
13
|
-
import { robotsTemplate } from "../templates/app/robots.js";
|
|
14
13
|
import { themeTemplate } from "../templates/app/theme.js";
|
|
15
14
|
import { chatTemplate } from "../templates/components/Chat.js";
|
|
16
15
|
import { clickOutsideTemplate } from "../templates/components/ClickOutside.js";
|
|
@@ -121,7 +120,6 @@ export const appStructure = {
|
|
|
121
120
|
"package.json": packageJsonTemplate,
|
|
122
121
|
"tsconfig.json": tsconfigTemplate,
|
|
123
122
|
"app/not-found.tsx": notFoundTemplate,
|
|
124
|
-
"app/robots.ts": robotsTemplate,
|
|
125
123
|
"app/theme.ts": themeTemplate,
|
|
126
124
|
"app/api/mcp/route.ts": mcpRoutesTemplate,
|
|
127
125
|
"app/api/rag/route.ts": ragRoutesTemplate,
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const robotsTemplate
|
|
1
|
+
export declare const robotsTemplate: (hasSiteUrl: boolean) => string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export const robotsTemplate =
|
|
1
|
+
export const robotsTemplate = (hasSiteUrl) => {
|
|
2
|
+
if (!hasSiteUrl) {
|
|
3
|
+
return `import type { MetadataRoute } from "next";
|
|
2
4
|
|
|
3
5
|
export default function robots(): MetadataRoute.Robots {
|
|
4
6
|
return {
|
|
@@ -9,3 +11,28 @@ export default function robots(): MetadataRoute.Robots {
|
|
|
9
11
|
};
|
|
10
12
|
}
|
|
11
13
|
`;
|
|
14
|
+
}
|
|
15
|
+
return `import type { MetadataRoute } from "next";
|
|
16
|
+
import { config } from "@/utils/config";
|
|
17
|
+
|
|
18
|
+
function resolveBaseUrl(): string | null {
|
|
19
|
+
const raw = process.env.NEXT_PUBLIC_SITE_URL ?? config.url;
|
|
20
|
+
if (!raw) return null;
|
|
21
|
+
return raw.replace(/\\/$/, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function robots(): MetadataRoute.Robots {
|
|
25
|
+
const base = resolveBaseUrl();
|
|
26
|
+
const rules: MetadataRoute.Robots = {
|
|
27
|
+
rules: {
|
|
28
|
+
userAgent: "*",
|
|
29
|
+
allow: "/",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
if (base) {
|
|
33
|
+
rules.sitemap = \`\${base}/sitemap.xml\`;
|
|
34
|
+
}
|
|
35
|
+
return rules;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
function formatEntries(entries) {
|
|
2
|
+
const lines = entries.map((entry) => {
|
|
3
|
+
const parts = [
|
|
4
|
+
` slug: ${JSON.stringify(entry.slug)}`,
|
|
5
|
+
entry.lastModified
|
|
6
|
+
? ` lastModified: ${JSON.stringify(entry.lastModified)}`
|
|
7
|
+
: null,
|
|
8
|
+
` changeFrequency: ${JSON.stringify(entry.changeFrequency)}`,
|
|
9
|
+
` priority: ${entry.priority}`,
|
|
10
|
+
].filter(Boolean);
|
|
11
|
+
return ` {\n${parts.join(",\n")},\n }`;
|
|
12
|
+
});
|
|
13
|
+
return `[\n${lines.join(",\n")},\n]`;
|
|
14
|
+
}
|
|
15
|
+
export const sitemapTemplate = (entries) => {
|
|
16
|
+
const entriesLiteral = formatEntries(entries);
|
|
17
|
+
return `import type { MetadataRoute } from "next";
|
|
18
|
+
import { config } from "@/utils/config";
|
|
19
|
+
|
|
20
|
+
type ChangeFrequency =
|
|
21
|
+
| "always"
|
|
22
|
+
| "hourly"
|
|
23
|
+
| "daily"
|
|
24
|
+
| "weekly"
|
|
25
|
+
| "monthly"
|
|
26
|
+
| "yearly"
|
|
27
|
+
| "never";
|
|
28
|
+
|
|
29
|
+
interface Entry {
|
|
30
|
+
slug: string;
|
|
31
|
+
lastModified?: string;
|
|
32
|
+
changeFrequency: ChangeFrequency;
|
|
33
|
+
priority: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ENTRIES: Entry[] = ${entriesLiteral};
|
|
37
|
+
|
|
38
|
+
function resolveBaseUrl(): string | null {
|
|
39
|
+
const raw = process.env.NEXT_PUBLIC_SITE_URL ?? config.url;
|
|
40
|
+
if (!raw) return null;
|
|
41
|
+
return raw.replace(/\\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
45
|
+
const base = resolveBaseUrl();
|
|
46
|
+
if (!base) return [];
|
|
47
|
+
|
|
48
|
+
return ENTRIES.map((entry) => ({
|
|
49
|
+
url: entry.slug === "" ? base : \`\${base}/\${entry.slug}\`,
|
|
50
|
+
lastModified: entry.lastModified ? new Date(entry.lastModified) : undefined,
|
|
51
|
+
changeFrequency: entry.changeFrequency,
|
|
52
|
+
priority: entry.priority,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const envExampleTemplate = "# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model\n# See available models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/models\n# Anthropic: https://docs.anthropic.com/claude/docs/models-overview\n# Google: https://ai.google.dev/models/gemini\n# LLM_CHAT_MODEL=\n\n# Optional: Override default embedding model\n# See available embedding models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/guides/embeddings\n# Google: https://ai.google.dev/gemini-api/docs/embeddings\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n";
|
|
1
|
+
export declare const envExampleTemplate = "# Public Site URL\n# Used by sitemap.xml and robots.txt. Overrides `url` in config.json when set.\n# NEXT_PUBLIC_SITE_URL=https://docs.example.com\n\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model\n# See available models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/models\n# Anthropic: https://docs.anthropic.com/claude/docs/models-overview\n# Google: https://ai.google.dev/models/gemini\n# LLM_CHAT_MODEL=\n\n# Optional: Override default embedding model\n# See available embedding models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/guides/embeddings\n# Google: https://ai.google.dev/gemini-api/docs/embeddings\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n";
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export const envExampleTemplate = `#
|
|
1
|
+
export const envExampleTemplate = `# Public Site URL
|
|
2
|
+
# Used by sitemap.xml and robots.txt. Overrides \`url\` in config.json when set.
|
|
3
|
+
# NEXT_PUBLIC_SITE_URL=https://docs.example.com
|
|
4
|
+
|
|
5
|
+
# LLM Provider Configuration
|
|
2
6
|
# Choose your preferred LLM provider: openai, anthropic, or google
|
|
3
7
|
LLM_PROVIDER=openai
|
|
4
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n# Deployment & Hosting\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Troubleshooting\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
|
|
1
|
+
export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n# Deployment & Hosting\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Sitemap and robots.txt\nDoccupine generates `sitemap.xml` and `robots.txt` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.\n\nSet the URL in `config.json`:\n\n```json\n{\n \"url\": \"https://docs.example.com\"\n}\n```\n\nAt deploy time, you can override the value with the `NEXT_PUBLIC_SITE_URL` environment variable - useful for preview deployments or staging environments.\n\n```bash\nNEXT_PUBLIC_SITE_URL=https://staging.example.com\n```\n\nThe generated sitemap includes every page from every [section](/sections), with `lastModified` derived from each page's frontmatter `date` or the file's modification time. When no URL is configured, the sitemap is skipped and `robots.txt` is emitted without a sitemap reference.\n\n### Troubleshooting\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
|
|
@@ -55,6 +55,25 @@ Doccupine generates a standard Next.js app, so you can deploy it anywhere that s
|
|
|
55
55
|
- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.
|
|
56
56
|
- **Node.js server** - run \`next build && next start\` on any server or VPS with Node.js installed.
|
|
57
57
|
|
|
58
|
+
### Sitemap and robots.txt
|
|
59
|
+
Doccupine generates \`sitemap.xml\` and \`robots.txt\` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.
|
|
60
|
+
|
|
61
|
+
Set the URL in \`config.json\`:
|
|
62
|
+
|
|
63
|
+
\`\`\`json
|
|
64
|
+
{
|
|
65
|
+
"url": "https://docs.example.com"
|
|
66
|
+
}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
At deploy time, you can override the value with the \`NEXT_PUBLIC_SITE_URL\` environment variable - useful for preview deployments or staging environments.
|
|
70
|
+
|
|
71
|
+
\`\`\`bash
|
|
72
|
+
NEXT_PUBLIC_SITE_URL=https://staging.example.com
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
The generated sitemap includes every page from every [section](/sections), with \`lastModified\` derived from each page's frontmatter \`date\` or the file's modification time. When no URL is configured, the sitemap is skipped and \`robots.txt\` is emitted without a sitemap reference.
|
|
76
|
+
|
|
58
77
|
### Troubleshooting
|
|
59
78
|
- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.
|
|
60
79
|
- **Missing content** - verify your MDX files and assets are in the repository.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 1\n---\n# Global Configuration\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\"\n}\n```\n\n## Fields\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n\n## Per-page overrides\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n|---|---|---|\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```\n\n";
|
|
1
|
+
export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 1\n---\n# Global Configuration\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n|---|---|---|\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```\n\n";
|
|
@@ -18,7 +18,8 @@ Place a \`config.json\` at your project root (the same folder where you execute
|
|
|
18
18
|
"name": "Doccupine",
|
|
19
19
|
"description": "${DEFAULT_DESCRIPTION}",
|
|
20
20
|
"icon": "${DEFAULT_FAVICON}",
|
|
21
|
-
"image": "${DEFAULT_OG_IMAGE}"
|
|
21
|
+
"image": "${DEFAULT_OG_IMAGE}",
|
|
22
|
+
"url": "https://docs.example.com"
|
|
22
23
|
}
|
|
23
24
|
\`\`\`
|
|
24
25
|
|
|
@@ -29,6 +30,7 @@ All fields are optional. Doccupine uses sensible defaults when a field is not se
|
|
|
29
30
|
- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.
|
|
30
31
|
- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.
|
|
31
32
|
- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.
|
|
33
|
+
- **url**: The public URL of your deployed site. Used as the base URL for \`sitemap.xml\` and \`robots.txt\`. When omitted, no sitemap is generated. Can be overridden at deploy time with the \`NEXT_PUBLIC_SITE_URL\` environment variable.
|
|
32
34
|
|
|
33
35
|
## Per-page overrides
|
|
34
36
|
Any page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over \`config.json\` for that page only.
|
|
@@ -10,29 +10,29 @@ export const packageJsonTemplate = JSON.stringify({
|
|
|
10
10
|
format: "prettier --write .",
|
|
11
11
|
},
|
|
12
12
|
dependencies: {
|
|
13
|
-
"@langchain/anthropic": "^1.3.
|
|
14
|
-
"@langchain/core": "^1.1.
|
|
15
|
-
"@langchain/google-genai": "^2.1.
|
|
16
|
-
"@langchain/openai": "^1.4.
|
|
13
|
+
"@langchain/anthropic": "^1.3.27",
|
|
14
|
+
"@langchain/core": "^1.1.41",
|
|
15
|
+
"@langchain/google-genai": "^2.1.28",
|
|
16
|
+
"@langchain/openai": "^1.4.4",
|
|
17
17
|
"@mdx-js/react": "^3.1.1",
|
|
18
18
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
|
-
"@posthog/react": "^1.
|
|
20
|
-
"cherry-styled-components": "^0.1.
|
|
21
|
-
langchain: "^1.
|
|
22
|
-
"lucide-react": "^1.
|
|
19
|
+
"@posthog/react": "^1.9.0",
|
|
20
|
+
"cherry-styled-components": "^0.1.17",
|
|
21
|
+
langchain: "^1.3.4",
|
|
22
|
+
"lucide-react": "^1.9.0",
|
|
23
23
|
minisearch: "^7.2.0",
|
|
24
|
-
next: "16.2.
|
|
24
|
+
next: "16.2.4",
|
|
25
25
|
"next-mdx-remote": "^6.0.0",
|
|
26
26
|
polished: "^4.3.1",
|
|
27
|
-
"posthog-js": "^1.
|
|
28
|
-
"posthog-node": "^5.
|
|
29
|
-
react: "19.2.
|
|
30
|
-
"react-dom": "19.2.
|
|
27
|
+
"posthog-js": "^1.371.2",
|
|
28
|
+
"posthog-node": "^5.30.0",
|
|
29
|
+
react: "19.2.5",
|
|
30
|
+
"react-dom": "19.2.5",
|
|
31
31
|
"rehype-highlight": "^7.0.2",
|
|
32
32
|
"rehype-parse": "^9.0.1",
|
|
33
33
|
"rehype-stringify": "^10.0.1",
|
|
34
34
|
"remark-gfm": "^4.0.1",
|
|
35
|
-
"styled-components": "^6.
|
|
35
|
+
"styled-components": "^6.4.1",
|
|
36
36
|
unified: "^11.0.5",
|
|
37
37
|
zod: "^4.3.6",
|
|
38
38
|
},
|
|
@@ -40,10 +40,10 @@ export const packageJsonTemplate = JSON.stringify({
|
|
|
40
40
|
"@types/node": "^25",
|
|
41
41
|
"@types/react": "^19",
|
|
42
42
|
"@types/react-dom": "^19",
|
|
43
|
-
"baseline-browser-mapping": "^2.10.
|
|
43
|
+
"baseline-browser-mapping": "^2.10.21",
|
|
44
44
|
eslint: "^9",
|
|
45
|
-
"eslint-config-next": "16.2.
|
|
46
|
-
prettier: "^3.8.
|
|
45
|
+
"eslint-config-next": "16.2.4",
|
|
46
|
+
prettier: "^3.8.3",
|
|
47
47
|
typescript: "^6",
|
|
48
48
|
},
|
|
49
49
|
}, null, 2) + "\n";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const configTemplate = "import { z } from \"zod\";\nimport configData from \"@/config.json\";\n\nconst configSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n image: z.string().optional(),\n});\n\ntype Config = z.infer<typeof configSchema>;\n\nexport const config: Config = configSchema.parse(configData);\n";
|
|
1
|
+
export declare const configTemplate = "import { z } from \"zod\";\nimport configData from \"@/config.json\";\n\nconst configSchema = z.object({\n name: z.string().optional(),\n description: z.string().optional(),\n icon: z.string().optional(),\n image: z.string().optional(),\n url: z.string().url().optional(),\n});\n\ntype Config = z.infer<typeof configSchema>;\n\nexport const config: Config = configSchema.parse(configData);\n";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doccupine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.88",
|
|
4
4
|
"description": "Free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,19 +39,19 @@
|
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"fs-extra": "^11.3.4",
|
|
41
41
|
"gray-matter": "^4.0.3",
|
|
42
|
-
"next": "^16.2.
|
|
42
|
+
"next": "^16.2.4",
|
|
43
43
|
"prompts": "^2.4.2",
|
|
44
|
-
"react": "^19.2.
|
|
45
|
-
"react-dom": "^19.2.
|
|
44
|
+
"react": "^19.2.5",
|
|
45
|
+
"react-dom": "^19.2.5"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/chokidar": "^2.1.7",
|
|
49
49
|
"@types/fs-extra": "^11.0.4",
|
|
50
|
-
"@types/node": "^25.
|
|
50
|
+
"@types/node": "^25.6.0",
|
|
51
51
|
"@types/prompts": "^2.4.9",
|
|
52
|
-
"prettier": "^3.8.
|
|
53
|
-
"typescript": "^6.0.
|
|
54
|
-
"vitest": "^4.1.
|
|
52
|
+
"prettier": "^3.8.3",
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^4.1.5"
|
|
55
55
|
},
|
|
56
56
|
"files": [
|
|
57
57
|
"dist/**/*"
|