Skip to content

Commit 13baeaa

Browse files
committed
[added] Release task to push and tag docs and bower repos
1 parent 0193046 commit 13baeaa

File tree

8 files changed

+292
-4
lines changed

8 files changed

+292
-4
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ cjs/*
1212
amd/*
1313
ie8/bundle.js
1414
lib/*
15+
tmp-bower-repo/
16+
tmp-docs-repo/

Gruntfile.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ module.exports = function (grunt) {
2626
dest: 'amd/',
2727
cwd: 'tools/amd',
2828
expand: true
29+
},
30+
{
31+
src: ['.gitignore-template'],
32+
dest: 'amd/',
33+
cwd: 'tools/amd',
34+
expand: true
2935
}
3036
]
3137
},
@@ -156,8 +162,7 @@ module.exports = function (grunt) {
156162
src: 'amd/<%= pkg.name %>.js',
157163
dest: 'amd/<%= pkg.name %>.min.js'
158164
}
159-
}
160-
165+
},
161166
});
162167

163168
grunt.loadNpmTasks('grunt-contrib-uglify');
@@ -183,6 +188,8 @@ module.exports = function (grunt) {
183188
'clean:transpiled'
184189
]);
185190

191+
require('./tools/release/tasks')(grunt);
192+
186193
grunt.registerTask('default', ['build']);
187194

188195
};

docs/.gitignore-template

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
*~
2+
node_modules/
3+
.DS_Store
4+
npm-debug.log
5+
.idea
6+
examples/
7+
src/
8+
build.js
9+
client.js
10+
server.js
11+
package.json

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
"scripts": {
1111
"build": "./node_modules/.bin/grunt build",
1212
"test-watch": "./node_modules/.bin/grunt watch 2>&1 >/dev/null & ./node_modules/karma/bin/karma start karma.dev.js",
13-
"test": "./node_modules/.bin/grunt build && ./node_modules/karma/bin/karma start karma.ci.js",
14-
"prepublish": "./node_modules/.bin/grunt build"
13+
"test": "./node_modules/.bin/grunt build && ./node_modules/karma/bin/karma start karma.ci.js"
1514
},
1615
"main": "lib/main.js",
1716
"directories": {
@@ -29,6 +28,7 @@
2928
"react": ">=0.12"
3029
},
3130
"devDependencies": {
31+
"async": "~0.2.9",
3232
"envify": "~1.2.1",
3333
"grunt": "~0.4.2",
3434
"grunt-amd-wrap": "^1.0.1",
@@ -58,6 +58,7 @@
5858
"react-async": "~2.0.0",
5959
"react-router-component": "git://github.com/STRML/react-router-component#react-0.12",
6060
"requirejs": "~2.1.9",
61+
"semver": "~2.0.7",
6162
"sinon": "^1.10.3"
6263
}
6364
}

tools/amd/.gitignore-template

Whitespace-only changes.

tools/release/exec-series.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var async = require('async'),
2+
spawnCommand = require('./spawn-command.js');
3+
4+
module.exports = function execSeries(args, cb, options) {
5+
async.eachSeries(
6+
args,
7+
function(args, callback) {
8+
console.log(args[0] + ' ' + args[1].join(' '));
9+
spawnCommand.apply(this, args.concat(options))
10+
.on('error', function(err) {
11+
throw err;
12+
})
13+
.on('exit', function(code) {
14+
if (code) {
15+
throw new Error('Failed executing ' + args);
16+
} else {
17+
callback();
18+
}
19+
});
20+
},
21+
cb);
22+
};

tools/release/spawn-command.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var spawn = require('child_process').spawn;
2+
var win32 = process.platform === 'win32';
3+
4+
// Normalize a command across OS and spawn it
5+
//
6+
// - command - A String containing a command to run
7+
// - arguments - An Array of arguments to pass the command
8+
//
9+
// Returns ChildProcess object (of the spawned command)
10+
module.exports = function spawnCommand(command, args, options) {
11+
var winCommand = win32 ? 'cmd' : command;
12+
var winArgs = win32 ? ['/c ' + command + ' ' + args.join(' ')] : args;
13+
14+
options = options || {};
15+
options.stdio = 'inherit';
16+
17+
return spawn(winCommand, winArgs, options);
18+
};

tools/release/tasks.js

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
var fs = require('fs');
2+
var async = require('async');
3+
var childProcess = require('child_process');
4+
var path = require('path');
5+
var semver = require('semver');
6+
var execSeries = require('./exec-series.js');
7+
var spawnCommand = require('./spawn-command.js');
8+
9+
module.exports = function(grunt) {
10+
grunt.registerTask('release', function(type) {
11+
var complete = this.async();
12+
13+
var filesToCopy = grunt.config.get('release-component.options.copy');
14+
var bowerRepo = '[email protected]:react-bootstrap/react-bootstrap-bower.git';
15+
var docsRepo = '[email protected]:react-bootstrap/react-bootstrap.github.io.git';
16+
17+
// Grunt is kind enough to change cwd to the directory the Gruntfile is in
18+
// but double check just in case
19+
var repoRoot = process.cwd();
20+
var libRoot = path.join(repoRoot, 'lib/');
21+
var bowerRoot = path.join(repoRoot, 'amd/');
22+
var docsRoot = path.join(repoRoot, 'docs/');
23+
var tmpBowerRepo = path.join(repoRoot, 'tmp-bower-repo');
24+
var tmpDocsRepo = path.join(repoRoot, 'tmp-docs-repo');
25+
26+
var version;
27+
28+
async.series([
29+
// Ensure git repo is actually ready to release
30+
ensureClean,
31+
ensureFetched,
32+
33+
// Clean build output
34+
function(next) {
35+
execSeries([
36+
['rm', ['-rf', bowerRoot]],
37+
['rm', ['-rf', libRoot]],
38+
], next);
39+
},
40+
41+
// Bump versions
42+
function(next) {
43+
modifyJSONSync(path.join(repoRoot, 'package.json'), function(packageJSON) {
44+
var oldVersion = packageJSON.version;
45+
if (!type) {
46+
type = 'patch';
47+
}
48+
if (['major', 'minor', 'patch'].indexOf(type) === -1) {
49+
version = type;
50+
} else {
51+
version = semver.inc(packageJSON.version, type || 'patch');
52+
}
53+
console.log('version changed from ' + oldVersion + ' to ' + version);
54+
packageJSON.version = version;
55+
});
56+
next();
57+
},
58+
59+
// Add and commit
60+
function(next) {
61+
execSeries([
62+
['git', ['add', path.join(repoRoot, 'package.json')]],
63+
['git', ['commit', '-m', '"Release v' + version + '"']]
64+
], next);
65+
},
66+
67+
// Build src
68+
function(next) {
69+
execSeries([
70+
['grunt', ['build']]
71+
], next);
72+
},
73+
74+
// Build docs
75+
function(next) {
76+
execSeries([
77+
['rm', ['-rf', path.join(docsRoot, 'node_modules')]],
78+
['git', ['clean', '-dfx']],
79+
['npm', ['install']],
80+
['npm', ['run', 'build']]
81+
], next, {
82+
cwd: docsRoot
83+
});
84+
},
85+
86+
// Tag
87+
function(next) {
88+
tag('v' + version, next);
89+
},
90+
91+
// Push
92+
function(next) {
93+
execSeries([
94+
['git', ['push']],
95+
['git', ['push', '--tags']]
96+
], next);
97+
},
98+
99+
// Publish to npm
100+
function(next) {
101+
execSeries([
102+
['npm', ['publish']]
103+
], next);
104+
},
105+
106+
function(next) {
107+
ReleaseRepo(bowerRepo, bowerRoot, tmpBowerRepo, version, next);
108+
},
109+
110+
function(next) {
111+
ReleaseRepo(docsRepo, docsRoot, tmpDocsRepo, version, next);
112+
},
113+
114+
], complete);
115+
});
116+
};
117+
118+
function ReleaseRepo(repo, srcFolder, tmpFolder, version, callback) {
119+
async.series([
120+
// Clone repo into tmpFolder and copy built files into it
121+
function(next) {
122+
var commands = [
123+
['rm', ['-rf', tmpFolder]],
124+
['git', ['clone', repo, tmpFolder]]
125+
];
126+
execSeries(commands, function() {
127+
var additionalCommands = fs.readdirSync(tmpFolder)
128+
.filter(function(f) { return f !== '.git'; })
129+
.map(function(f) { return ['rm', ['-rf', path.join(tmpFolder, f)]] });
130+
131+
additionalCommands.push(['cp', ['-R', srcFolder, tmpFolder]]);
132+
additionalCommands.push(['mv', [path.join(tmpFolder, '.gitignore-template'), path.join(tmpFolder, '.gitignore')]]);
133+
134+
execSeries(additionalCommands, next)
135+
});
136+
},
137+
138+
// Add and commit in repo
139+
function(next) {
140+
var commands = [
141+
['git', ['add', '-A', '.']],
142+
['git', ['commit', '-m', '"Release v' + version + '"']]
143+
];
144+
execSeries(commands, next, {
145+
cwd: tmpFolder
146+
});
147+
},
148+
149+
// Tag in repo
150+
function(next) {
151+
tag('v' + version, next, {
152+
cwd: tmpFolder
153+
});
154+
},
155+
156+
// Push in repo
157+
function(next) {
158+
execSeries([
159+
['git', ['push']],
160+
['git', ['push', '--tags']]
161+
], next, {
162+
cwd: tmpFolder
163+
});
164+
},
165+
166+
// Delete repo
167+
function(next) {
168+
execSeries([
169+
['rm', ['-rf', tmpFolder]]
170+
], next);
171+
}
172+
], callback);
173+
}
174+
175+
function ensureClean(callback) {
176+
childProcess.exec('git diff-index --name-only HEAD --', function(err, stdout, stderr) {
177+
if (err) {
178+
throw err;
179+
}
180+
181+
if (stdout.length) {
182+
throw new Error('Git repository must be clean');
183+
} else {
184+
callback();
185+
}
186+
});
187+
}
188+
189+
function tag(name, callback, options) {
190+
spawnCommand('git', ['tag', '-a', '--message=' + name, name], options)
191+
.on('error', function(err) {
192+
throw err;
193+
})
194+
.on('exit', function(code) {
195+
if (code) {
196+
throw new Error('Failed tagging ' + name + ' code: ' + code);
197+
} else {
198+
callback();
199+
}
200+
});
201+
}
202+
203+
function ensureFetched(callback) {
204+
childProcess.exec('git fetch', function(err, stdout, stderr) {
205+
if (err) {
206+
throw err;
207+
}
208+
209+
childProcess.exec('git branch -v --no-color | grep -e "^\\*"', function(err, stdout, stderr) {
210+
if (err) {
211+
throw err;
212+
}
213+
214+
if (/\[behind (.*)\]/.test(stdout)) {
215+
throw new Error('Your repo is behind by ' + RegExp.$1 + ' commits');
216+
}
217+
218+
callback();
219+
});
220+
});
221+
}
222+
223+
function modifyJSONSync(JSONPath, callback) {
224+
var json = JSON.parse(fs.readFileSync(JSONPath).toString());
225+
callback(json);
226+
fs.writeFileSync(JSONPath, JSON.stringify(json, null, 2));
227+
}

0 commit comments

Comments
 (0)