diff --git a/Gruntfile.js b/Gruntfile.js index 2b8c1c3e2603..045f057cd2dd 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -89,4 +89,5 @@ module.exports = function (grunt) { // load task definitions grunt.task.loadTasks('tasks'); grunt.task.loadTasks('tasks/build'); + grunt.task.loadTasks('tasks/rebuild'); }; diff --git a/tasks/rebuild/confirm.js b/tasks/rebuild/confirm.js new file mode 100644 index 000000000000..9a4c6b310f4a --- /dev/null +++ b/tasks/rebuild/confirm.js @@ -0,0 +1,36 @@ +import { execFileSync } from 'child_process'; +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { createInterface } from 'readline'; + +export default (grunt) => { + grunt.registerTask('_rebuild:confirm', function () { + const newVersion = grunt.option('buildversion') || grunt.config.get('pkg').version; + const newBuildNum = grunt.option('buildnum') || grunt.config.get('buildNum'); + const newSha = grunt.option('buildsha') || grunt.config.get('buildSha'); + + grunt.config('rebuild', { newVersion, newBuildNum, newSha }); + + grunt.log.writeln('Targets will be rebuilt with the following:'); + grunt.log.writeln(`Version: ${newVersion}`); + grunt.log.writeln(`Build number: ${newBuildNum}`); + grunt.log.writeln(`Build sha: ${newSha}`); + + const rl = createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.on('close', this.async()); + + rl.question('Do you want to rebuild these packages? [N/y] ', (resp) => { + const answer = resp.toLowerCase().trim(); + + if (answer === 'y') { + grunt.config.set('rebuild.continue', true); + } + + rl.close(); + }); + }); +}; diff --git a/tasks/rebuild/create_archives.js b/tasks/rebuild/create_archives.js new file mode 100644 index 000000000000..1593f66facaa --- /dev/null +++ b/tasks/rebuild/create_archives.js @@ -0,0 +1,23 @@ +import { execFileSync } from 'child_process'; +import { join } from 'path'; + +export default (grunt) => { + grunt.registerTask('_rebuild:createArchives', function () { + const buildDir = grunt.config.get('build'); + const targetDir = grunt.config.get('target'); + + grunt.file.mkdir('target'); + + grunt.file.expand({ cwd: buildDir }, '*').forEach(build => { + const tar = join(targetDir, `${build}.tar.gz`); + execFileSync('tar', ['-zchf', tar, build], { cwd: buildDir }); + + const zip = join(targetDir, `${build}.zip`); + if (/windows/.test(build)) { + execFileSync('zip', ['-rq', '-ll', zip, build], { cwd: buildDir }); + } else { + execFileSync('zip', ['-rq', zip, build], { cwd: buildDir }); + } + }); + }); +}; diff --git a/tasks/rebuild/extract_zips.js b/tasks/rebuild/extract_zips.js new file mode 100644 index 000000000000..497c1ec148c4 --- /dev/null +++ b/tasks/rebuild/extract_zips.js @@ -0,0 +1,14 @@ +import { execFileSync } from 'child_process'; + +export default (grunt) => { + grunt.registerTask('_rebuild:extractZips', function () { + const buildDir = grunt.config.get('build'); + const targetDir = grunt.config.get('target'); + + const zips = grunt.file.expand({ cwd: targetDir }, '*.zip'); + + zips.forEach(zip => { + execFileSync('unzip', [zip, '-d', buildDir], { cwd: targetDir }); + }); + }); +}; diff --git a/tasks/rebuild/index.js b/tasks/rebuild/index.js new file mode 100644 index 000000000000..d6d73b01bbb6 --- /dev/null +++ b/tasks/rebuild/index.js @@ -0,0 +1,66 @@ +import { execSync } from 'child_process'; +import { trim } from 'lodash'; + +/** + * Repackages all of the current archives in target/ with the same build + * number, sha, and commit hash. This is useful when all you need to do is bump + * the version of the release and do not want to introduce any other changes. + * + * Even if there are new commits, the standard build task reinstalls all npm + * dependencies, which introduces at least a small amount of risk of + * introducing bugs into the build since not all dependencies have fixed + * versions. + * + * Options: + * --skip-archives Will skip the archive step, useful for debugging + * --buildversion="1.2.3" Sets new version to 1.2.3 + * --buildnum="99999" Sets new build number to 99999 + * --buildsha="9a5b2c1" Sets new build sha to 9a5b2c1 (use the full sha, though) + */ +export default (grunt) => { + grunt.registerTask('rebuild', 'Rebuilds targets as a new version', function () { + grunt.task.run([ + '_build:getProps', + '_rebuild:confirm', + '_rebuild:continue' + ]); + }); + + grunt.registerTask('_rebuild:continue', function () { + grunt.task.requires('_rebuild:confirm'); + + if (!grunt.config.get('rebuild.continue')) { + grunt.log.writeln('Aborting without rebuilding anything'); + } else { + grunt.task.run([ + '_rebuild:builds', + '_rebuild:archives' + ]); + } + }); + + grunt.registerTask('_rebuild:builds', function () { + grunt.task.requires('_rebuild:continue'); + + grunt.task.run([ + 'clean:build', + '_rebuild:extractZips', + '_rebuild:updateBuilds' + ]); + }); + + grunt.registerTask('_rebuild:archives', function () { + grunt.task.requires('_rebuild:continue'); + + const skip = grunt.option('skip-archives'); + if (skip) { + grunt.log.writeln('Skipping archive step since rebuild debugging was enabled'); + } else { + grunt.task.run([ + 'clean:target', + '_rebuild:createArchives', + '_build:shasums' + ]); + } + }); +}; diff --git a/tasks/rebuild/update_builds.js b/tasks/rebuild/update_builds.js new file mode 100644 index 000000000000..d9a3a64caef2 --- /dev/null +++ b/tasks/rebuild/update_builds.js @@ -0,0 +1,57 @@ +import { execFileSync } from 'child_process'; +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +export default (grunt) => { + grunt.registerTask('_rebuild:updateBuilds', function () { + const buildDir = grunt.config.get('build'); + + const { newVersion, newBuildNum, newSha } = grunt.config.get('rebuild'); + + grunt.file.expand({ cwd: buildDir }, '*').forEach(build => { + const thisBuildDir = join(buildDir, build); + const thisBundlesDir = join(thisBuildDir, 'optimize', 'bundles'); + + const readmePath = join(thisBuildDir, 'README.txt'); + const pkgjsonPath = join(thisBuildDir, 'package.json'); + const bundlePaths = [ + ...grunt.file.expand({ cwd: thisBundlesDir }, '*.bundle.js'), + ...grunt.file.expand({ cwd: thisBundlesDir }, '*.entry.js') + ]; + + const { oldBuildNum, oldSha, oldVersion } = readBuildInfo(pkgjsonPath); + + replaceIn(readmePath, oldVersion, newVersion); + replaceIn(pkgjsonPath, oldVersion, newVersion); + replaceIn(pkgjsonPath, `"number": ${oldBuildNum},`, `"number": ${newBuildNum},`); + replaceIn(pkgjsonPath, oldSha, newSha); + bundlePaths + .map(bundle => join(thisBundlesDir, bundle)) + .forEach(file => { + replaceIn(file, `"kbnVersion":"${oldVersion}"`, `"kbnVersion":"${newVersion}"`); + replaceIn(file, `"buildNum":${oldBuildNum}`, `"buildNum":${newBuildNum}`); + }); + + const newBuild = build.replace(oldVersion, newVersion); + if (build !== newBuild) { + execFileSync('mv', [ build, newBuild ], { cwd: buildDir }); + } + }); + }); +}; + +function readBuildInfo(path) { + const pkgjson = readFileSync(path).toString(); + const pkg = JSON.parse(pkgjson); + return { + oldBuildNum: pkg.build.number, + oldSha: pkg.build.sha, + oldVersion: pkg.version + }; +} + +function replaceIn(path, oldValue, newValue) { + let contents = readFileSync(path).toString(); + contents = contents.replace(oldValue, newValue); + writeFileSync(path, contents); +}