diff --git a/Dockerfile.docs b/Dockerfile.docs index 60c602f..97dd525 100644 --- a/Dockerfile.docs +++ b/Dockerfile.docs @@ -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 diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx index ee98bd2..523486e 100644 --- a/apps/docs/mdx-components.tsx +++ b/apps/docs/mdx-components.tsx @@ -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 }) => ( diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index dc4fcb1..24fe13c 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -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); diff --git a/apps/docs/scripts/generate-templates.mjs b/apps/docs/scripts/generate-templates.mjs index dd29edf..754eeb8 100644 --- a/apps/docs/scripts/generate-templates.mjs +++ b/apps/docs/scripts/generate-templates.mjs @@ -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* + +## Configuration + + + + \`\`\`yaml +${formatCodeForMdx(composeYaml)} + \`\`\` + + + \`\`\`toml +${formatCodeForMdx(templateToml)} + \`\`\` + + + +## 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