mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-26 04:13:30 +03:00
f16dc290b7
addresses #1789, #1364 - Moves ./core/server/loader -> ./core/bootstrap. The bootstrap file is only accessed once during startup, and it’s sole job is to ensure a config.js file exists (creating one if it doesn’t) and then validates the contents of the config file. Since this is directly related to the initializing the application is is appropriate to have it in the ./core folder, named bootstrap as that is what it does. This also improves the dependency graph, as now the bootstrap file require’s the ./core/server/config module and is responsible for passing in the validated config file. Whereas before we had ./core/server/config require’ing ./core/server/loader and running its init code and then passing that value back to itself, the flow is now more straight forward of ./core/bootstrap handling initialization and then instatiation of config module - Merges ./core/server/config/paths into ./core/server/config This flow was always confusing me to that some config options were on the config object, and some were on the paths object. This change now incorporates all of the variables previously defined in config/paths directly into the config module, and in extension, the config.js file. This means that you now have the option of deciding at startup where the content directory for ghost should reside. - broke out loader tests in config_spec to bootstrap_spec - updated all relevant files to now use config().paths - moved urlFor and urlForPost function into ./server/config/url.js
860 lines
32 KiB
JavaScript
860 lines
32 KiB
JavaScript
// # Gruntfile - Task automation for Ghost
|
|
// Run various tasks when developing for and working with Ghost
|
|
// Run `grunt --help` or visit https://github.com/TryGhost/Ghost/wiki/Grunt-Toolkit/ for usage instructions
|
|
|
|
var path = require('path'),
|
|
when = require('when'),
|
|
semver = require('semver'),
|
|
fs = require('fs'),
|
|
_ = require('lodash'),
|
|
spawn = require('child_process').spawn,
|
|
buildDirectory = path.resolve(process.cwd(), '.build'),
|
|
distDirectory = path.resolve(process.cwd(), '.dist'),
|
|
bootstrap = require('./core/bootstrap'),
|
|
|
|
|
|
// ## Build File Patterns
|
|
// a list of files and paterns to process and exclude when running builds & releases
|
|
buildGlob = [
|
|
'**',
|
|
'!docs/**',
|
|
'!_site/**',
|
|
'!content/images/**',
|
|
'content/images/README.md',
|
|
'!content/themes/**',
|
|
'content/themes/casper/**',
|
|
'!content/apps/**',
|
|
'content/apps/README.md',
|
|
'!node_modules/**',
|
|
'!core/test/**',
|
|
'!core/client/assets/sass/**',
|
|
'!core/server/data/export/exported*',
|
|
'!**/*.db*',
|
|
'!*.db*',
|
|
'!.sass*',
|
|
'!.af*',
|
|
'!.git*',
|
|
'!.groc*',
|
|
'!*.iml',
|
|
'!config.js',
|
|
'!CONTRIBUTING.md',
|
|
'!SECURITY.md',
|
|
'!.travis.yml',
|
|
'!Gemfile*',
|
|
'!*.html'
|
|
],
|
|
|
|
// ## Grunt configuration
|
|
|
|
configureGrunt = function (grunt) {
|
|
|
|
// load all grunt tasks
|
|
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
|
|
|
|
var cfg = {
|
|
// Common paths to be used by tasks
|
|
paths: {
|
|
adminAssets: './core/client/assets',
|
|
build: buildDirectory,
|
|
releaseBuild: path.join(buildDirectory, 'release'),
|
|
dist: distDirectory,
|
|
releaseDist: path.join(distDirectory, 'release')
|
|
},
|
|
buildType: 'Build',
|
|
pkg: grunt.file.readJSON('package.json'),
|
|
|
|
// ### Config for grunt-contrib-watch
|
|
// Watch files and livereload in the browser during development
|
|
watch: {
|
|
handlebars: {
|
|
files: ['core/client/tpl/**/*.hbs'],
|
|
tasks: ['handlebars']
|
|
},
|
|
sass: {
|
|
files: ['<%= paths.adminAssets %>/sass/**/*'],
|
|
tasks: ['sass:admin']
|
|
},
|
|
concat: {
|
|
files: [
|
|
'core/client/*.js',
|
|
'core/client/helpers/*.js',
|
|
'core/client/models/*.js',
|
|
'core/client/tpl/*.js',
|
|
'core/client/views/*.js'
|
|
],
|
|
tasks: ['concat']
|
|
},
|
|
livereload: {
|
|
files: [
|
|
// Theme CSS
|
|
'content/themes/casper/css/*.css',
|
|
// Theme JS
|
|
'content/themes/casper/js/*.js',
|
|
// Admin CSS
|
|
'<%= paths.adminAssets %>/css/*.css',
|
|
// Admin JS
|
|
'core/built/scripts/*.js'
|
|
],
|
|
options: {
|
|
livereload: true
|
|
}
|
|
},
|
|
express: {
|
|
// Restart any time client or server js files change
|
|
files: ['core/server.js', 'core/server/**/*.js'],
|
|
tasks: ['express:dev'],
|
|
options: {
|
|
//Without this option specified express won't be reloaded
|
|
nospawn: true
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-express-server
|
|
// Start our server in development
|
|
express: {
|
|
options: {
|
|
script: 'index.js',
|
|
output: 'Ghost is running'
|
|
},
|
|
|
|
dev: {
|
|
options: {
|
|
//output: 'Express server listening on address:.*$'
|
|
}
|
|
},
|
|
test: {
|
|
options: {
|
|
node_env: 'testing'
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-jslint
|
|
// JSLint all the things!
|
|
jslint: {
|
|
server: {
|
|
directives: {
|
|
// node environment
|
|
node: true,
|
|
// browser environment
|
|
browser: false,
|
|
// allow dangling underscores in var names
|
|
nomen: true,
|
|
// allow to do statements
|
|
todo: true,
|
|
// don't require use strict pragma
|
|
sloppy: true
|
|
},
|
|
files: {
|
|
src: [
|
|
'*.js',
|
|
'core/*.js',
|
|
'core/server/**/*.js'
|
|
]
|
|
}
|
|
},
|
|
client: {
|
|
directives: {
|
|
// node environment
|
|
node: false,
|
|
// browser environment
|
|
browser: true,
|
|
// allow dangling underscores in var names
|
|
nomen: true,
|
|
// allow to do statements
|
|
todo: true
|
|
},
|
|
files: {
|
|
src: 'core/client/**/*.js'
|
|
},
|
|
exclude: [
|
|
'core/client/assets/vendor/**/*.js',
|
|
'core/client/tpl/**/*.js'
|
|
]
|
|
},
|
|
shared: {
|
|
directives: {
|
|
// node environment
|
|
node: true,
|
|
// browser environment
|
|
browser: false,
|
|
// allow dangling underscores in var names
|
|
nomen: true,
|
|
// allow to do statements
|
|
todo: true,
|
|
// allow unused parameters
|
|
unparam: true,
|
|
// don't require use strict pragma
|
|
sloppy: true
|
|
},
|
|
files: {
|
|
src: [
|
|
'core/shared/**/*.js'
|
|
]
|
|
},
|
|
exclude: [
|
|
'core/shared/vendor/**/*.js'
|
|
]
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-mocha-cli
|
|
// Run mocha unit tests
|
|
mochacli: {
|
|
options: {
|
|
ui: 'bdd',
|
|
reporter: 'spec',
|
|
timeout: '15000'
|
|
},
|
|
|
|
unit: {
|
|
src: ['core/test/unit/**/*_spec.js']
|
|
},
|
|
|
|
model: {
|
|
src: ['core/test/integration/**/model*_spec.js']
|
|
},
|
|
|
|
client: {
|
|
src: ['core/test/unit/**/client*_spec.js']
|
|
},
|
|
|
|
server: {
|
|
src: ['core/test/unit/**/server*_spec.js']
|
|
},
|
|
|
|
shared: {
|
|
src: ['core/test/unit/**/shared*_spec.js']
|
|
},
|
|
|
|
perm: {
|
|
src: ['core/test/unit/**/permissions_spec.js']
|
|
},
|
|
|
|
migrate: {
|
|
src: [
|
|
'core/test/unit/**/export_spec.js',
|
|
'core/test/unit/**/import_spec.js'
|
|
]
|
|
},
|
|
|
|
storage: {
|
|
src: ['core/test/unit/**/storage*_spec.js']
|
|
},
|
|
|
|
integration: {
|
|
src: ['core/test/integration/**/model*_spec.js']
|
|
},
|
|
|
|
api: {
|
|
src: ['core/test/functional/api/*_test.js']
|
|
},
|
|
|
|
routes: {
|
|
src: ['core/test/functional/routes/*_test.js']
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-sass
|
|
// Compile all the SASS!
|
|
sass: {
|
|
admin: {
|
|
files: {
|
|
'<%= paths.adminAssets %>/css/screen.css': '<%= paths.adminAssets %>/sass/screen.scss'
|
|
}
|
|
},
|
|
compress: {
|
|
options: {
|
|
style: 'compressed'
|
|
},
|
|
files: {
|
|
'<%= paths.adminAssets %>/css/screen.css': '<%= paths.adminAssets %>/sass/screen.scss'
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### config for grunt-shell
|
|
// command line tools
|
|
shell: {
|
|
// install bourbon
|
|
bourbon: {
|
|
command: 'bourbon install --path <%= paths.adminAssets %>/sass/modules/'
|
|
},
|
|
// generate coverage report
|
|
coverage: {
|
|
command: function () {
|
|
// will work on windows only if mocha is globally installed
|
|
var cmd = !!process.platform.match(/^win/) ? 'mocha' : './node_modules/mocha/bin/mocha';
|
|
return cmd + ' --timeout 15000 --reporter html-cov > coverage.html ./core/test/blanket_coverage.js';
|
|
},
|
|
execOptions: {
|
|
env: 'NODE_ENV=' + process.env.NODE_ENV
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-handlebars
|
|
// Compile templates for admin client
|
|
handlebars: {
|
|
core: {
|
|
options: {
|
|
namespace: 'JST',
|
|
processName: function (filename) {
|
|
filename = filename.replace('core/client/tpl/', '');
|
|
return filename.replace('.hbs', '');
|
|
}
|
|
},
|
|
files: {
|
|
'core/client/tpl/hbs-tpl.js': 'core/client/tpl/**/*.hbs'
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-groc
|
|
// Generate documentation from code
|
|
groc: {
|
|
docs: {
|
|
options: {
|
|
'out': './docs/',
|
|
'glob': [
|
|
'README.md',
|
|
'config.example.js',
|
|
'index.js',
|
|
'core/*.js',
|
|
'core/server/**/*.js',
|
|
'core/shared/**/*.js',
|
|
'core/client/**/*.js'
|
|
],
|
|
'except': [
|
|
'!core/**/vendor/**/*.js',
|
|
'!core/client/tpl/**/*.js'
|
|
]
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-clean
|
|
// Clean up files as part of other tasks
|
|
clean: {
|
|
release: {
|
|
src: ['<%= paths.releaseBuild %>/**']
|
|
},
|
|
test: {
|
|
src: ['content/data/ghost-test.db']
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-copy
|
|
// Prepare files for builds / releases
|
|
copy: {
|
|
release: {
|
|
files: [{
|
|
expand: true,
|
|
src: buildGlob,
|
|
dest: '<%= paths.releaseBuild %>/'
|
|
}]
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-compress
|
|
// Zip up builds / releases
|
|
compress: {
|
|
release: {
|
|
options: {
|
|
archive: '<%= paths.releaseDist %>/Ghost-<%= pkg.version %>.zip'
|
|
},
|
|
expand: true,
|
|
cwd: '<%= paths.releaseBuild %>/',
|
|
src: ['**']
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-concat
|
|
// concatenate multiple JS files into a single file ready for use
|
|
concat: {
|
|
dev: {
|
|
files: {
|
|
'core/built/scripts/vendor.js': [
|
|
'core/shared/vendor/jquery/jquery.js',
|
|
'core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js',
|
|
'core/client/assets/lib/jquery-utils.js',
|
|
'core/client/assets/lib/uploader.js',
|
|
'core/shared/vendor/lodash.underscore.js',
|
|
'core/shared/vendor/backbone/backbone.js',
|
|
'core/shared/vendor/handlebars/handlebars-runtime.js',
|
|
'core/shared/vendor/moment.js',
|
|
|
|
'core/shared/vendor/jquery/jquery.ui.widget.js',
|
|
'core/shared/vendor/jquery/jquery.iframe-transport.js',
|
|
'core/shared/vendor/jquery/jquery.fileupload.js',
|
|
|
|
'core/client/assets/vendor/codemirror/codemirror.js',
|
|
'core/client/assets/vendor/codemirror/addon/mode/overlay.js',
|
|
'core/client/assets/vendor/codemirror/mode/markdown/markdown.js',
|
|
'core/client/assets/vendor/codemirror/mode/gfm/gfm.js',
|
|
'core/client/assets/vendor/showdown/showdown.js',
|
|
'core/client/assets/vendor/showdown/extensions/ghostdown.js',
|
|
'core/shared/vendor/showdown/extensions/github.js',
|
|
'core/client/assets/vendor/shortcuts.js',
|
|
'core/client/assets/vendor/validator-client.js',
|
|
'core/client/assets/vendor/countable.js',
|
|
'core/client/assets/vendor/to-title-case.js',
|
|
'core/client/assets/vendor/packery.pkgd.min.js',
|
|
'core/client/assets/vendor/fastclick.js',
|
|
'core/client/assets/vendor/nprogress.js'
|
|
],
|
|
|
|
'core/built/scripts/helpers.js': [
|
|
'core/client/init.js',
|
|
|
|
'core/client/mobile-interactions.js',
|
|
'core/client/toggle.js',
|
|
'core/client/markdown-actions.js',
|
|
'core/client/helpers/index.js'
|
|
],
|
|
|
|
'core/built/scripts/templates.js': [
|
|
'core/client/tpl/hbs-tpl.js'
|
|
],
|
|
|
|
'core/built/scripts/models.js': [
|
|
'core/client/models/**/*.js'
|
|
],
|
|
|
|
'core/built/scripts/views.js': [
|
|
'core/client/views/**/*.js',
|
|
'core/client/router.js'
|
|
]
|
|
}
|
|
},
|
|
prod: {
|
|
files: {
|
|
'core/built/scripts/ghost.js': [
|
|
'core/shared/vendor/jquery/jquery.js',
|
|
'core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js',
|
|
'core/client/assets/lib/jquery-utils.js',
|
|
'core/client/assets/lib/uploader.js',
|
|
'core/shared/vendor/lodash.underscore.js',
|
|
'core/shared/vendor/backbone/backbone.js',
|
|
'core/shared/vendor/handlebars/handlebars-runtime.js',
|
|
'core/shared/vendor/moment.js',
|
|
|
|
'core/shared/vendor/jquery/jquery.ui.widget.js',
|
|
'core/shared/vendor/jquery/jquery.iframe-transport.js',
|
|
'core/shared/vendor/jquery/jquery.fileupload.js',
|
|
|
|
'core/client/assets/vendor/codemirror/codemirror.js',
|
|
'core/client/assets/vendor/codemirror/addon/mode/overlay.js',
|
|
'core/client/assets/vendor/codemirror/mode/markdown/markdown.js',
|
|
'core/client/assets/vendor/codemirror/mode/gfm/gfm.js',
|
|
'core/client/assets/vendor/showdown/showdown.js',
|
|
'core/client/assets/vendor/showdown/extensions/ghostdown.js',
|
|
'core/shared/vendor/showdown/extensions/github.js',
|
|
'core/client/assets/vendor/shortcuts.js',
|
|
'core/client/assets/vendor/validator-client.js',
|
|
'core/client/assets/vendor/countable.js',
|
|
'core/client/assets/vendor/to-title-case.js',
|
|
'core/client/assets/vendor/packery.pkgd.min.js',
|
|
'core/client/assets/vendor/fastclick.js',
|
|
'core/client/assets/vendor/nprogress.js',
|
|
|
|
'core/client/init.js',
|
|
|
|
'core/client/mobile-interactions.js',
|
|
'core/client/toggle.js',
|
|
'core/client/markdown-actions.js',
|
|
'core/client/helpers/index.js',
|
|
|
|
'core/client/tpl/hbs-tpl.js',
|
|
|
|
'core/client/models/**/*.js',
|
|
|
|
'core/client/views/**/*.js',
|
|
|
|
'core/client/router.js'
|
|
]
|
|
}
|
|
}
|
|
},
|
|
|
|
// ### Config for grunt-contrib-uglify
|
|
// minify javascript file for production
|
|
uglify: {
|
|
prod: {
|
|
files: {
|
|
'core/built/scripts/ghost.min.js': 'core/built/scripts/ghost.js'
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
grunt.initConfig(cfg);
|
|
|
|
|
|
// ## Custom Tasks
|
|
|
|
grunt.registerTask('setTestEnv', 'Use "testing" Ghost config; unless we are running on travis (then show queries for debugging)', function () {
|
|
process.env.NODE_ENV = process.env.TRAVIS ? 'travis-' + process.env.DB : 'testing';
|
|
cfg.express.test.options.node_env = process.env.NODE_ENV;
|
|
});
|
|
|
|
grunt.registerTask('loadConfig', function () {
|
|
var done = this.async();
|
|
bootstrap().then(function () {
|
|
done();
|
|
});
|
|
});
|
|
|
|
grunt.registerTask('spawn-casperjs', function () {
|
|
var done = this.async(),
|
|
options = ['host', 'noPort', 'port', 'email', 'password'],
|
|
args = ['test']
|
|
.concat(grunt.option('target') || ['admin/', 'frontend/'])
|
|
.concat(['--includes=base.js', '--verbose', '--log-level=debug', '--port=2369']);
|
|
|
|
// Forward parameters from grunt to casperjs
|
|
_.each(options, function processOption(option) {
|
|
if (grunt.option(option)) {
|
|
args.push('--' + option + '=' + grunt.option(option));
|
|
}
|
|
});
|
|
|
|
grunt.util.spawn({
|
|
cmd: 'casperjs',
|
|
args: args,
|
|
opts: {
|
|
cwd: path.resolve('core/test/functional'),
|
|
stdio: 'inherit'
|
|
}
|
|
}, function (error, result, code) {
|
|
/*jslint unparam:true*/
|
|
if (error) {
|
|
grunt.fail.fatal(result.stdout);
|
|
}
|
|
grunt.log.writeln(result.stdout);
|
|
done();
|
|
});
|
|
});
|
|
|
|
/* Generate Changelog
|
|
* - Pulls changelog from git, excluding merges.
|
|
* - Uses first line of commit message. Includes committer name.
|
|
*/
|
|
grunt.registerTask('changelog', 'Generate changelog from Git', function () {
|
|
// TODO: Break the contents of this task out into a separate module,
|
|
// put on npm. (@cgiffard)
|
|
|
|
var done = this.async();
|
|
|
|
function git(args, callback, depth) {
|
|
depth = depth || 0;
|
|
|
|
if (!depth) {
|
|
grunt.log.writeln('git ' + args.join(' '));
|
|
}
|
|
|
|
var buffer = [];
|
|
spawn('git', args, {
|
|
// We can reasonably assume the gruntfile will be in the root of the repo.
|
|
cwd : __dirname,
|
|
|
|
stdio : ['ignore', 'pipe', process.stderr]
|
|
|
|
}).on('exit', function (code) {
|
|
|
|
// Process exited correctly but we got no output.
|
|
// Spawn again, but make sure we don't spiral out of control.
|
|
// Hack to work around an apparent node bug.
|
|
//
|
|
// Frustratingly, it's impossible to distinguish this
|
|
// bug from a genuine empty log.
|
|
|
|
if (!buffer.length && code === 0 && depth < 20) {
|
|
return setImmediate(function () {
|
|
git(args, callback, depth ? depth + 1 : 1);
|
|
});
|
|
}
|
|
|
|
if (code === 0) {
|
|
return callback(buffer.join(''));
|
|
}
|
|
|
|
// We failed. Git returned a non-standard exit code.
|
|
grunt.log.error('Git returned a non-zero exit code.');
|
|
done(false);
|
|
|
|
// Push returned data into the buffer
|
|
}).stdout.on('data', buffer.push.bind(buffer));
|
|
}
|
|
|
|
// Crazy function for getting around inconsistencies in tagging
|
|
function sortTags(a, b) {
|
|
a = a.tag;
|
|
b = b.tag;
|
|
|
|
// NOTE: Accounting for different tagging method for
|
|
// 0.2.1 and up.
|
|
|
|
// If we didn't have this issue I'd just pass rcompare
|
|
// into sort directly. Could be something to think about
|
|
// in future.
|
|
|
|
if (semver.rcompare(a, '0.2.0') < 0 ||
|
|
semver.rcompare(b, '0.2.0') < 0) {
|
|
|
|
return semver.rcompare(a, b);
|
|
}
|
|
|
|
a = a.split('-');
|
|
b = b.split('-');
|
|
|
|
if (semver.rcompare(a[0], b[0]) !== 0) {
|
|
return semver.rcompare(a[0], b[0]);
|
|
}
|
|
|
|
// Using this janky looking integerising-method
|
|
// because it's faster and doesn't result in NaN, which
|
|
// breaks sorting
|
|
/*jslint bitwise: true */
|
|
return (+b[1] | 0) - (+a[1] | 0);
|
|
}
|
|
|
|
// Gets tags in master branch, sorts them with semver,
|
|
function getTags(callback) {
|
|
git(['show-ref', '--tags'], function (results) {
|
|
results = results
|
|
.split(/\n+/)
|
|
.filter(function (tag) {
|
|
return tag.length && tag.match(/\/\d+\.\d+/);
|
|
})
|
|
.map(function (tag) {
|
|
return {
|
|
'tag': tag.split(/tags\//).pop().trim(),
|
|
'ref': tag.split(/\s+/).shift().trim()
|
|
};
|
|
})
|
|
.sort(sortTags);
|
|
|
|
callback(results);
|
|
});
|
|
}
|
|
|
|
// Parses log to extract commit data.
|
|
function parseLog(data) {
|
|
var commits = [],
|
|
commitRegex =
|
|
new RegExp(
|
|
'\\n*[|\\*\\s]*commit\\s+([a-f0-9]+)' +
|
|
'\\n[|\\*\\s]*Author:\\s+([^<\\n]+)<([^>\\n]+)>' +
|
|
'\\n[|\\*\\s]*Date:\\s+([^\\n]+)' +
|
|
'\\n+[|\\*\\s]*[ ]{4}([^\\n]+)',
|
|
'ig'
|
|
);
|
|
|
|
// Using String.prototype.replace as a kind of poor-man's substitute
|
|
// for a streaming parser.
|
|
data.replace(
|
|
commitRegex,
|
|
function (wholeCommit, hash, author, email, date, message) {
|
|
/*jslint unparam:true*/
|
|
|
|
// The author name and commit message may have trailing space.
|
|
author = author.trim();
|
|
message = message.trim();
|
|
|
|
// Reformat date to make it parse-able by JS
|
|
date =
|
|
date.replace(
|
|
/^(\w+)\s(\w+)\s(\d+)\s([\d\:]+)\s(\d+)\s([\+\-\d]+)$/,
|
|
'$1, $2 $3 $5 $4 $6'
|
|
);
|
|
|
|
commits.push({
|
|
'hash': hash,
|
|
'author': author,
|
|
'email': email,
|
|
'date': date,
|
|
'parsedDate': new Date(Date.parse(date)),
|
|
'message': message
|
|
});
|
|
|
|
return null;
|
|
}
|
|
);
|
|
|
|
return commits;
|
|
}
|
|
|
|
// Gets git log for specified range.
|
|
function getLog(to, from, callback) {
|
|
var range = from && to ? from + '..' + to : '',
|
|
args = [ 'log', 'master', '--no-color', '--no-merges', '--graph' ];
|
|
|
|
if (range) {
|
|
args.push(range);
|
|
}
|
|
|
|
git(args, function (data) {
|
|
callback(parseLog(data));
|
|
});
|
|
}
|
|
|
|
// Run the job
|
|
getTags(function (tags) {
|
|
var logPath = path.join(__dirname, 'CHANGELOG.md'),
|
|
log = fs.createWriteStream(logPath),
|
|
commitCache = {};
|
|
|
|
function processTag(tag, callback) {
|
|
var buffer = '',
|
|
peek = tag[1];
|
|
|
|
tag = tag[0];
|
|
|
|
getLog(tag.tag, peek.tag, function (commits) {
|
|
|
|
// Use the comparison with HEAD to remove commits which
|
|
// haven't been included in a build/release yet.
|
|
|
|
if (tag.tag === 'HEAD') {
|
|
commits.forEach(function (commit) {
|
|
commitCache[commit.hash] = true;
|
|
});
|
|
|
|
return callback('');
|
|
}
|
|
|
|
buffer += '## Release ' + tag.tag + '\n';
|
|
|
|
commits = commits
|
|
.filter(function (commit) {
|
|
|
|
// Get rid of jenkins' release tagging commits
|
|
// Remove commits we've already spat out
|
|
return (
|
|
commit.author !== 'TryGhost-Jenkins' &&
|
|
!commitCache[commit.hash]
|
|
);
|
|
})
|
|
.map(function (commit) {
|
|
buffer += '\n* ' + commit.message + ' (_' + commit.author + '_)';
|
|
commitCache[commit.hash] = true;
|
|
});
|
|
|
|
if (!commits.length) {
|
|
buffer += '\nNo changes were made in this build.\n';
|
|
}
|
|
|
|
callback(buffer + '\n');
|
|
});
|
|
}
|
|
|
|
// Get two weeks' worth of tags
|
|
tags.unshift({'tag': 'HEAD'});
|
|
|
|
tags =
|
|
tags
|
|
.slice(0, 14)
|
|
.map(function (tag, index) {
|
|
return [
|
|
tag,
|
|
tags[index + 1] || tags[index]
|
|
];
|
|
});
|
|
|
|
log.write('# Ghost Changelog\n\n');
|
|
log.write('_Showing ' + tags.length + ' releases._\n');
|
|
|
|
when.reduce(tags,
|
|
function (prev, tag, idx) {
|
|
/*jslint unparam:true*/
|
|
return when.promise(function (resolve) {
|
|
processTag(tag, function (releaseData) {
|
|
resolve(prev + '\n' + releaseData);
|
|
});
|
|
});
|
|
}, '')
|
|
.then(function (reducedChangelog) {
|
|
log.write(reducedChangelog);
|
|
log.close();
|
|
done(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
grunt.registerTask('release',
|
|
'Release task - creates a final built zip\n' +
|
|
' - Do our standard build steps (sass, handlebars, etc)\n' +
|
|
' - Generate changelog for the past 14 releases\n' +
|
|
' - Copy files to release-folder/#/#{version} directory\n' +
|
|
' - Clean out unnecessary files (travis, .git*, .af*, .groc*)\n' +
|
|
' - Zip files in release-folder to dist-folder/#{version} directory',
|
|
[
|
|
'shell:bourbon',
|
|
'sass:compress',
|
|
'handlebars',
|
|
'concat',
|
|
'uglify',
|
|
'clean:release',
|
|
'copy:release',
|
|
'compress:release'
|
|
]);
|
|
|
|
grunt.registerTask('dev',
|
|
'Dev Mode; watch files and restart server on changes',
|
|
[
|
|
'sass:admin',
|
|
'handlebars',
|
|
'concat',
|
|
'express:dev',
|
|
'watch'
|
|
]);
|
|
|
|
// ### Find out more about grunt task usage
|
|
|
|
grunt.registerTask('help',
|
|
'Outputs help information if you type `grunt help` instead of `grunt --help`',
|
|
function () {
|
|
console.log('Type `grunt --help` to get the details of available grunt tasks, or alternatively visit https://github.com/TryGhost/Ghost/wiki/Grunt-Toolkit');
|
|
});
|
|
|
|
|
|
// ### Running the test suites
|
|
|
|
grunt.registerTask('test-unit', 'Run unit tests (mocha)', ['clean:test', 'setTestEnv', 'loadConfig', 'mochacli:unit']);
|
|
|
|
grunt.registerTask('test-integration', 'Run integration tests (mocha + db access)', ['clean:test', 'setTestEnv', 'loadConfig', 'mochacli:integration']);
|
|
|
|
grunt.registerTask('test-functional', 'Run functional interface tests (CasperJS)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'spawn-casperjs', 'express:test:stop']);
|
|
|
|
grunt.registerTask('test-api', 'Run functional api tests (mocha)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'mochacli:api', 'express:test:stop']);
|
|
|
|
grunt.registerTask('test-routes', 'Run functional route tests (mocha)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'mochacli:routes', 'express:test:stop']);
|
|
|
|
grunt.registerTask('validate', 'Run tests and lint code', ['jslint', 'test-routes', 'test-unit', 'test-api', 'test-integration', 'test-functional']);
|
|
|
|
|
|
// ### Coverage report for Unit and Integration Tests
|
|
|
|
grunt.registerTask('test-coverage', 'Generate unit and integration (mocha) tests coverage report', ['clean:test', 'setTestEnv', 'loadConfig', 'shell:coverage']);
|
|
|
|
|
|
// ### Documentation
|
|
|
|
grunt.registerTask('docs', 'Generate Docs', ['groc']);
|
|
|
|
|
|
// ### Tools for building assets
|
|
|
|
grunt.registerTask('init', 'Prepare the project for development', ['shell:bourbon', 'default']);
|
|
|
|
// Before running in production mode
|
|
grunt.registerTask('prod', 'Build CSS, JS & templates for production', ['sass:compress', 'handlebars', 'concat', 'uglify']);
|
|
|
|
// When you just say 'grunt'
|
|
grunt.registerTask('default', 'Build CSS, JS & templates for development', ['update_submodules', 'sass:compress', 'handlebars', 'concat']);
|
|
};
|
|
|
|
module.exports = configureGrunt;
|