Merge branch '4.x'

# Conflicts:
#	Gruntfile.js
#	package-lock.json
#	package.json
This commit is contained in:
Nils Knappmeier
2019-12-14 18:46:25 +01:00
27 changed files with 1497 additions and 727 deletions
+1
View File
@@ -9,6 +9,7 @@ sauce_connect.log*
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
-1
View File
@@ -17,7 +17,6 @@ module.exports = {
// Best Practices //
//----------------//
'default-case': 'warn',
'dot-notation': ['error', { allowKeywords: false }],
'guard-for-in': 'warn',
'no-alert': 'error',
'no-caller': 'error',
+1
View File
@@ -10,6 +10,7 @@ sauce_connect.log*
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
-2
View File
@@ -1,2 +0,0 @@
instrumentation:
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
+1
View File
@@ -9,6 +9,7 @@ sauce_connect.log*
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
+3 -1
View File
@@ -211,6 +211,8 @@ module.exports = function(grunt) {
'copy:components'
]);
this.registerTask('test', ['test:bin', 'test:cov']);
grunt.registerTask('bench', ['metrics']);
if (process.env.SAUCE_USERNAME) {
@@ -225,7 +227,7 @@ module.exports = function(grunt) {
'bgShell:integrationTests',
'sauce',
'metrics',
'publish:latest'
'publish-to-aws'
]);
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
'check-coverage': true,
branches: 100,
lines: 100,
functions: 100,
statements: 100,
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
reporter: 'html'
};
+918 -275
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -44,7 +44,8 @@
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-compat": "^3.3.0",
"eslint-plugin-es5": "^1.4.1",
"grunt": "^1.0.3",
"fs-extra": "^8.1.0",
"grunt": "^1.0.4",
"grunt-babel": "^5.0.0",
"grunt-bg-shell": "^2.3.3",
"grunt-cli": "^1",
@@ -57,12 +58,12 @@
"grunt-contrib-watch": "^1.1.0",
"grunt-webpack": "^1.0.8",
"husky": "^3.1.0",
"istanbul": "kpdecker/istanbul",
"jison": "0.4.16",
"lint-staged": "^9.5.0",
"mocha": "^5",
"mock-stdin": "^0.3.0",
"mustache": "^2.1.3",
"nyc": "^14.1.1",
"prettier": "^1.19.1",
"semver": "^5.0.1",
"sinon": "^7.5.0",
@@ -86,7 +87,7 @@
"lint": "eslint --max-warnings 0 . ",
"dtslint": "dtslint types",
"test": "grunt",
"extensive-tests-and-publish-to-aws": "grunt --stack extensive-tests-and-publish-to-aws",
"extensive-tests-and-publish-to-aws": "npx mocha tasks/task-tests/ && grunt --stack extensive-tests-and-publish-to-aws",
"integration-test": "grunt integration-tests",
"--- combined tasks ---": "",
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:dtslint npm:check-format npm:test"
+17
View File
@@ -710,6 +710,23 @@ describe('builtin helpers', function() {
shouldCompileTo(string, hash, '');
equals(true, called);
});
it('should pass zero log arguments', function() {
var string = '{{log}}';
var hash = { blah: 'whee' };
var called;
console.info = console.log = function() {
expect(arguments.length).to.equal(0);
called = true;
console.log = $log;
};
expectTemplate(string)
.withInput(hash)
.toCompileTo('');
expect(called).to.be.true();
});
/* eslint-enable no-console */
});
-16
View File
@@ -1,16 +0,0 @@
{
"globals": {
"require": true
},
"rules": {
// Disabling for tests, for now.
"no-path-concat": 0,
"no-var": 0,
"no-shadow": 0,
"handle-callback-err": 0,
"no-console": 0,
"no-process-env": 0,
"dot-notation": [2, {"allowKeywords": true}]
}
}
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
extends: ['../.eslintrc.js'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2017,
ecmaFeatures: {}
},
rules: {
'no-process-env': 'off',
'prefer-const': 'warn',
'compat/compat': 'off',
'dot-notation': ['error', { allowKeywords: true }]
}
};
+20 -17
View File
@@ -1,26 +1,29 @@
var _ = require('underscore'),
async = require('neo-async'),
metrics = require('../bench');
const metrics = require('../bench');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
module.exports = function(grunt) {
grunt.registerTask('metrics', function() {
var done = this.async(),
execName = grunt.option('name'),
events = {};
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
async.each(
_.keys(metrics),
function(name, complete) {
if (/^_/.test(name) || (execName && name !== execName)) {
return complete();
}
registerAsyncTask('metrics', function() {
const onlyExecuteName = grunt.option('name');
const events = {};
const promises = Object.keys(metrics).map(async name => {
if (/^_/.test(name)) {
return;
}
if (onlyExecuteName != null && name !== onlyExecuteName) {
return;
}
return new Promise(resolve => {
metrics[name](grunt, function(data) {
events[name] = data;
complete();
resolve();
});
},
done
);
});
});
return Promise.all(promises);
});
};
+28 -34
View File
@@ -1,39 +1,33 @@
var childProcess = require('child_process');
const { execFileWithInheritedOutput } = require('./util/exec-file');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const OUTPUT_FILE = 'lib/handlebars/compiler/parser.js';
module.exports = function(grunt) {
grunt.registerTask('parser', 'Generate jison parser.', function() {
var done = this.async();
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
var cmd = './node_modules/.bin/jison';
if (process.platform === 'win32') {
cmd = 'node_modules\\.bin\\jison.cmd';
}
var child = childProcess.spawn(
cmd,
['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'],
{ stdio: 'inherit' }
);
child.on('exit', function(code) {
if (code != 0) {
grunt.fatal('Jison failure: ' + code);
done();
return;
}
var src = [
'src/parser-prefix.js',
'handlebars.js',
'src/parser-suffix.js'
]
.map(grunt.file.read)
.join('');
grunt.file.delete('handlebars.js');
grunt.file.write('lib/handlebars/compiler/parser.js', src);
grunt.log.writeln('Parser "lib/handlebars/compiler/parser.js" created.');
done();
});
registerAsyncTask('parser', async () => {
await runJison();
combineWithPrefixAndSuffix();
grunt.log.writeln(`Parser "${OUTPUT_FILE}" created.`);
});
async function runJison() {
await execFileWithInheritedOutput('jison', [
'-m',
'js',
'src/handlebars.yy',
'src/handlebars.l'
]);
}
function combineWithPrefixAndSuffix() {
const combinedParserSourceCode =
grunt.file.read('src/parser-prefix.js') +
grunt.file.read('handlebars.js') +
grunt.file.read('src/parser-suffix.js');
grunt.file.write(OUTPUT_FILE, combinedParserSourceCode);
grunt.file.delete('handlebars.js');
}
};
+102
View File
@@ -0,0 +1,102 @@
const AWS = require('aws-sdk');
const git = require('./util/git');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const semver = require('semver');
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('publish-to-aws', async () => {
grunt.log.writeln('remotes: ' + (await git.remotes()));
grunt.log.writeln('branches: ' + (await git.branches()));
const commitInfo = await git.commitInfo();
grunt.log.writeln('tag: ', commitInfo.tagName);
const suffixes = [];
// Publish the master as "latest" and with the commit-id
if (commitInfo.isMaster) {
suffixes.push('-latest');
suffixes.push('-' + commitInfo.headSha);
}
// Publish tags by their tag-name
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
suffixes.push('-' + commitInfo.tagName);
}
if (suffixes.length > 0) {
initSDK();
grunt.log.writeln(
'publishing file-suffixes: ' + JSON.stringify(suffixes)
);
await publish(suffixes);
}
});
function initSDK() {
const bucket = process.env.S3_BUCKET_NAME,
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
if (!bucket || !key || !secret) {
throw new Error('Missing S3 config values');
}
AWS.config.update({ accessKeyId: key, secretAccessKey: secret });
}
async function publish(suffixes) {
const publishPromises = suffixes.map(suffix => publishSuffix(suffix));
return Promise.all(publishPromises);
}
async function publishSuffix(suffix) {
const filenames = [
'handlebars.js',
'handlebars.min.js',
'handlebars.runtime.js',
'handlebars.runtime.min.js'
];
const publishPromises = filenames.map(filename => {
const nameInBucket = getNameInBucket(filename, suffix);
const localFile = getLocalFile(filename);
uploadToBucket(localFile, nameInBucket);
grunt.log.writeln(
`Published ${localFile} to build server (${nameInBucket})`
);
});
return Promise.all(publishPromises);
}
async function uploadToBucket(localFile, nameInBucket) {
const bucket = process.env.S3_BUCKET_NAME;
const uploadParams = {
Bucket: bucket,
Key: nameInBucket,
Body: grunt.file.read(localFile)
};
return s3PutObject(uploadParams);
}
};
function s3PutObject(uploadParams) {
const s3 = new AWS.S3();
return new Promise((resolve, reject) => {
s3.putObject(uploadParams, err => {
if (err != null) {
return reject(err);
}
resolve();
});
});
}
function getNameInBucket(filename, suffix) {
return filename.replace(/\.js$/, suffix + '.js');
}
function getLocalFile(filename) {
return 'dist/' + filename;
}
-106
View File
@@ -1,106 +0,0 @@
var _ = require('underscore'),
async = require('neo-async'),
AWS = require('aws-sdk'),
git = require('./util/git'),
semver = require('semver');
module.exports = function(grunt) {
grunt.registerTask('publish:latest', function() {
var done = this.async();
git.debug(function(remotes, branches) {
grunt.log.writeln('remotes: ' + remotes);
grunt.log.writeln('branches: ' + branches);
git.commitInfo(function(err, info) {
grunt.log.writeln('tag: ' + info.tagName);
var files = [];
// Publish the master as "latest" and with the commit-id
if (info.isMaster) {
files.push('-latest');
files.push('-' + info.head);
}
// Publish tags by their tag-name
if (info.tagName && semver.valid(info.tagName)) {
files.push('-' + info.tagName);
}
if (files.length > 0) {
initSDK();
grunt.log.writeln('publishing files: ' + JSON.stringify(files));
publish(fileMap(files), done);
} else {
// Silently ignore for branches
done();
}
});
});
});
grunt.registerTask('publish:version', function() {
var done = this.async();
initSDK();
git.commitInfo(function(err, info) {
if (!info.tagName) {
throw new Error('The current commit must be tagged');
}
publish(fileMap(['-' + info.tagName]), done);
});
});
function initSDK() {
var bucket = process.env.S3_BUCKET_NAME,
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
if (!bucket || !key || !secret) {
throw new Error('Missing S3 config values');
}
AWS.config.update({ accessKeyId: key, secretAccessKey: secret });
}
function publish(files, callback) {
var s3 = new AWS.S3(),
bucket = process.env.S3_BUCKET_NAME;
async.each(
_.keys(files),
function(file, callback) {
var params = {
Bucket: bucket,
Key: file,
Body: grunt.file.read(files[file])
};
s3.putObject(params, function(err) {
if (err) {
throw err;
} else {
grunt.log.writeln('Published ' + file + ' to build server.');
callback();
}
});
},
callback
);
}
function fileMap(suffixes) {
var map = {};
_.each(
[
'handlebars.js',
'handlebars.min.js',
'handlebars.runtime.js',
'handlebars.runtime.min.js'
],
function(file) {
_.each(suffixes, function(suffix) {
map[file.replace(/\.js$/, suffix + '.js')] = 'dist/' + file;
});
}
);
return map;
}
};
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
extends: '../../.eslintrc.js',
env: {
mocha: true
},
parserOptions: {
ecmaVersion: 2018
}
};
+1
View File
@@ -0,0 +1 @@
Use `mocha tasks/task-tests` to run these tests
+121
View File
@@ -0,0 +1,121 @@
const os = require('os');
const path = require('path');
const fs = require('fs-extra');
const chai = require('chai');
chai.use(require('dirty-chai'));
const git = require('../util/git');
const expect = chai.expect;
const tmpBaseDir = path.join(os.tmpdir(), 'handlebars-task-tests');
const tmpDir = path.join(tmpBaseDir, Date.now().toString(36));
const remoteDir = path.join(tmpDir, 'remote-repo');
const cloneDir = path.join(tmpDir, 'clone-repo');
const oldCwd = process.cwd();
describe('utils/git', function() {
beforeEach(async function() {
await fs.remove(tmpDir);
await createRepositoryThatActsAsRemote();
process.chdir(tmpDir);
await git.git('clone', 'remote-repo', 'clone-repo');
process.chdir(cloneDir);
});
async function createRepositoryThatActsAsRemote() {
await fs.mkdirp(remoteDir);
process.chdir(remoteDir);
await git.git('init');
await fs.writeFile('testfile.txt', 'Testfile');
await git.add('testfile.txt');
await git.commit('commit message');
}
afterEach(function() {
process.chdir(oldCwd);
});
describe('the "remotes"-function', function() {
it('should list all remotes', async function() {
await git.git('remote', 'set-url', 'origin', 'https://test.org/test');
await git.git('remote', 'add', 'second-remote', 'https://test.org/test2');
const result = await git.remotes();
expect(result.trim().split('\n')).to.deep.equal([
'origin\thttps://test.org/test (fetch)',
'origin\thttps://test.org/test (push)',
'second-remote\thttps://test.org/test2 (fetch)',
'second-remote\thttps://test.org/test2 (push)'
]);
});
});
describe('the "branches"-function', function() {
it('should list all branches', async function() {
await git.git('branch', 'test');
await git.git('branch', 'test2');
const result = await git.branches();
expect(result.trim().split('\n')).to.deep.equal([
'* master',
' test',
' test2',
' remotes/origin/HEAD -> origin/master',
' remotes/origin/master'
]);
});
});
describe('the "commitInfo"-function', function() {
it('should list head and master sha', async function() {
const result = await git.commitInfo();
expect(result.masterSha).to.equal(result.headSha);
expect(result.masterSha).to.match(/^[0-9a-f]+$/);
expect(result.headSha).to.match(/^[0-9a-f]+$/);
});
it('should have "isMaster=true" if the master branch is checked out', async function() {
const result = await git.commitInfo();
expect(result.isMaster).to.be.true();
});
it('should have "isMaster=true" if the current commit is the last commit of the master branch', async function() {
await git.git('checkout', '-b', 'new-branch');
const result = await git.commitInfo();
expect(result.isMaster).to.be.true();
});
it('should have "isMaster=false" if the current commit is NOT the last commit of the master branch', async function() {
await git.git('checkout', '-b', 'new-branch');
fs.writeFile('new-file.txt', 'new-file');
await git.add('new-file.txt');
await git.commit('added new file');
const result = await git.commitInfo();
expect(result.isMaster).to.be.false();
});
it('should show the current tag', async function() {
await git.git('tag', 'test-tag');
const result = await git.commitInfo();
expect(result.tagName).to.be.equal('test-tag');
});
it('should show a version tag rather than standard tags', async function() {
await git.git('tag', 'test-tag');
await git.git('tag', 'v1.2');
await git.git('tag', 'test-tag2');
const result = await git.commitInfo();
expect(result.tagName).to.be.equal('v1.2');
});
it('should show no tag if there is no tag', async function() {
const result = await git.commitInfo();
expect(result.tagName).to.be.null();
});
});
});
View File
+45
View File
@@ -0,0 +1,45 @@
const childProcess = require('child_process'),
fs = require('fs'),
os = require('os'),
expect = require('chai').expect;
module.exports = function(grunt) {
grunt.registerTask('test:bin', function() {
const stdout = executeBinHandlebars(
'-a',
'spec/artifacts/empty.handlebars'
);
const expectedOutput = fs.readFileSync(
'./spec/expected/empty.amd.js',
'utf-8'
);
const normalizedOutput = normalizeCrlf(stdout);
const normalizedExpectedOutput = normalizeCrlf(expectedOutput);
expect(normalizedOutput).to.equal(normalizedExpectedOutput);
});
};
// helper functions
function executeBinHandlebars(...args) {
if (os.platform() === 'win32') {
// On Windows, the executable handlebars.js file cannot be run directly
const nodeJs = process.argv[0];
return execFilesSyncUtf8(nodeJs, ['./bin/handlebars'].concat(args));
}
return execFilesSyncUtf8('./bin/handlebars', args);
}
function execFilesSyncUtf8(command, args) {
return childProcess.execFileSync(command, args, { encoding: 'utf-8' });
}
function normalizeCrlf(string) {
if (typeof string === 'string') {
return string.replace(/\r\n/g, '\n');
}
return string;
}
+22
View File
@@ -0,0 +1,22 @@
const { execNodeJsScriptWithInheritedOutput } = require('./util/exec-file');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const nodeJs = process.argv0;
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('test:mocha', async () =>
execNodeJsScriptWithInheritedOutput('./spec/env/runner')
);
registerAsyncTask('test:cov', async () =>
execNodeJsScriptWithInheritedOutput('node_modules/nyc/bin/nyc', [
nodeJs,
'./spec/env/runner.js'
])
);
registerAsyncTask('test:min', async () =>
execNodeJsScriptWithInheritedOutput('./spec/env/runner', ['--min'])
);
};
-107
View File
@@ -1,107 +0,0 @@
var childProcess = require('child_process'),
fs = require('fs'),
os = require('os');
module.exports = function(grunt) {
grunt.registerTask('test:bin', function() {
var done = this.async();
var cmd = './bin/handlebars';
var args = ['-a', 'spec/artifacts/empty.handlebars'];
// On Windows, the executable handlebars.js file cannot be run directly
if (os.platform() === 'win32') {
args.unshift(cmd);
cmd = process.argv[0];
}
childProcess.execFile(cmd, args, function(err, stdout) {
if (err) {
throw err;
}
var expected = fs
.readFileSync('./spec/expected/empty.amd.js')
.toString()
.replace(/\r\n/g, '\n');
if (stdout.toString() !== expected) {
throw new Error(
'Expected binary output differed:\n\n"' +
stdout +
'"\n\n"' +
expected +
'"'
);
}
done();
});
});
grunt.registerTask('test:mocha', function() {
var done = this.async();
var runner = childProcess.fork('./spec/env/runner', [], {
stdio: 'inherit'
});
runner.on('close', function(code) {
if (code !== 0) {
grunt.fatal(code + ' tests failed');
}
done();
});
});
grunt.registerTask('test:cov', function() {
var done = this.async();
var runner = childProcess.fork(
'node_modules/istanbul/lib/cli.js',
['cover', '--source-map', '--', './spec/env/runner.js'],
{ stdio: 'inherit' }
);
runner.on('close', function(code) {
if (code != 0) {
grunt.fatal(code + ' tests failed');
}
done();
});
});
grunt.registerTask('test:min', function() {
var done = this.async();
var runner = childProcess.fork('./spec/env/runner', ['--min'], {
stdio: 'inherit'
});
runner.on('close', function(code) {
if (code !== 0) {
grunt.fatal(code + ' tests failed');
}
done();
});
});
grunt.registerTask('test:check-cov', function() {
var done = this.async();
var runner = childProcess.fork(
'node_modules/istanbul/lib/cli.js',
[
'check-coverage',
'--statements',
'100',
'--functions',
'100',
'--branches',
'100',
'--lines 100'
],
{ stdio: 'inherit' }
);
runner.on('close', function(code) {
if (code != 0) {
grunt.fatal('Coverage check failed: ' + code);
}
done();
});
});
grunt.registerTask('test', ['test:bin', 'test:cov', 'test:check-cov']);
};
+13
View File
@@ -0,0 +1,13 @@
module.exports = { createRegisterAsyncTaskFn };
function createRegisterAsyncTaskFn(grunt) {
return function registerAsyncTask(name, asyncFunction) {
grunt.registerTask(name, function() {
asyncFunction()
.catch(error => {
grunt.fatal(error);
})
.finally(this.async());
});
};
}
+51
View File
@@ -0,0 +1,51 @@
const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
module.exports = {
execNodeJsScriptWithInheritedOutput,
execFileWithInheritedOutput
};
async function execNodeJsScriptWithInheritedOutput(command, args) {
return new Promise((resolve, reject) => {
const child = childProcess.fork(command, args, { stdio: 'inherit' });
child.on('close', code => {
if (code !== 0) {
reject(new Error(`Child process failed with exit-code ${code}`));
}
resolve();
});
});
}
async function execFileWithInheritedOutput(command, args) {
return new Promise((resolve, reject) => {
const resolvedCommand = preferLocalDependencies(command);
const child = childProcess.spawn(resolvedCommand, args, {
stdio: 'inherit'
});
child.on('exit', code => {
if (code !== 0) {
reject(new Error(`Child process failed with exit-code ${code}`));
}
resolve();
});
});
}
function preferLocalDependencies(command) {
const localCandidate = resolveLocalCandidate(command);
if (fs.existsSync(localCandidate)) {
return localCandidate;
}
return command;
}
function resolveLocalCandidate(command) {
if (process.platform === 'win32') {
return path.join('node_modules', '.bin', command + '.cmd');
}
return path.join('node_modules', '.bin', command);
}
+65 -107
View File
@@ -1,115 +1,73 @@
var childProcess = require('child_process');
const childProcess = require('child_process');
module.exports = {
debug: function(callback) {
childProcess.exec('git remote -v', {}, function(err, remotes) {
if (err) {
throw new Error('git.remote: ' + err.message);
}
childProcess.exec('git branch -a', {}, function(err, branches) {
if (err) {
throw new Error('git.branch: ' + err.message);
}
callback(remotes, branches);
});
});
async remotes() {
return git('remote', '-v');
},
clean: function(callback) {
childProcess.exec('git diff-index --name-only HEAD --', {}, function(
err,
stdout
) {
callback(undefined, !err && !stdout);
});
async branches() {
return git('branch', '-a');
},
commitInfo: function(callback) {
module.exports.head(function(err, headSha) {
module.exports.master(function(err, masterSha) {
module.exports.tagName(function(err, tagName) {
callback(undefined, {
head: headSha,
master: masterSha,
tagName: tagName,
isMaster: headSha === masterSha
});
});
});
});
async commitInfo() {
const headSha = await getHeadSha();
const masterSha = await getMasterSha();
return {
headSha,
masterSha,
tagName: await getTagName(),
isMaster: headSha === masterSha
};
},
head: function(callback) {
childProcess.exec('git rev-parse --short HEAD', {}, function(err, stdout) {
if (err) {
throw new Error('git.head: ' + err.message);
}
callback(undefined, stdout.trim());
});
async add(path) {
return git('add', '-f', path);
},
master: function(callback) {
childProcess.exec('git rev-parse --short origin/master', {}, function(
err,
stdout
) {
// This will error if master was not checked out but in this case we know we are not master
// so we can ignore.
if (err && !/Needed a single revision/.test(err.message)) {
throw new Error('git.master: ' + err.message);
}
callback(undefined, stdout.trim());
});
async commit(message) {
return git('commit', '--message', message);
},
add: function(path, callback) {
childProcess.exec('git add -f ' + path, {}, function(err) {
if (err) {
throw new Error('git.add: ' + err.message);
}
callback();
});
},
commit: function(name, callback) {
childProcess.exec('git commit --message=' + name, {}, function(err) {
if (err) {
throw new Error('git.commit: ' + err.message);
}
callback();
});
},
tag: function(name, callback) {
childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(
err
) {
if (err) {
throw new Error('git.tag: ' + err.message);
}
callback();
});
},
tagName: function(callback) {
childProcess.exec('git describe --tags', {}, function(err, stdout) {
if (err) {
throw new Error('git.tagName: ' + err.message);
}
var tags = stdout.trim().split(/\n/);
tags = tags.filter(function(info) {
info = info.split('-');
return info.length == 1;
});
var versionTags = tags.filter(function(info) {
return /^v/.test(info[0]);
});
callback(undefined, versionTags[0] || tags[0]);
});
}
git // visible for testing
};
async function getHeadSha() {
const stdout = await git('rev-parse', '--short', 'HEAD');
return stdout.trim();
}
async function getMasterSha() {
try {
const stdout = await git('rev-parse', '--short', 'origin/master');
return stdout.trim();
} catch (error) {
if (/Needed a single revision/.test(error.message)) {
// Master was not checked out but in this case, so we know we are not master. We can ignore this
return '';
}
throw error;
}
}
async function getTagName() {
const stdout = await git('tag', '-l', '--points-at', 'HEAD');
const trimmedStdout = stdout.trim();
if (trimmedStdout === '') {
return null; // there is no tag
}
const tags = trimmedStdout.split(/\n|\r\n/);
const versionTags = tags.filter(tag => /^v/.test(tag));
if (versionTags[0] != null) {
return versionTags[0];
}
return tags[0];
}
async function git(...args) {
return new Promise((resolve, reject) =>
childProcess.execFile('git', args, (err, stdout) => {
if (err != null) {
return reject(
new Error(`"git ${args.join(' ')}" caused error: ${err.message}`)
);
}
resolve(stdout);
})
);
}
+52 -58
View File
@@ -1,68 +1,62 @@
var async = require('neo-async'),
git = require('./util/git'),
semver = require('semver');
const git = require('./util/git');
const semver = require('semver');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
module.exports = function(grunt) {
grunt.registerTask(
'version',
'Updates the current release version',
function() {
var done = this.async(),
pkg = grunt.config('pkg'),
version = grunt.option('ver');
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
if (!semver.valid(version)) {
throw new Error(
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
version +
'\n\n'
);
}
pkg.version = version;
grunt.config('pkg', pkg);
grunt.log.writeln('Updating to version ' + version);
async.each(
[
[
'lib/handlebars/base.js',
/const VERSION = ['"](.*)['"];/,
"const VERSION = '" + version + "';"
],
[
'components/bower.json',
/"version":.*/,
'"version": "' + version + '",'
],
[
'components/package.json',
/"version":.*/,
'"version": "' + version + '",'
],
[
'components/handlebars.js.nuspec',
/<version>.*<\/version>/,
'<version>' + version + '</version>'
]
],
function(args, callback) {
replace.apply(undefined, args);
grunt.log.writeln(' - ' + args[0]);
git.add(args[0], callback);
},
function() {
grunt.task.run(['default']);
done();
}
registerAsyncTask('version', async () => {
const pkg = grunt.config('pkg');
const version = grunt.option('ver');
if (!semver.valid(version)) {
throw new Error(
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
version +
'\n\n'
);
}
);
pkg.version = version;
grunt.config('pkg', pkg);
function replace(path, regex, value) {
var content = grunt.file.read(path);
const replaceSpec = [
{
path: 'lib/handlebars/base.js',
regex: /const VERSION = ['"](.*)['"];/,
replacement: `const VERSION = '${version}';`
},
{
path: 'components/bower.json',
regex: /"version":.*/,
replacement: `"version": "${version}",`
},
{
path: 'components/package.json',
regex: /"version":.*/,
replacement: `"version": "${version}",`
},
{
path: 'components/handlebars.js.nuspec',
regex: /<version>.*<\/version>/,
replacement: `<version>${version}</version>`
}
];
await Promise.all(
replaceSpec.map(replaceSpec =>
replaceAndAdd(
replaceSpec.path,
replaceSpec.regex,
replaceSpec.replacement
)
)
);
grunt.task.run(['default']);
});
async function replaceAndAdd(path, regex, value) {
let content = grunt.file.read(path);
content = content.replace(regex, value);
grunt.file.write(path, content);
await git.add(path);
}
};