Files
dokploy/packages/server/src/templates/utils/index.ts
Mauricio Siu 6e7e7b3f9a feat(templates): refactor template processing and GitHub template fetching
- Implement new template processing utility in `processors.ts`
- Simplify GitHub template fetching with a more lightweight approach
- Add comprehensive test suite for template processing
- Improve type safety and modularity of template-related functions
2025-03-09 13:50:34 -06:00

119 lines
3.1 KiB
TypeScript

import { randomBytes } from "node:crypto";
import { existsSync } from "node:fs";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import type { Domain } from "@dokploy/server/services/domain";
import { TRPCError } from "@trpc/server";
import { fetchTemplateFiles } from "./github";
export interface Schema {
serverIp: string;
projectName: string;
}
export type DomainSchema = Pick<Domain, "host" | "port" | "serviceName"> & {
path?: string;
};
export interface Template {
envs: string[];
mounts: Array<{
filePath: string;
content: string;
}>;
domains: DomainSchema[];
}
export const generateRandomDomain = ({
serverIp,
projectName,
}: Schema): string => {
const hash = randomBytes(3).toString("hex");
const slugIp = serverIp.replaceAll(".", "-");
return `${projectName}-${hash}${slugIp === "" ? "" : `-${slugIp}`}.traefik.me`;
};
export const generateHash = (length = 8): string => {
return randomBytes(Math.ceil(length / 2))
.toString("hex")
.substring(0, length);
};
export const generatePassword = (quantity = 16): string => {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let password = "";
for (let i = 0; i < quantity; i++) {
password += characters.charAt(
Math.floor(Math.random() * characters.length),
);
}
return password.toLowerCase();
};
/**
* Generate a random base64 string of specified length
*/
export function generateBase64(length: number): string {
// To get N characters in base64, we need to generate N * 3/4 bytes
const bytesNeeded = Math.ceil((length * 3) / 4);
return Buffer.from(randomBytes(bytesNeeded))
.toString("base64")
.substring(0, length);
}
/**
* Reads a template's docker-compose.yml file
* First tries to fetch from GitHub, falls back to local cache if fetch fails
*/
export const readTemplateComposeFile = async (id: string) => {
// First try to fetch from GitHub
try {
const { dockerCompose } = await fetchTemplateFiles(id);
// Cache the file for future use
const cwd = process.cwd();
const templatePath = join(cwd, ".next", "templates", id);
const composeFilePath = join(templatePath, "docker-compose.yml");
// Ensure the template directory exists
if (!existsSync(templatePath)) {
await mkdir(templatePath, { recursive: true });
}
// Cache the file for future use
await writeFile(composeFilePath, dockerCompose, "utf8");
return dockerCompose;
} catch (error) {
console.warn(`Failed to fetch template ${id} from GitHub:`, error);
// Try to use cached version as fallback
const cwd = process.cwd();
const composeFilePath = join(
cwd,
".next",
"templates",
id,
"docker-compose.yml",
);
if (existsSync(composeFilePath)) {
console.warn(`Using cached version of template ${id}`);
return await readFile(composeFilePath, "utf8");
}
console.error(`Error: Template ${id} not found in GitHub or cache`);
throw new TRPCError({
code: "NOT_FOUND",
message: `Template ${id} not found or could not be fetched`,
});
}
};
/**
* Loads a template module from GitHub or local cache
* First tries to fetch from GitHub, falls back to local cache if fetch fails
*/