"use strict"; import { parallel, queue } from "async"; import { exists, readFile, writeFileSync } from "fs"; import { mkdirsSync, remove } from "fs-extra"; import * as JSONParse from "json-parse-safe"; import { join } from "path"; import { gitLoader } from "./git/loader"; import { createGithub } from "./github-wrapper"; import { send as sendMail } from "./mail-sender"; import { writeReport } from "./report-processor"; import { processTask } from "./task-processor"; import { ReportResult, Settings } from "./types"; const codePostfix = ""; const mailLazinessLevel = 1000; const maxDescriptionLength = 140; const maxTmpcodepathLength = 15; const twoDigits = 100; const createFinalState = (isSuccess) => { if (isSuccess) { return "success"; } return "error"; }; const createBuildDoneMessage = (isSuccess, name) => { if (isSuccess) { return `Successfully built ${name}`; } return `Build failed for ${name}`; }; const notifyStatus = (settings: Settings, options, notifyStatusCallback) => { const status = { description: String(options.description || "").substr(0, maxDescriptionLength), owner: options.owner, repo: options.reponame, sha: options.hash, state: options.state, target_url: `${settings.siteRoot}status/${options.owner}/${options.reponame}/${options.hash}`, }; createGithub(settings, options.owner).repos.createStatus(status, (createStatusErr) => { if (createStatusErr) { console.log(`Error while creating status: ${createStatusErr}`); console.log(status); return notifyStatusCallback(createStatusErr); } return notifyStatusCallback(); }); }; const wrapGitLoader: (skipGitLoader: boolean) => typeof gitLoader = (skipGitLoader) => { if (!skipGitLoader) { return gitLoader; } return (_gitLoaderOptions, gitLoaderCallback) => process.nextTick(gitLoaderCallback); }; export const build = (settings: Settings, options, buildCallback) => { const url = options.url; const owner = options.owner; const reponame = options.reponame; const rev = options.rev; const branch = options.branch; const skipGitLoader = options.skipGitLoader; const local = join(settings.gitpath, "r"); const tmp = join(settings.tmpcodepath, rev.substr(0, maxTmpcodepathLength)); const exported = tmp + codePostfix; const release = join(settings.releasepath, owner, reponame, branch, rev); const statusQueue = queue((task: (callback: any) => void, queueCallback) => task(queueCallback), 1); const actualGitLoader = wrapGitLoader(skipGitLoader); const date = new Date(); const versionMajor = date.getFullYear(); const versionMinor = date.getMonth() + 1; const versionBuild = date.getDate(); const versionRev = (date.getHours() * twoDigits) + date.getMinutes(); const version = `${versionMajor}.${versionMinor}.${versionBuild}.${versionRev}`; const versionInfo = `${version}; built from ${rev}; repository: ${owner}/${reponame}; branch: ${branch}`; statusQueue.push((queueCallback) => notifyStatus(settings, { description: "Preparing to build...", hash: rev, owner, reponame, state: "pending", }, queueCallback)); mkdirsSync(release); writeFileSync(join(settings.releasepath, owner, reponame, branch, "latest.id"), rev); mkdirsSync(join(settings.releasepath, owner, reponame, "$revs")); writeFileSync(join(settings.releasepath, owner, reponame, "$revs", `${rev}.branch`), branch); const createErrorMessageForMail = (doneErr) => { if (!doneErr) { return ""; } return `Error message: ${doneErr}\r\n\r\n`; }; const createResultMessageForMail = (result) => { if (!result || !result.messages || !result.messages.$allMessages) { return JSON.stringify(result, null, " "); } return result.messages.$allMessages.map((msg) => `${msg.prefix}\t${msg.message}`).join("\r\n"); }; const done = (doneErr, result?: ReportResult) => { const allErrors = (result && result.errors && result.errors.$allMessages) || []; const allWarns = (result && result.warns && result.errors.$allMessages) || []; const allInfos = (result && result.infos && result.errors.$allMessages) || []; const errorMessage = (allErrors[0] && allErrors[0].message) || doneErr; const warnMessage = allWarns[0] && allWarns[0].message; const infoMessage = allInfos[allInfos.length - 1] && allInfos[allInfos.length - 1].message; writeReport(release, doneErr, result, (writeErr) => { statusQueue.push((queueCallback) => parallel([ (parallelCallback) => notifyStatus(settings, { description: errorMessage || warnMessage || infoMessage || "Success", hash: rev, owner, reponame, state: createFinalState(!doneErr), }, parallelCallback), (parallelCallback) => sendMail({ from: settings.smtp.sender, headers: { "X-Laziness-level": mailLazinessLevel }, subject: createBuildDoneMessage(doneErr, `${owner}/${reponame}/${branch}`), text: `Build status URL: ${settings.siteRoot}status/${owner}/${reponame}/${rev}\r\n\r\n${createErrorMessageForMail(doneErr)}${createResultMessageForMail(result)}`, to: settings.smtp.receiver, }, parallelCallback), (parallelCallback) => { if (doneErr) { return process.nextTick(parallelCallback); } return remove(tmp, parallelCallback); }, ], queueCallback)); if (writeErr) { return buildCallback(writeErr); } return buildCallback(doneErr, result); }); }; actualGitLoader({ branch, exported, hash: rev, local, remote: `${url}.git`, }, (gitLoaderErr) => { if (gitLoaderErr) { console.log(gitLoaderErr); return done(`Git fetch error: ${gitLoaderErr}`); } console.log("Done loading from git"); return exists(join(exported, "mbs.json"), (exists) => { if (!exists) { return done("MBSNotFound"); } return readFile(join(exported, "mbs.json"), (readErr, data) => { if (readErr) { return done(`MBSUnableToRead: ${readErr}`); } const { value, error } = JSONParse(data); if (error) { console.log(`Malformed data: ${data}`); return done("MBSMalformed"); } return processTask(settings, value, { branch, exported, owner, release, reponame, rev, versionInfo, }, (processErr, result) => { if (processErr) { return done(processErr, result); } return done(processErr, result); }); }); }); }); };