"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mkdocsConfigService = void 0; const promises_1 = require("fs/promises"); const http_1 = require("http"); const env_1 = require("../../config/env"); const logger_1 = require("../../utils/logger"); const yaml_1 = require("yaml"); const DOCKER_SOCKET = '/var/run/docker.sock'; /** * Custom YAML tag schema to preserve !!python/name: and !!python/object: tags. * Without this, the yaml library would reject these custom tags. */ const pythonNameTag = { identify: () => false, tag: '!python/name', collection: undefined, resolve: (str) => str, }; const pythonObjectTag = { identify: () => false, tag: '!python/object', collection: undefined, resolve: (str) => str, }; /** * Parse mkdocs.yml content with support for !!python/name: tags. * Returns a yaml Document that preserves comments and formatting. */ function parseConfig(content) { return (0, yaml_1.parseDocument)(content, { customTags: (tags) => [ ...tags, pythonNameTag, pythonObjectTag, ], keepSourceTokens: true, }); } /** * Validate that a string is valid YAML (with python tags support). * Returns null if valid, error message if invalid. */ function validateYaml(content) { try { const doc = parseConfig(content); const errors = doc.errors; if (errors.length > 0) { return errors.map(e => e.message).join('; '); } return null; } catch (err) { return err.message; } } async function readConfig() { return (0, promises_1.readFile)(env_1.env.MKDOCS_CONFIG_PATH, 'utf-8'); } async function writeConfig(content) { // Validate YAML first const error = validateYaml(content); if (error) { throw new Error(`Invalid YAML: ${error}`); } // Create backup try { await (0, promises_1.copyFile)(env_1.env.MKDOCS_CONFIG_PATH, `${env_1.env.MKDOCS_CONFIG_PATH}.bak`); } catch { logger_1.logger.warn('Could not create backup of mkdocs.yml'); } await (0, promises_1.writeFile)(env_1.env.MKDOCS_CONFIG_PATH, content, 'utf-8'); } /** * Make a request to the Docker Engine API over Unix socket. */ function dockerRequest(method, path, body) { return new Promise((resolve, reject) => { const options = { socketPath: DOCKER_SOCKET, path, method, headers: body ? { 'Content-Type': 'application/json' } : undefined, }; const req = (0, http_1.request)(options, (res) => { const chunks = []; res.on('data', (chunk) => chunks.push(chunk)); res.on('end', () => { resolve({ statusCode: res.statusCode || 0, body: Buffer.concat(chunks).toString(), }); }); }); req.on('error', reject); if (body) { req.write(JSON.stringify(body)); } req.end(); }); } /** * Read raw output from a Docker exec start stream. * Docker multiplexes stdout/stderr with 8-byte headers. */ function demuxDockerStream(raw) { const lines = []; let offset = 0; while (offset < raw.length) { if (offset + 8 > raw.length) break; // byte 0: stream type (1=stdout, 2=stderr) const size = raw.readUInt32BE(offset + 4); offset += 8; if (offset + size > raw.length) { lines.push(raw.subarray(offset).toString('utf-8')); break; } lines.push(raw.subarray(offset, offset + size).toString('utf-8')); offset += size; } return lines.join(''); } /** * Execute `mkdocs build` inside the running MkDocs container via Docker Engine API. */ async function triggerBuild() { const containerName = env_1.env.MKDOCS_CONTAINER_NAME; const startTime = Date.now(); try { // 1. Create exec instance const execCreate = await dockerRequest('POST', `/containers/${containerName}/exec`, { AttachStdout: true, AttachStderr: true, Cmd: ['mkdocs', 'build', '--clean'], }); if (execCreate.statusCode !== 201) { throw new Error(`Failed to create exec: ${execCreate.body}`); } const { Id: execId } = JSON.parse(execCreate.body); // 2. Start exec and collect output const execOutput = await new Promise((resolve, reject) => { const options = { socketPath: DOCKER_SOCKET, path: `/exec/${execId}/start`, method: 'POST', headers: { 'Content-Type': 'application/json' }, }; const req = (0, http_1.request)(options, (res) => { const chunks = []; res.on('data', (chunk) => chunks.push(chunk)); res.on('end', () => resolve(Buffer.concat(chunks))); }); req.on('error', reject); req.write(JSON.stringify({ Detach: false, Tty: false })); req.end(); }); const output = demuxDockerStream(execOutput); // 3. Check exit code const execInspect = await dockerRequest('GET', `/exec/${execId}/json`); const inspectData = JSON.parse(execInspect.body); const exitCode = inspectData.ExitCode ?? -1; const duration = Date.now() - startTime; return { success: exitCode === 0, output: output || '(no output)', duration, }; } catch (err) { const duration = Date.now() - startTime; logger_1.logger.error('MkDocs build failed', err); return { success: false, output: `Build error: ${err.message}`, duration, }; } } exports.mkdocsConfigService = { readConfig, writeConfig, validateYaml, parseConfig, triggerBuild, }; //# sourceMappingURL=mkdocs-config.service.js.map