changemaker.lite/api/dist/modules/docs/mkdocs-config.service.js

190 lines
5.9 KiB
JavaScript

"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