blob: 63a2eaab716fc8b1da9c279528551c475514b47a [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
const glob = require('glob');
const optimist = require('optimist');
const shelljs = require('shelljs');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
const apputil = require('./apputil');
const executil = require('./executil');
const flagutil = require('./flagutil');
const gitutil = require('./gitutil');
const repoutil = require('./repoutil');
const print = apputil.print;
const settingUpGpg = path.resolve(path.dirname(__dirname), 'docs', 'setting-up-gpg.md');
const isWin = process.platform === 'win32';
exports.GPG_DOCS = settingUpGpg;
exports.createCommand = function * (argv) {
let opt = flagutil.registerRepoFlag(optimist);
opt = opt
.options('tag', {
desc: 'The pre-existing tag or hash to archive (defaults to newest tag on branch)'
})
.options('allow-pending', {
desc: 'Whether to allow uncommitted changes to exist when packing (use only for testing)',
type: 'boolean'
})
.options('sign', {
desc: 'Whether to create .asc, .sha512 files (defaults to true)',
type: 'boolean',
default: 'true'
})
.options('dest', {
desc: 'The directory to hold the resulting files.',
default: '.'
});
opt = flagutil.registerHelpFlag(opt);
var argv = opt // eslint-disable-line
.usage('Creates a .zip, .asc, .sha for a repo at a tag or hash.\n' +
'Refer to ' + settingUpGpg + ' for how to set up gpg\n' +
'\n' +
'Usage: $0 create-archive [--tag tagname] [--sign] --repo=name [-r repos] --dest cordova-dist-dev/CB-1111')
.argv;
// Optimist doesn't cast from string :(
argv.sign = argv.sign === true || argv.sign === 'true';
argv['allow-pending'] = argv['allow-pending'] === true || argv['allow-pending'] === 'true';
if (argv.h) {
optimist.showHelp();
process.exit(1);
}
const repos = flagutil.computeReposFromFlag(argv.r, { includeModules: true });
if (argv.sign && !shelljs.which('gpg')) {
apputil.fatal('gpg command not found on your PATH. Refer to ' + settingUpGpg);
}
const outDir = apputil.resolveUserSpecifiedPath(argv.dest);
shelljs.mkdir('-p', outDir);
const absOutDir = path.resolve(outDir);
yield repoutil.forEachRepo(repos, function * (repo) {
if (isWin) {
yield checkLineEndings(repo);
}
const tag = argv.tag || (yield gitutil.findMostRecentTag(repo.versionPrefix))[0];
if (!tag) {
apputil.fatal('Could not find most recent tag. Try running with --tag');
}
if (!argv['allow-pending'] && (yield gitutil.pendingChangesExist())) {
apputil.fatal('Aborting because pending changes exist in ' + repo.repoName + ' (run "git status")');
}
const origBranch = yield gitutil.retrieveCurrentBranchName(true);
yield gitutil.gitCheckout(tag);
yield createArchive(repo, tag, absOutDir, argv.sign);
if (origBranch) {
yield gitutil.gitCheckout(origBranch);
}
});
print();
print('Archives created.');
print('Verify them using: coho verify-archive ' + path.join(outDir, '*.tgz'));
};
// WARNING: NEEDS to be executed in the current working directory of a cordova repo!!!
function * createArchive (repo, tag, outDir, sign) {
print('Creating archive of ' + repo.repoName + '@' + tag);
let outPath;
if (repo.id !== 'mobile-spec') {
const pkgInfo = require(path.resolve('package'));
const tgzname = pkgInfo.name + '-' + tag + '.tgz';
yield executil.execHelper(executil.ARGS('npm pack'), 1, false);
outPath = path.join(outDir, tgzname);
if (path.resolve(tgzname) !== outPath) {
shelljs.rm('-f', outPath + '*');
shelljs.mv(tgzname, outPath);
}
} else {
outPath = path.join(outDir, repo.repoName + '-' + tag + '.zip');
yield executil.execHelper(executil.ARGS('git archive --format zip --prefix ' + repo.repoName + '/ -o ', outPath, tag));
}
print('Created archive: ' + outPath);
if (sign) {
yield executil.execHelper(executil.ARGS('gpg --armor --detach-sig --output', outPath + '.asc', outPath), false, false);
fs.writeFileSync(outPath + '.sha512', (yield computeHash(outPath, 'SHA512')) + '\n');
}
return outPath;
}
exports.createArchive = createArchive;
exports.verifyCommand = function * () {
const opt = flagutil.registerHelpFlag(optimist);
const argv = opt
.usage('Ensures the given .zip files match their neighbouring .asc, .sha512 files.\n' +
'Refer to ' + settingUpGpg + ' for how to set up gpg\n' +
'\n' +
'Usage: $0 verify-archive a.zip b.zip c.zip')
.argv;
const zipPaths = argv._.slice(1);
if (argv.h || !zipPaths.length) {
optimist.showHelp();
process.exit(1);
}
if (!shelljs.which('gpg')) {
apputil.fatal('gpg command not found on your PATH. Refer to ' + settingUpGpg);
}
const resolvedZipPaths = zipPaths.reduce(function (current, zipPath) {
const matchingPaths = glob.sync(apputil.resolveUserSpecifiedPath(zipPath));
if (!matchingPaths || !matchingPaths.length) {
apputil.fatal(chalk.red('No files found that match \'' + zipPath + '\''));
}
return current.concat(matchingPaths);
}, []);
for (let i = 0; i < resolvedZipPaths.length; ++i) {
const zipPath = resolvedZipPaths[i];
yield verifyArchive(zipPath);
}
print(chalk.green('Verified ' + resolvedZipPaths.length + ' signatures and hashes.'));
};
function * verifyArchive (archive) {
const result = yield executil.execHelper(executil.ARGS('gpg --verify', archive + '.asc', archive), false, true);
if (result === null) {
apputil.fatal('Verification failed. You may need to update your keys. Run: curl "https://dist.apache.org/repos/dist/release/cordova/KEYS" | gpg --import');
}
const sha = yield computeHash(archive, 'SHA512');
let archiveFileName = archive + '.sha512';
const oldArchiveFileName = archive + '.sha';
if (fs.existsSync(oldArchiveFileName) && !fs.existsSync(archiveFileName)) {
print('Old .sha extension found, this might have been generated by an old cordova-coho version. Using .sha extension for the check.');
archiveFileName = oldArchiveFileName;
}
if (extractHashFromOutput(fs.readFileSync(archiveFileName, 'utf8')) !== sha) {
apputil.fatal('SHA512 does not match.');
}
print(archive + chalk.green(' signature and hashes verified.'));
}
exports.verifyArchive = verifyArchive;
function * computeHash (path, algo) {
print('Computing ' + algo + ' for: ' + path);
const result = yield executil.execHelper(executil.ARGS('gpg --print-md', algo, path), true);
return extractHashFromOutput(result);
}
function extractHashFromOutput (output) {
return output.slice(output.lastIndexOf(':') + 1).replace(/\s*/g, '').toLowerCase();
}
function * checkLineEndings (repo) {
let autoCRLF;
let eol;
let msg = '';
try {
autoCRLF = yield executil.execHelper(executil.ARGS('git config --get core.autocrlf'), true);
} catch (e) {
autoCRLF = '';
}
try {
eol = yield executil.execHelper(executil.ARGS('git config --get core.eol'), true);
} catch (e) {
eol = '';
}
if (autoCRLF !== 'false') {
msg = 'Warning: core.autocrlf is set to "' + autoCRLF + '".\n' +
'Set either "' + repo.repoName + '" or global core.autocrlf setting to "false" to avoid issues with executables on non-Windows OS.\n';
}
if (eol !== 'lf') {
msg += 'Warning: core.eol is set to "' + eol + '". Set it to "lf" to normalize line endings.\n';
}
if (msg) {
console.error(msg +
'Run these commands in the repos:\n' +
' git config core.eol lf\n' +
' git config core.autocrlf false\n' +
' git rm --cached -r .\n' +
' git reset --hard\n' +
'Alternatively you can setup it globally for your user:\n' +
' git config --global core.eol lf\n' +
' git config --global core.autocrlf false\n' +
'Or update the repos automatically using coho (change the repo groups to your ones):\n' +
' coho for-each -r tools -r android -r ios "git config core.eol lf && git config core.autocrlf false && git rm --cached -r . && git reset --hard"');
process.exit(1);
}
}