const package = require("../package.json"); const packageLock = require("../package-lock.json"); const readline = require("readline"); const fs = require("fs"); const exec = require("child_process").exec; const chalk = require("chalk"); const branchName = require('current-git-branch'); const REL_NOTES_FILE = `release-notes.md`; const branch = "master"; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); startRelease().then(() => { rl.close(); }); // ----------------------------------------------------------------------------- // Relase Script // ----------------------------------------------------------------------------- async function startRelease() { // First check we are on master if (branchName() !== branch) { logError(`You shall not pass! You are not on the ${branch} branch!`) return; } log(`*** Owl release script ***`); log(`Current Version: ${package.version}`); log(`Warning: this script will push to the master branch!`); log(`Make sure that github is configured to allow it:`); log(` settings => branches => edit master => uncheck Do not allow bypassing the above settings`); log(` (and probably a good idea to readd the protection after)`) const STEPS = 11; let step = 1; // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: Checking if code formatting is right...`) const checkFormatting = await execCommand("npm run check-formatting"); if (checkFormatting !== 0) { logError("Prettier format validation failed. Aborting."); return; } // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: collecting info...`); let next = await ask("Next version: "); if (next[0] === 'v') next = next.substring(1); let file = await ask(`Release notes (${REL_NOTES_FILE}): `); file = file || REL_NOTES_FILE; if (!fs.existsSync(`./${file}`)) { const lastRelease = await getOutput("git log --grep='\\[REL\\]' -n 1 --pretty=%H"); const commitsSinceLastRelease = await getOutput(`git log ${lastRelease.trim()}..HEAD --pretty=%s`); const commitsAsMdList = commitsSinceLastRelease.trim().split("\n").map(l => " - " + l).join("\n"); log(`${file} did not exist, created a template containing all commits since last release.`) fs.writeFileSync(file, `# v${next}\n\n${commitsAsMdList}`); const shouldContinue = await ask(`Check that the contents of ${file} is correct, then press y to continue: `); if (shouldContinue.toLowerCase() !== "y") { log("aborted"); return; } } let content; try { content = await readFile("./" + file); } catch (e) { logSubContent(e.message); logError("Cannot find release notes... Aborting"); return; } let shouldBeDraft = await ask(`Should be a draft [y/n] ? (n)`); let draft = "" if (shouldBeDraft.toLowerCase() === 'y') { draft = "--draft"; } // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: running tests...`); const testsResult = await execCommand("npm run test"); if (testsResult !== 0) { logError("Test suite does not pass. Aborting."); return; } // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: updating package.json...`); await writeFile("package.json", JSON.stringify({...package, version: next}, null, 2) + "\n"); await writeFile("package-lock.json", JSON.stringify({...packageLock, version: next}, null, 2) + "\n"); await writeFile("./src/version.ts", `// do not modify manually. This file is generated by the release script.\nexport const version = "${next}";\n`); // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: building owl...`); await execCommand("rm -rf dist/"); const buildResult = await execCommand("npm run build"); if (buildResult !== 0) { logError("Build failed. Aborting."); return; } // ---------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: building devtools...`); const chromeResult = await execCommand("npm run build:devtools-chrome"); if (chromeResult !== 0) { logError("Build devtools chrome failed. Aborting."); return; } await execCommand("mv dist/devtools dist/devtools-chrome"); const firefoxResult = await execCommand("npm run build:devtools-firefox"); if (firefoxResult !== 0) { logError("Build devtools firefox failed. Aborting."); return; } await execCommand("mv dist/devtools dist/devtools-firefox"); await execCommand("cd dist && zip -r owl-devtools.zip devtools-chrome devtools-firefox && cd .."); await execCommand("rm -r dist/devtools-chrome dist/devtools-firefox && rm dist/compiler.js"); // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: updating owl on github page...`); await fs.copyFileSync("dist/owl.es.js", "docs/owl.js"); // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: creating git commit...`); const escapedContent = content.replace(/\"/g, '\\\"').replace(/\`/g, '\\\`'); const gitResult = await execCommand(`git commit -am "[REL] v${next}\n\n${escapedContent}"`); if (gitResult !== 0) { logError("Git commit failed. Aborting."); return; } // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: pushing on github...`); const pushResult = await execCommand("git push origin " + branch); if (pushResult !== 0) { logError("git push failed. Aborting."); return; } // --------------------------------------------------------------------------- log(`Step ${step++}/${STEPS}: Creating the release...`); const relaseResult = await execCommand(`gh release create v${next} dist/*.js dist/*.zip ${draft} -F ${file}`); if (relaseResult !== 0) { logError("github release failed. Aborting."); return; } log(`Step ${step++}/${STEPS}: publishing module on npm...`); await execCommand("npm publish"); log("Owl Release process completed! Thank you for your patience"); await execCommand(`gh release view`); await execCommand(`gh release view -w`); } // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- function log(text) { console.log(chalk.yellow(formatLog(text))); } function logError(text) { console.log(chalk.red(formatLog(text))); } function formatLog(text) { return `[REL] ${text}`; } function logSubContent(text) { for (let line of text.split("\n")) { if (line.trim()) { console.log(" " + line); } } } function ask(question) { return new Promise(resolve => { rl.question(question, result => { resolve(result); }); }); } function logStream(stream) { stream.on("data", data => { logSubContent(data); }); } function execCommand(command) { return new Promise(resolve => { const childProcess = exec(command, (err, stdout, stderr) => { if (err) { resolve(err.code); } }); childProcess.on("exit", code => { resolve(code); }); logStream(childProcess.stdout); logStream(childProcess.stderr); }); } function readFile(file) { return new Promise((resolve, reject) => { fs.readFile(file, "utf8", function(err, content) { if (err) { reject(err); } else { resolve(content); } }); }); } function writeFile(file, content) { return new Promise((resolve, reject) => { fs.writeFile(file, content, "utf8", err => { if (err) { reject(err); } else { resolve(); } }); }); } async function getOutput(command) { return new Promise((resolve, reject) => { const childProcess = exec(command, (err, stdout, stderr) => { if (err) { reject(err); } resolve(stdout); }); childProcess.on("exit", code => { if (code !== 0) { reject(code); } }); }); }