Enhance documentation generation and template handling

- Added a new script to generate templates from external sources, improving the documentation process.
- Integrated new components (Tabs and Tab) into the MDX documentation for better content organization.
- Updated the Next.js configuration to allow images from the templates URL.
- Implemented batch processing for fetching and generating template files, enhancing performance and error handling.

These changes streamline the documentation generation workflow and improve the user experience when accessing template information.
This commit is contained in:
Mauricio Siu
2026-01-29 18:45:01 -06:00
parent 08c47af4ec
commit 947c8652f9
4 changed files with 128 additions and 8 deletions

View File

@@ -14,6 +14,8 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --filter=./apps/d
# Generate OpenAPI documentation from apps/docs/public/openapi.json
RUN pnpm --filter=./apps/docs run build:docs
# Generate templates
RUN pnpm --filter=./apps/docs run generate-templates
# Deploy only the dokploy app
ENV NODE_ENV=production

View File

@@ -1,6 +1,7 @@
import { APIPage } from "@/lib/source";
import { Callout } from "fumadocs-ui/components/callout";
import { ImageZoom } from "fumadocs-ui/components/image-zoom";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import defaultMdxComponents from "fumadocs-ui/mdx";
import type { MDXComponents } from "mdx/types";
@@ -9,6 +10,8 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
...defaultMdxComponents,
ImageZoom,
Callout,
Tab,
Tabs,
APIPage,
...components,
p: ({ children }) => (

View File

@@ -5,6 +5,14 @@ const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
images: {
remotePatterns: [
{
protocol: "https",
hostname: "templates.dokploy.com",
},
],
},
};
export default withMDX(config);

View File

@@ -2,29 +2,135 @@ import fs from 'node:fs';
import path from 'node:path';
const TEMPLATES_URL = 'https://templates.dokploy.com/meta.json';
const BASE_BLUEPRINT_URL = 'https://templates.dokploy.com/blueprints';
const OUTPUT_DIR = './content/docs/templates';
async function fetchWithTimeout(url, options = {}, timeout = 10000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(id);
return response;
} catch (e) {
clearTimeout(id);
throw e;
}
}
async function getTemplateFile(id, fileName) {
try {
const response = await fetchWithTimeout(`${BASE_BLUEPRINT_URL}/${id}/${fileName}`);
if (!response.ok) return null;
return await response.text();
} catch (error) {
console.error(`Error fetching ${fileName} for ${id}:`, error.message);
return null;
}
}
/** Normalize and indent code so it renders correctly inside MDX code blocks (preserves YAML/TOML formatting). */
function formatCodeForMdx(code) {
if (!code || !code.trim()) return '# Not available';
return code
.replace(/\r\n/g, '\n')
.trim()
.split('\n')
.map((line) => line.trimEnd())
.join('\n')
.split('\n')
.map(line => ` ${line}`)
.join('\n');
}
/** Build Base64 payload for Dokploy import (same format as UI: compose + config as JSON, then base64). */
function templateToBase64(dockerCompose, config) {
const configObj = {
compose: dockerCompose || '',
config: config || '',
};
const jsonString = JSON.stringify(configObj, null, 2);
return Buffer.from(jsonString, 'utf-8').toString('base64');
}
async function generateTemplates() {
try {
console.log('Fetching templates...');
console.log('Fetching templates metadata...');
const response = await fetch(TEMPLATES_URL);
const templates = await response.json();
console.log(`Found ${templates.length} templates.`);
console.log(`Found ${templates.length} templates. Starting data collection...`);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
// Generate individual MDX files
for (const template of templates) {
const safeDescription = template.description.replace(/"/g, '\\"');
const safeName = template.name.replace(/"/g, '\\"');
const mdxContent = `---
const batchSize = 10;
for (let i = 0; i < templates.length; i += batchSize) {
const batch = templates.slice(i, i + batchSize);
console.log(`Processing batch ${i / batchSize + 1}/${Math.ceil(templates.length / batchSize)}...`);
await Promise.all(batch.map(async (template) => {
const composeYaml = await getTemplateFile(template.id, 'docker-compose.yml');
const templateToml = await getTemplateFile(template.id, 'template.toml');
const instructionsRaw = await getTemplateFile(template.id, 'instructions.md');
const hasRealInstructions =
instructionsRaw &&
instructionsRaw.trim().length > 0 &&
!/^\s*<!doctype/i.test(instructionsRaw.trim()) &&
!/^\s*<html\b/i.test(instructionsRaw.trim());
const instructionsSafe = hasRealInstructions
? instructionsRaw.trim().replace(/\$\{/g, '\\${')
: '';
const safeDescription = template.description.replace(/"/g, '\\"');
const safeName = template.name.replace(/"/g, '\\"');
const logoUrl = `${BASE_BLUEPRINT_URL}/${template.id}/${template.logo}`;
const mdxContent = `---
title: "${safeName}"
description: "${safeDescription}"
---
<ImageZoom
src="${logoUrl}"
alt="${template.name} logo"
width={100}
height={100}
className="my-8 rounded-xl"
/>
## Configuration
<Tabs items={["docker-compose.yml", "template.toml"]}>
<Tab value="docker-compose.yml">
\`\`\`yaml
${formatCodeForMdx(composeYaml)}
\`\`\`
</Tab>
<Tab value="template.toml">
\`\`\`toml
${formatCodeForMdx(templateToml)}
\`\`\`
</Tab>
</Tabs>
## Base64
To import this template in Dokploy: create a **Compose** service → **Advanced** → **Base64 import** and paste the content below:
\`\`\`text
${templateToBase64(composeYaml, templateToml)}
\`\`\`
${instructionsSafe ? `
## Instructions
${instructionsSafe}
` : ''}
## Links
${template.links.website ? `- [Website](${template.links.website})` : ''}
${template.links.github ? `- [Github](${template.links.github})` : ''}
@@ -37,7 +143,8 @@ ${template.tags.map(tag => `\`${tag}\``).join(', ')}
Version: \`${template.version}\`
`;
fs.writeFileSync(path.join(OUTPUT_DIR, `${template.id}.mdx`), mdxContent);
fs.writeFileSync(path.join(OUTPUT_DIR, `${template.id}.mdx`), mdxContent);
}));
}
// Generate index.mdx