"use strict"; import { join } from "path"; import { createReadStream, createWriteStream, exists } from "fs"; import { createGzip, createGunzip } from "zlib"; import * as glob from "glob"; import { ReadableStreamBuffer, WritableStreamBuffer } from "stream-buffers"; import * as _ from "underscore"; import * as JSONParse from "json-parse-safe"; interface Message { message: string; prefix: string; }; interface PartialMessagesLeaf { $messages: string[]; }; interface PartialMessagesRecursive { [propName: string]: Messages; }; interface PartialMessagesRoot { $allMessages: Message[]; }; type Messages = PartialMessagesLeaf & PartialMessagesRecursive; type MessagesRoot = PartialMessagesLeaf & PartialMessagesRecursive & PartialMessagesRoot; interface ReportResult { errors: MessagesRoot; warns: MessagesRoot; infos: MessagesRoot; messages: MessagesRoot; }; interface Report { date: number; err?: string; result?: ReportResult; }; const reportFilename = "report.json.gz"; const maxAttemptsNumber = 100; const attemptsTimeout = 30000; const reportReadTimeout = 5000; const directoryCheckTimeout = 2000; const attemptsDebugFrequency = 10; const readableStreamBufferOptions = { "chunkSize": 262144, "frequency": 1 }; const getAllErrors = (report: Report): Message[] => (report.result && report.result.errors && report.result.errors.$allMessages) || []; const getAllWarns = (report: Report): Message[] => (report.result && report.result.warns && report.result.errors.$allMessages) || []; const getAllInfos = (report: Report): Message[] => (report.result && report.result.infos && report.result.errors.$allMessages) || []; export const writeReport = (releaseDir, err, result: ReportResult, callback) => { const data = JSON.stringify({ "date": Date.now(), err, result }); const readable = new ReadableStreamBuffer(readableStreamBufferOptions); const writeStream = createWriteStream(join(releaseDir, reportFilename)); readable .on("error", callback) .pipe(createGzip()) .on("error", callback) .pipe(writeStream) .on("error", callback) .on("finish", () => { writeStream.end(); callback(); }); readable.put(data); readable.stop(); }; export const readReport = (releaseDir, callback) => { const readStream = createReadStream(join(releaseDir, reportFilename)); const writable = new WritableStreamBuffer(); readStream .on("error", callback) .pipe(createGunzip()) .on("error", callback) .pipe(writable) .on("error", callback) .on("finish", () => { readStream.destroy(); const data = writable.getContentsAsString(); if (!data) { return callback("ReportFileNotFound"); } const { error, value }: { error: any, value?: Report } = JSONParse(data); if (error) { return callback("ReportFileMalformed"); } return callback(null, value); }); }; export const loadReport = (app, options, callback) => { const releaseDir = join(app.get("releasepath"), options.owner, options.reponame, options.branch, options.rev); glob("**", { "cwd": releaseDir, "mark": true }, (err, files) => { if (err) { return callback(err, options); } const reportFile = join(releaseDir, reportFilename); return exists(reportFile, (reportFileExists) => { if (!reportFileExists) { return callback("ReportFileNotFound", options); } return readReport(releaseDir, (readErr, report) => { if (readErr) { return callback(readErr, _.extend(options, { files })); } return callback(null, _.extend(options, { files, report })); }); }); }); }; export const getStatusMessageFromRelease = (app, originalOptions, callback) => { const options = _.extend(originalOptions, { "attemptsGetReport": (Number(originalOptions.attemptsGetReport) || Number()) + 1 }); const releaseDir = join(app.get("releasepath"), options.owner, options.reponame, options.branch, options.rev); const reportFile = join(releaseDir, reportFilename); exists(reportFile, (reportFileExists) => { if (!reportFileExists) { return setTimeout(() => exists(releaseDir, (dirExists) => { if (!dirExists) { return callback("Release directory not found. Probably repository hooks are not configured"); } if (options.attemptsGetReport > maxAttemptsNumber) { return callback("Report file not found"); } // Maybe it is building right now if (!(options.attemptsGetReport % attemptsDebugFrequency) && options.onTenthAttempt) { options.onTenthAttempt(); } return setTimeout(() => exports.getStatusMessageFromRelease(app, options, callback), attemptsTimeout); }), directoryCheckTimeout); } return setTimeout(() => readReport(releaseDir, (readErr, report) => { if (readErr) { return callback(readErr); } if (report.result === "MBSNotFound") { return callback("mbs.json is not found"); } const errors = getAllErrors(report); const warns = getAllWarns(report); const infos = getAllInfos(report); if (errors.length + warns.length) { return callback(_.map( errors, (message) => `ERR: ${message.message}` ).concat(_.map( warns, (message) => `WARN: ${message.message}` )) .join("\r\n")); } if (!report.result || report.err) { return callback(`CRITICAL ERROR: ${report.err}`); } return callback(null, (infos[infos.length - 1] || { "message": "OK" }).message); }), reportReadTimeout); }); };