diff --git a/packages/app/main/gulpfile.common.js b/packages/app/main/gulpfile.common.js new file mode 100644 index 000000000..7ada729b7 --- /dev/null +++ b/packages/app/main/gulpfile.common.js @@ -0,0 +1,158 @@ +const packageJson = require('./package.json'); +const gulp = require ('gulp'); + +const defaultElectronMirror = 'https://github.com/electron/electron/releases/download/v'; +const defaultElectronVersion = packageJson.devDependencies["electron"]; +const githubAccountName = "Microsoft"; +const githubRepoName = "BotFramework-Emulator"; +const appId = "F3C061A6-FE81-4548-82ED-C1171D9856BB"; + +/** Copies extension json files into built */ +gulp.task('copy-extension-stubs', function () { + return gulp + .src('./src/extensions/**/*') + .pipe(gulp.dest('./app/extensions')); +}); + +/** Checks all files for missing GitHub copyright text and reports missing files */ +gulp.task('verify:copyright', function () { + const lernaRoot = '../../../'; + const lernaJson = require(join(lernaRoot, 'lerna.json')); + const files = lernaJson.packages.filter(p => !/\/custom-/.test(p)).map(dir => join(lernaRoot, dir, '**/*.@(js|jsx|ts|tsx)')); + const filesWithoutCopyright = []; + let count = 0; + let scanned = 0; + + return gulp + .src(files, { buffer: false }) + .pipe(through2( + (file, _, callback) => { + const filename = file.history[0]; + + count++; + + if ( + // TODO: Instead of using pattern, we should use .gitignore + !/[\\\/](build|built|lib|node_modules)[\\\/]/.test(filename) + && !file.isDirectory() + ) { + callback(null, file); + } else { + callback(); + } + } + )) + .pipe(buffer()) + .pipe(through2( + (file, _, callback) => { + const filename = file.history[0]; + const first1000 = file.contents.toString('utf8', 0, 1000); + + if (!~first1000.indexOf('Copyright (c) Microsoft Corporation')) { + filesWithoutCopyright.push(relative(process.cwd(), filename)); + } + + scanned++; + + callback(); + }, + callback => { + log.info(`Verified ${chalk.magenta(scanned)} out of ${chalk.magenta(count)} files with copyright header`); + + if (filesWithoutCopyright.length) { + log.error(chalk.red('Copyright header is missing from the following files:')); + filesWithoutCopyright.forEach(filename => log.error(chalk.magenta(filename))); + callback(new Error('missing copyright header')); + } else { + callback(); + } + } + )); +}); + +/** Gets an environment variable value with the provided name */ +function getEnvironmentVar(name, defaultValue = undefined) { + return (process.env[name] === undefined) ? defaultValue : process.env[name] +} + +/** Replaces an environment variable */ +function replaceEnvironmentVar(str, name, defaultValue = undefined) { + const value = getEnvironmentVar(name, defaultValue); + if (value === undefined) + throw new Error(`Required environment variable missing: ${name}`); + return str.replace(new RegExp('\\${' + name + '}', 'g'), value); +} + +/** Replaces a packaging-related environment variable */ +function replacePackageEnvironmentVars(obj) { + let str = JSON.stringify(obj); + str = replaceEnvironmentVar(str, "ELECTRON_MIRROR", defaultElectronMirror); + str = replaceEnvironmentVar(str, "ELECTRON_VERSION", defaultElectronVersion); + str = replaceEnvironmentVar(str, "appId", appId); + return JSON.parse(str); +} + +/** Returns the Electron Mirror URL from where electron is downloaded */ +function getElectronMirrorUrl() { + return `${getEnvironmentVar("ELECTRON_MIRROR", defaultElectronMirror)}${getEnvironmentVar("ELECTRON_VERSION", defaultElectronVersion)}`; +} + +/** Gets the config file for a specific platform */ +function getConfig(platform, target) { + return extend({}, + replacePackageEnvironmentVars(require('./scripts/config/common.json')), + replacePackageEnvironmentVars(require(`./scripts/config/${platform}.json`)), + (target ? replacePackageEnvironmentVars(require(`./scripts/config/${platform}-${target}.json`)) : {}) + ); +} + +/** _.extend */ +function extend(...sources) { + let output = {}; + sources.forEach(source => { + extend1(output, source); + }); + return output; +} + +function extend1(destination, source) { + for (var property in source) { + if (source[property] && source[property].constructor && + source[property].constructor === Object) { + destination[property] = destination[property] || {}; + arguments.callee(destination[property], source[property]); + } else { + destination[property] = source[property]; + } + } + return destination; +}; + +/** Hashes a file asynchronously */ +function hashFileAsync(filename, algo = 'sha512', encoding = 'base64') { + var builderUtil = require('builder-util'); + return builderUtil.hashFile(filename, algo, encoding); +} + +/** Sets the packaged artifact filenames */ +function getReleaseFilename() { + const releaseVersion = getEnvironmentVar('EMU_VERSION', packageJson.version); + const releasePlatform = getEnvironmentVar('EMU_PLATFORM'); + if (!releasePlatform) { + throw new Error('Environment variable EMU_PLATFORM missing. Please retry with valid value.'); + } + const releaseName = `${packageJson.packagename}-${releaseVersion}-${releasePlatform}`; + + return releaseName; +} + +module.exports = { + extend, + getConfig, + getEnvironmentVar, + getElectronMirrorUrl, + getReleaseFilename, + githubAccountName, + githubRepoName, + hashFileAsync +}; diff --git a/packages/app/main/gulpfile.js b/packages/app/main/gulpfile.js deleted file mode 100644 index 589863450..000000000 --- a/packages/app/main/gulpfile.js +++ /dev/null @@ -1,772 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. -// -// Microsoft Bot Framework: http://botframework.com -// -// Bot Framework Emulator Github: -// https://github.com/Microsoft/BotFramwork-Emulator -// -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License: -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// - -const { join, relative } = require('path'); -const buffer = require('vinyl-buffer'); -const chalk = require('chalk'); -const gulp = require('gulp'); -const log = require('fancy-log'); -const pjson = require('./package.json'); -const shell = require('gulp-shell'); -const through2 = require('through2').obj; - -const defaultElectronMirror = 'https://github.com/electron/electron/releases/download/v'; -const defaultElectronVersion = pjson.devDependencies["electron"]; -const githubAccountName = "Microsoft"; -const githubRepoName = "BotFramework-Emulator"; -const appId = "F3C061A6-FE81-4548-82ED-C1171D9856BB"; - -//============================================================================ -// BUILD -//============================================================================ - -//---------------------------------------------------------------------------- -gulp.task('clean', function () { - const clean = require('gulp-clean'); - return gulp.src('./app/', { read: false, allowEmpty: true }) - .pipe(clean()); -}); - -//---------------------------------------------------------------------------- -gulp.task('copy-extension-stubs', function () { - return gulp - .src('./src/extensions/**/*') - .pipe(gulp.dest('./app/extensions')); -}); - -//============================================================================ -// GET-LICENSES -//============================================================================ - -//---------------------------------------------------------------------------- -gulp.task('get-licenses', function () { - const licenses = require('license-list'); - const source = require('vinyl-source-stream'); - const lerna = require('../../../lerna.json'); - const stream = source('../../../ThirdPartyLicenses.txt'); - - const formatLicense = (pkgInfo) => { - const formatLicenseFile = () => { - if (typeof pkgInfo.licenseFile === 'string') { - return pkgInfo.licenseFile.split(/\n/).map(line => `\t${line}`).join('\n'); - } else { - return '\tLICENSE file does not exist'; - } - } - return `${pkgInfo.name}@${pkgInfo.version} (${pkgInfo.license})\n\n${formatLicenseFile()}\n\n`; - } - - const tasks = lerna.packages.map(package => licenses(`../../../${package}`), { dev: false }); - - return Promise.all(tasks) - .then(values => { - const main = values[0]; - const client = values[1]; - const packages = { - ...main, - ...client - }; - const keys = Object.keys(packages).sort().filter(key => - !key.startsWith(`${pjson.name}@`) && - !key.startsWith('@bfemulator')) - keys.forEach(pkgId => { - const pkgInfo = packages[pkgId]; - stream.write(formatLicense(pkgInfo)); - }) - stream.end(); - stream.pipe(gulp.dest('.')); - }) - .catch(err => { - console.log(err) - }); -}); - -//============================================================================ -// STAGE -// Stages the application folder from which redistributables are built. -//============================================================================ - -//============================================================================ -// STAGE:WINDOWS - -//---------------------------------------------------------------------------- -gulp.task('stage:windows', function () { - var builder = require('electron-builder'); - const config = getConfig("windows", "dir"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.WINDOWS.createTarget(["dir"], builder.Arch.ia32, builder.Arch.x64), - config - }); -}); - -//============================================================================ -// STAGE:MAC - -//---------------------------------------------------------------------------- -gulp.task('stage:mac', function () { - var builder = require('electron-builder'); - const config = getConfig("mac", "dir"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.MAC.createTarget(["dir"]), - config - }); -}); - -//============================================================================ -// STAGE:LINUX - -//---------------------------------------------------------------------------- -gulp.task('stage:linux', function () { - var builder = require('electron-builder'); - const config = getConfig("linux", "dir"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.LINUX.createTarget(["dir"]), - config - }); -}); - -//============================================================================ -// REDIST -// Builds a redistributable from the staged application folder. -//============================================================================ - -//---------------------------------------------------------------------------- -function hashFileAsync(filename, algo = 'sha512', encoding = 'base64') { - var builderUtil = require('builder-util'); - return builderUtil.hashFile(filename, algo, encoding); -} - -//---------------------------------------------------------------------------- -function writeYamlMetadataFile(releaseFilename, yamlFilename, path, fileHash, releaseDate, extra = {}) { - var fsp = require('fs-extra'); - var yaml = require('js-yaml'); - - const ymlInfo = { - version: pjson.version, - releaseDate: releaseDate, - githubArtifactName: releaseFilename, - path: releaseFilename, - sha512: fileHash - }; - const obj = extend({}, ymlInfo, extra); - const ymlStr = yaml.safeDump(obj); - fsp.writeFileSync(`./${path}/${yamlFilename}`, ymlStr); -} - -//---------------------------------------------------------------------------- -function writeJsonMetadataFile(releaseFilename, jsonFilename, path, releaseDate) { - var fsp = require('fs-extra'); - - const jsonInfo = { - version: pjson.version, - releaseDate: releaseDate, - url: `https://github.com/${githubAccountName}/${githubRepoName}/releases/v${pjson.version}/${releaseFilename}` - }; - fsp.outputJsonSync(`./${path}/${jsonFilename}`, jsonInfo, { spaces: 2 }); -} - -//============================================================================ -// REDIST:WINDOWS-NSIS - -//---------------------------------------------------------------------------- -gulp.task('redist:windows-nsis:binaries', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("windows", "nsis"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.WINDOWS.createTarget(["nsis"], builder.Arch.ia32), - config, - prepackaged: './dist/win-ia32-unpacked' - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('redist:windows-nsis:metadata-only', function () { - const config = getConfig("windows", "nsis"); - const releaseFilename = config.nsis.artifactName.replace('${version}', pjson.version); - const sha512 = hashFileAsync(`./dist/${releaseFilename}`); - const sha2 = hashFileAsync(`./dist/${releaseFilename}`, 'sha256', 'hex'); - const releaseDate = new Date().toISOString(); - - return Promise.all([sha512, sha2]) - .then((values) => { - writeYamlMetadataFile(releaseFilename, 'latest.yml', './dist', values[0], releaseDate, { sha2: values[1] }); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('redist:windows-nsis:metadata', - gulp.series('redist:windows-nsis:binaries', 'redist:windows-nsis:metadata-only')); - -//---------------------------------------------------------------------------- -gulp.task('redist:windows-nsis', gulp.series('redist:windows-nsis:metadata')); - -//============================================================================ -// REDIST:WINDOWS-SQUIRREL - -//---------------------------------------------------------------------------- -gulp.task('redist:windows-squirrel', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("windows", "squirrel"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.WINDOWS.createTarget(["squirrel"], builder.Arch.x64), - config, - prepackaged: './dist/win-ia32-unpacked' - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - lowerCase: false, - replaceName: true, - replaceWhitespace: true, - srcName: config.productName, - dstName: config.squirrelWindows.name - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//============================================================================ -// REDIST:MAC - -//---------------------------------------------------------------------------- -gulp.task('redist:mac:binaries', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("mac"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.MAC.createTarget(["zip", "dmg"]), - config, - prepackaged: './dist/mac' - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true, - fixMacBinaryNames: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('redist:mac:metadata-only', function () { - const releaseFilename = `${pjson.packagename}-${pjson.version}-mac.zip`; - const releaseHash = hashFileAsync(`./dist/${releaseFilename}`); - const releaseDate = new Date().toISOString(); - - writeJsonMetadataFile(releaseFilename, 'latest-mac.json', './dist', releaseDate); - return releaseHash.then((hashValue) => { - writeYamlMetadataFile(releaseFilename, 'latest-mac.yml', './dist', hashValue, releaseDate); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('redist:mac:metadata', - gulp.series('redist:mac:binaries', 'redist:mac:metadata-only')); - -//---------------------------------------------------------------------------- -gulp.task('redist:mac', gulp.series('redist:mac:metadata')); - -//============================================================================ -// REDIST:LINUX - -//---------------------------------------------------------------------------- -gulp.task('redist:linux', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("linux"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.LINUX.createTarget(["deb", "AppImage"], builder.Arch.ia32, builder.Arch.x64), - config, - prepackaged: './dist/linux-unpacked' - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//============================================================================ -// PACKAGE -// Stages and builds redist in a single step. -//============================================================================ - -//============================================================================ -// PACKAGE:WINDOWS-NSIS - -//---------------------------------------------------------------------------- -gulp.task('package:windows-nsis:binaries', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("windows", "nsis"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.WINDOWS.createTarget(["nsis"], builder.Arch.ia32, builder.Arch.x64), - config - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('package:windows-nsis:metadata', gulp.series('package:windows-nsis:binaries', function () { - const config = getConfig("windows", "nsis"); - const releaseFilename = `${config.productName}-Setup-${pjson.version}.exe`; - const sha512 = hashFileAsync(`./dist/${releaseFilename}`); - const sha2 = hashFileAsync(`./dist/${releaseFilename}`, 'sha256', 'hex'); - const releaseDate = new Date().toISOString(); - - return Promise.all([sha512, sha2]) - .then((values) => { - writeYamlMetadataFile(releaseFilename, 'latest.yml', './dist', values[0], releaseDate, { sha2: values[1] }); - }); -})); - -//---------------------------------------------------------------------------- -gulp.task('package:windows-nsis', gulp.series('package:windows-nsis:metadata')); - -//============================================================================ -// PACKAGE:WINDOWS-SQUIRREL - -//---------------------------------------------------------------------------- -gulp.task('package:windows-squirrel', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("windows", "squirrel"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.WINDOWS.createTarget(["squirrel"], builder.Arch.x64), - config - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - lowerCase: false, - replaceName: true, - replaceWhitespace: true, - srcName: config.productName, - dstName: config.squirrelWindows.name - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//============================================================================ -// PACKAGE:MAC - -//---------------------------------------------------------------------------- -gulp.task('package:mac:binaries', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("mac"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.MAC.createTarget(["zip"]), - config - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - -//---------------------------------------------------------------------------- -gulp.task('package:mac:metadata', gulp.series('package:mac:binaries', function () { - const config = getConfig("mac"); - const releaseFilename = `${config.productName}-${pjson.version}-mac.zip`; - const releaseHash = hashFileAsync(`./dist/${releaseFilename}`); - const releaseDate = new Date().toISOString(); - - writeJsonMetadataFile(releaseFilename, 'latest-mac.json', './dist', releaseDate); - return releaseHash.then((hashValue) => { - writeYamlMetadataFile(releaseFilename, 'latest-mac.yml', './dist', hashValue, releaseDate); - }); -})); - -//---------------------------------------------------------------------------- -gulp.task('package:mac', gulp.series('package:mac:metadata')); - -//============================================================================ -// PACKAGE:LINUX - -//---------------------------------------------------------------------------- -gulp.task('package:linux', function () { - var rename = require('gulp-rename'); - var builder = require('electron-builder'); - const config = getConfig("linux"); - console.log(`Electron mirror: ${getElectronMirrorUrl()}`); - return builder.build({ - targets: builder.Platform.LINUX.createTarget(["deb", "AppImage"], builder.Arch.ia32, builder.Arch.x64), - config - }).then((filenames) => { - return gulp.src(filenames, { allowEmpty: true }) - .pipe(rename(function (path) { - path.basename = setReleaseFilename(path.basename, { - replaceWhitespace: true - }); - })) - .pipe(gulp.dest('./dist')); - }).then(() => { - // Wait for the files to be written to disk and closed. - return delay(10000); - }); -}); - - -//============================================================================ -// PUBLISH -//============================================================================ - -//---------------------------------------------------------------------------- -function publishFiles(filelist) { - var CancellationToken = require('electron-builder-http/out/CancellationToken').CancellationToken; - var GitHubPublisher = require('electron-publish/out/gitHubPublisher').GitHubPublisher; - var publishConfig = replacePublishEnvironmentVars(require('./scripts/config/publish.json')); - - const context = { - cancellationToken: new CancellationToken(), - progress: null - }; - const publisher = new GitHubPublisher( - context, - publishConfig, - pjson.version, { - publish: "always", - draft: true, - prerelease: false - }); - const errorlist = []; - - const uploads = filelist.map(file => { - return publisher.upload({ file }) - .catch((err) => { - errorlist.push(err.response ? `Failed to upload ${file}, http status code ${err.response.statusCode}` : err); - return Promise.resolve(); - }); - }); - - return Promise.all(uploads) - .then(() => errorlist.forEach((err) => console.error(err))); -} - -//---------------------------------------------------------------------------- -gulp.task('publish:windows-nsis', function () { - const filelist = getFileList("windows", "nsis"); - return publishFiles(filelist); -}); - -//---------------------------------------------------------------------------- -gulp.task('publish:windows-squirrel', function () { - const basename = require('./scripts/config/windows-squirrel.json').squirrelWindows.name; - const filelist = getFileList("windows", "squirrel", { - basename, - }); - return publishFiles(filelist); -}); - -//---------------------------------------------------------------------------- -gulp.task('publish:mac', function () { - const filelist = getFileList("mac"); - return publishFiles(filelist); -}); - -//---------------------------------------------------------------------------- -gulp.task('publish:linux', function () { - const filelist = getFileList("linux"); - return publishFiles(filelist); -}); - - -//============================================================================ -// VERIFY:COPYRIGHT - -//---------------------------------------------------------------------------- -gulp.task('verify:copyright', function () { - const lernaRoot = '../../../'; - const lernaJson = require(join(lernaRoot, 'lerna.json')); - const files = lernaJson.packages.filter(p => !/\/custom-/.test(p)).map(dir => join(lernaRoot, dir, '**/*.@(js|jsx|ts|tsx)')); - const filesWithoutCopyright = []; - let count = 0; - let scanned = 0; - - return gulp - .src(files, { buffer: false }) - .pipe(through2( - (file, _, callback) => { - const filename = file.history[0]; - - count++; - - if ( - // TODO: Instead of using pattern, we should use .gitignore - !/[\\\/](build|built|lib|node_modules)[\\\/]/.test(filename) - && !file.isDirectory() - ) { - callback(null, file); - } else { - callback(); - } - } - )) - .pipe(buffer()) - .pipe(through2( - (file, _, callback) => { - const filename = file.history[0]; - const first1000 = file.contents.toString('utf8', 0, 1000); - - if (!~first1000.indexOf('Copyright (c) Microsoft Corporation')) { - filesWithoutCopyright.push(relative(process.cwd(), filename)); - } - - scanned++; - - callback(); - }, - callback => { - log.info(`Verified ${chalk.magenta(scanned)} out of ${chalk.magenta(count)} files with copyright header`); - - if (filesWithoutCopyright.length) { - log.error(chalk.red('Copyright header is missing from the following files:')); - filesWithoutCopyright.forEach(filename => log.error(chalk.magenta(filename))); - callback(new Error('missing copyright header')); - } else { - callback(); - } - } - )); -}); - -//============================================================================ -// UTILS -//============================================================================ - -//---------------------------------------------------------------------------- -function getConfig(platform, target) { - return extend({}, - replacePackageEnvironmentVars(require('./scripts/config/common.json')), - replacePackageEnvironmentVars(require(`./scripts/config/${platform}.json`)), - (target ? replacePackageEnvironmentVars(require(`./scripts/config/${platform}-${target}.json`)) : {}) - ); -} - -//---------------------------------------------------------------------------- -function getFileList(platform, target, options = {}) { - options = extend({}, { - basename: pjson.packagename, - version: pjson.version, - }, options); - const path = './dist'; - const filelist = []; - switch (`${platform}-${target || ''}`) { - case "windows-nsis": - filelist.push(`${path}/latest.yml`); - filelist.push(`${path}/${options.basename}-setup-${options.version}.exe`); - //filelist.push(`${path}/${options.basename}-${options.version}-win.zip`); - //filelist.push(`${path}/${options.basename}-${options.version}-ia32-win.zip`); - break; - - case "windows-squirrel": - filelist.push(`${path}/RELEASES`); - //filelist.push(`${options.path}${options.basename}-Setup-${options.version}.exe`); - filelist.push(`${path}/${options.basename}-${options.version}-full.nupkg`); - break; - - case "mac-": - filelist.push(`${path}/latest-mac.yml`); - filelist.push(`${path}/latest-mac.json`); - filelist.push(`${path}/${options.basename}-${options.version}-mac.zip`); - filelist.push(`${path}/${options.basename}-${options.version}.dmg`); - break; - - case "linux-": - filelist.push(`${path}/${options.basename}-${options.version}-i386.AppImage`); - filelist.push(`${path}/${options.basename}-${options.version}-x86_64.AppImage`); - filelist.push(`${path}/${options.basename}_${options.version}_i386.deb`); - filelist.push(`${path}/${options.basename}_${options.version}_amd64.deb`); - break; - } - return filelist; -} - -//---------------------------------------------------------------------------- -function setReleaseFilename(filename, options = {}) { - options = extend({}, { - lowerCase: true, - replaceWhitespace: true, - fixBasename: true, - replaceName: false, - srcName: null, - dstName: null, - fixMacBinaryNames: false - }, - options); - if (options.replaceName && options.srcName && options.dstName) { - filename = filename.replace(options.srcName, options.dstName); - } - if (options.lowerCase) { - filename = filename.toLowerCase(); - } - if (options.replaceWhitespace) { - filename = filename.replace(/\s/g, '-'); - } - if (options.fixBasename) { - // renames build artifacts like 'bot-framework_{version}.*' or 'main_{version}.*' - // to '{package name in package.json}_{version}.*' - filename = filename.replace(/(bot[-|\s]framework)?(main)?/, pjson.packagename); - } - if (options.fixMacBinaryNames) { - // "Bot Framework Emulator-{version}.*" is being renamed to "Botframework-Emulator-emulator-{version}.*" - // This resolves that issue - filename = filename.replace(/Botframework-Emulator-emulator/, pjson.packagename); - } - return filename; -} - -//---------------------------------------------------------------------------- -function getEnvironmentVar(name, defaultValue = undefined) { - return (process.env[name] === undefined) ? defaultValue : process.env[name] -} - -//---------------------------------------------------------------------------- -function replaceEnvironmentVar(str, name, defaultValue = undefined) { - let value = getEnvironmentVar(name, defaultValue); - if (value === undefined) - throw new Error(`Required environment variable missing: ${name}`); - return str.replace(new RegExp('\\${' + name + '}', 'g'), value); -} - -//---------------------------------------------------------------------------- -function replacePackageEnvironmentVars(obj) { - let str = JSON.stringify(obj); - str = replaceEnvironmentVar(str, "ELECTRON_MIRROR", defaultElectronMirror); - str = replaceEnvironmentVar(str, "ELECTRON_VERSION", defaultElectronVersion); - str = replaceEnvironmentVar(str, "appId", appId); - return JSON.parse(str); -} - -//---------------------------------------------------------------------------- -function replacePublishEnvironmentVars(obj) { - let str = JSON.stringify(obj); - str = replaceEnvironmentVar(str, "GH_TOKEN"); - str = replaceEnvironmentVar(str, "githubAccountName", githubAccountName); - str = replaceEnvironmentVar(str, "githubRepoName", githubRepoName); - return JSON.parse(str); -} - -//---------------------------------------------------------------------------- -function getElectronMirrorUrl() { - return `${getEnvironmentVar("ELECTRON_MIRROR", defaultElectronMirror)}${getEnvironmentVar("ELECTRON_VERSION", defaultElectronVersion)}`; -} - -//---------------------------------------------------------------------------- -function delay(ms, result) { - return new Promise((resolve, reject) => setTimeout(resolve, ms, result)); -} - -//---------------------------------------------------------------------------- -function extend1(destination, source) { - for (var property in source) { - if (source[property] && source[property].constructor && - source[property].constructor === Object) { - destination[property] = destination[property] || {}; - arguments.callee(destination[property], source[property]); - } else { - destination[property] = source[property]; - } - } - return destination; -}; - -//---------------------------------------------------------------------------- -function extend(...sources) { - let output = {}; - sources.forEach(source => { - extend1(output, source); - }); - return output; -} diff --git a/packages/app/main/gulpfile.linux.js b/packages/app/main/gulpfile.linux.js new file mode 100644 index 000000000..f1e4af8b4 --- /dev/null +++ b/packages/app/main/gulpfile.linux.js @@ -0,0 +1,31 @@ +const gulp = require('gulp'); +const common = require('./gulpfile.common.js'); + +/** Package the emulator using electron-builder + * and output artifacts to /dist/ + */ +gulp.task('package', async () => { + const { getElectronMirrorUrl, getConfig, getReleaseFilename } = common; + const rename = require('gulp-rename'); + const builder = require('electron-builder'); + const config = getConfig('linux'); + + console.log(`Electron mirror: ${getElectronMirrorUrl(getReleaseFilename)}`); + + // create build artifacts + const filenames = await builder.build({ + targets: builder.Platform.LINUX.createTarget(['deb', 'AppImage'], builder.Arch.ia32, builder.Arch.x64), + config + }); + + // rename and move the files to the /dist/ directory + await new Promise(resolve => { + gulp + .src(filenames, { allowEmpty: true }) + .pipe(rename(path => { + path.basename = getReleaseFilename(); + })) + .pipe(gulp.dest('./dist')) + .on('end', resolve); + }); +}); diff --git a/packages/app/main/gulpfile.mac.js b/packages/app/main/gulpfile.mac.js new file mode 100644 index 000000000..fbc5fd21f --- /dev/null +++ b/packages/app/main/gulpfile.mac.js @@ -0,0 +1,88 @@ +const gulp = require('gulp'); +const common = require('./gulpfile.common.js'); +const packageJson = require('./package.json'); + +/** Package the emulator using electron-builder */ +gulp.task('stage', async () => { + const { getElectronMirrorUrl, getConfig } = common; + const builder = require('electron-builder'); + const config = getConfig('mac', 'dir'); + + console.log(`Electron mirror: ${getElectronMirrorUrl()}`); + + // create build artifacts + await builder.build({ + targets: builder.Platform.MAC.createTarget(['dir']), + config + }); +}); + +/** Creates the emulator installers */ +gulp.task('redist:binaries', async () => { + const { getElectronMirrorUrl, getConfig, getReleaseFilename } = common; + const rename = require('gulp-rename'); + const builder = require('electron-builder'); + const config = getConfig('mac'); + + console.log(`Electron mirror: ${getElectronMirrorUrl()}`); + + // create installers + const filenames = await builder.build({ + targets: builder.Platform.MAC.createTarget(['zip', 'dmg']), + config, + prepackaged: './dist/mac' + }); + + // rename and move the files to the /dist/ directory + await new Promise(resolve => { + gulp + .src(filenames, { allowEmpty: true }) + .pipe(rename(path => { + path.basename = getReleaseFilename(); + })) + .pipe(gulp.dest('./dist')) + .on('end', resolve); + }); +}); + +/** Creates the .yml and .json metadata files */ +gulp.task('redist:metadata-only', async () => { + const { hashFileAsync, getReleaseFilename } = common; + const releaseFilename = `${getReleaseFilename()}.zip`; + const releaseHash = await hashFileAsync(`./dist/${releaseFilename}`); + const releaseDate = new Date().toISOString(); + + writeJsonMetadataFile(releaseFilename, 'latest-mac.json', './dist', releaseDate); + writeYamlMetadataFile(releaseFilename, 'latest-mac.yml', './dist', releaseHash, releaseDate); +}); + +/** Writes the .yml metadata file */ +function writeYamlMetadataFile(releaseFilename, yamlFilename, path, fileHash, releaseDate, extra = {}) { + const { extend } = common; + const fsp = require('fs-extra'); + const yaml = require('js-yaml'); + + const ymlInfo = { + version: packageJson.version, + releaseDate, + githubArtifactName: releaseFilename, + path: releaseFilename, + sha512: fileHash + }; + const obj = extend({}, ymlInfo, extra); + const ymlStr = yaml.safeDump(obj); + fsp.writeFileSync(`./${path}/${yamlFilename}`, ymlStr); +} + +/** Writes the .json metadata file */ +function writeJsonMetadataFile(releaseFilename, jsonFilename, path, releaseDate) { + const fsp = require('fs-extra'); + const { githubAccountName, githubRepoName } = common; + + const jsonInfo = { + version: packageJson.version, + releaseDate, + url: `https://github.com/${githubAccountName}/${githubRepoName}/releases/v${packageJson.version}/${releaseFilename}` + }; + fsp.outputJsonSync(`./${path}/${jsonFilename}`, jsonInfo, { spaces: 2 }); +} diff --git a/packages/app/main/gulpfile.windows.js b/packages/app/main/gulpfile.windows.js new file mode 100644 index 000000000..e52a8f63d --- /dev/null +++ b/packages/app/main/gulpfile.windows.js @@ -0,0 +1,88 @@ +const common = require('./gulpfile.common.js'); +const gulp = require('gulp'); +const packageJson = require('./package.json'); + +/** Package the emulator using electron-builder */ +gulp.task('stage', async () => { + const { getConfig, getElectronMirrorUrl } = common; + const builder = require('electron-builder'); + const config = getConfig('windows', 'dir'); + + console.log(`Electron mirror: ${getElectronMirrorUrl()}`); + + // create build artifacts + await builder.build({ + targets: builder.Platform.WINDOWS.createTarget(['dir'], builder.Arch.ia32, builder.Arch.x64), + config + }); +}); + +/** Creates the emulator installers */ +gulp.task('redist:binaries', async () => { + const { getConfig, getElectronMirrorUrl, getReleaseFilename } = common; + var rename = require('gulp-rename'); + var builder = require('electron-builder'); + const config = getConfig("windows", "nsis"); + + console.log(`Electron mirror: ${getElectronMirrorUrl()}`); + + // create installers + const filenames = await builder.build({ + targets: builder.Platform.WINDOWS.createTarget(["nsis"], builder.Arch.ia32), + config, + prepackaged: './dist/win-ia32-unpacked' + }); + + // rename and move the files to the /dist/ directory + await new Promise(resolve => { + gulp + .src(filenames, { allowEmpty: true }) + .pipe(rename(path => { + path.basename = getReleaseFilename(); + })) + .pipe(gulp.dest('./dist')) + .on('end', resolve); + }); +}); + +/** Writes the build metadata to latest.yml */ +gulp.task('redist:metadata-only', async () => { + const { getConfig, hashFileAsync } = common; + const config = getConfig('windows', 'nsis'); + const releaseFilename = config.nsis.artifactName.replace('${version}', packageJson.version); + const sha512 = await hashFileAsync(`./dist/${releaseFilename}`); + const sha2 = await hashFileAsync(`./dist/${releaseFilename}`, 'sha256', 'hex'); + const releaseDate = new Date().toISOString(); + + writeYamlMetadataFile( + releaseFilename, + 'latest.yml', + './dist', + sha512, + releaseDate, + { sha2 } + ); +}); + +/** Creates the emulator installers and the metadata .yml file */ +gulp.task('redist', + gulp.series('redist:binaries', 'redist:metadata-only') +); + +/** Writes the .yml metadata file */ +function writeYamlMetadataFile(releaseFilename, yamlFilename, path, fileHash, releaseDate, extra = {}) { + const { extend } = common; + var fsp = require('fs-extra'); + var yaml = require('js-yaml'); + + const ymlInfo = { + version: packageJson.version, + releaseDate, + githubArtifactName: releaseFilename, + path: releaseFilename, + sha512: fileHash + }; + const obj = extend({}, ymlInfo, extra); + const ymlStr = yaml.safeDump(obj); + fsp.writeFileSync(`./${path}/${yamlFilename}`, ymlStr); +} diff --git a/packages/app/main/package.json b/packages/app/main/package.json index 4aef76ed5..572eecb62 100644 --- a/packages/app/main/package.json +++ b/packages/app/main/package.json @@ -1,6 +1,6 @@ { "name": "@bfemulator/main", - "packagename": "Botframework-Emulator", + "packagename": "BotFramework-Emulator", "version": "4.0.0-preview", "private": true, "description": "Development tool for the Microsoft Bot Framework. Allows developers to test and debug bots on localhost.", @@ -8,14 +8,14 @@ "homepage": "https://github.com/Microsoft/BotFramework-Emulator", "scripts": { "build": "run-s lint build:electron", - "build:electron": "babel ./src --out-dir app/server --extensions \".ts,.tsx\" --ignore \"**/*.spec.ts\" && gulp copy-extension-stubs", + "build:electron": "babel ./src --out-dir app/server --extensions \".ts,.tsx\" --ignore \"**/*.spec.ts\" && gulp --gulpfile gulpfile.common.js copy-extension-stubs", "lint": "tslint --project tsconfig.json", "start": "concurrently --kill-others --names \"electron,react-app\" --success first \"npm run start:electron:dev\" \"npm run start:react-app\"", "start:electron": "./node_modules/.bin/electron --inspect=7777 .", "start:electron:dev": "cross-env ELECTRON_TARGET_URL=http://localhost:3000/ npm run start:electron", "start:react-app": "cd ../client && npm start", "test": "jest", - "verify": "gulp verify:copyright" + "verify": "gulp --gulpfile gulpfile.common.js verify:copyright" }, "keywords": [ "microsoft", diff --git a/packages/app/main/scripts/config/publish.json b/packages/app/main/scripts/config/publish.json deleted file mode 100644 index 614028901..000000000 --- a/packages/app/main/scripts/config/publish.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "provider": "github", - "owner": "${githubAccountName}", - "repo": "${githubRepoName}", - "token": "${GH_TOKEN}" -} \ No newline at end of file diff --git a/packages/app/main/scripts/config/windows-nsis.json b/packages/app/main/scripts/config/windows-nsis.json index f7d195c87..2a4525046 100644 --- a/packages/app/main/scripts/config/windows-nsis.json +++ b/packages/app/main/scripts/config/windows-nsis.json @@ -5,7 +5,7 @@ "perMachine": true, "allowElevation": true, "packElevateHelper": true, - "artifactName": "botframework-emulator-setup-${version}.exe", + "artifactName": "BotFramework-Emulator-${version}-windows-setup.exe", "unicode": true, "runAfterFinish": true, "installerIcon": "./scripts/config/resources/icon.ico",