release.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import { exec, spawnSync } from 'child_process';
  2. import { spawn } from 'cross-spawn';
  3. import { writeFileSync } from 'fs';
  4. import * as changelog from 'conventional-changelog';
  5. import * as GithubApi from 'github';
  6. import { dest, src, start, task } from 'gulp';
  7. import { prompt } from 'inquirer';
  8. import { rollup } from 'rollup';
  9. import * as commonjs from 'rollup-plugin-commonjs';
  10. import * as nodeResolve from 'rollup-plugin-node-resolve';
  11. import * as runSequence from 'run-sequence';
  12. import * as semver from 'semver';
  13. import { obj } from 'through2';
  14. import { DIST_BUILD_ROOT, DIST_BUILD_UMD_BUNDLE_ENTRYPOINT, DIST_BUNDLE_ROOT, PROJECT_ROOT, SCRIPTS_ROOT, SRC_ROOT } from '../constants';
  15. import { compileSass, copyFonts, createTimestamp, setSassIonicVersion, writePolyfills } from '../util';
  16. var promptAnswers;
  17. // Nightly: releases a nightly version
  18. task('nightly', (done: (err: any) => void) => {
  19. runSequence('release.pullLatest',
  20. 'validate',
  21. 'release.prepareReleasePackage',
  22. 'release.publishNightly',
  23. done);
  24. });
  25. // Release: prompt, update, publish
  26. task('release', (done: (err: any) => void) => {
  27. runSequence('release.pullLatest',
  28. 'validate',
  29. 'release.prepareReleasePackage',
  30. 'release.promptVersion',
  31. 'release.update',
  32. 'release.publish',
  33. done);
  34. });
  35. // Release.test: prompt and update
  36. task('release.test', (done: (err: any) => void) => {
  37. runSequence('validate',
  38. 'release.prepareReleasePackage',
  39. 'release.promptVersion',
  40. 'release.update',
  41. done);
  42. });
  43. // Release.update: update package.json and changelog
  44. task('release.update', (done: (err: any) => void) => {
  45. if (promptAnswers.confirmRelease === 'yes') {
  46. runSequence('release.copyProdVersion',
  47. 'release.prepareChangelog',
  48. done);
  49. } else {
  50. console.log('Did not run release.update tasks, aborted release');
  51. done(null);
  52. }
  53. });
  54. // Release.publish: publish to GitHub and npm
  55. task('release.publish', (done: (err: any) => void) => {
  56. if (promptAnswers.confirmRelease === 'yes') {
  57. runSequence('release.publishNpmRelease',
  58. 'release.publishGithubRelease',
  59. done);
  60. } else {
  61. console.log('Did not run release.publish tasks, aborted release');
  62. done(null);
  63. }
  64. });
  65. task('release.publishGithubRelease', (done: Function) => {
  66. const packageJSON = require('../../../package.json');
  67. const github = new GithubApi({
  68. version: '3.0.0'
  69. });
  70. github.authenticate({
  71. type: 'oauth',
  72. token: process.env.GH_TOKEN
  73. });
  74. return changelog({
  75. preset: 'angular'
  76. })
  77. .pipe(obj(function(file, enc, cb) {
  78. github.releases.createRelease({
  79. owner: 'ionic-team',
  80. repo: 'ionic',
  81. target_commitish: 'master',
  82. tag_name: 'v' + packageJSON.version,
  83. name: packageJSON.version,
  84. body: file.toString(),
  85. prerelease: false
  86. }, done);
  87. }));
  88. });
  89. task('release.publishNpmRelease', (done: Function) => {
  90. const npmCmd = spawn('npm', ['publish', DIST_BUILD_ROOT]);
  91. npmCmd.stdout.on('data', function (data) {
  92. console.log(data.toString());
  93. });
  94. npmCmd.stderr.on('data', function (data) {
  95. console.log('npm err: ' + data.toString());
  96. });
  97. npmCmd.on('close', function() {
  98. done();
  99. });
  100. });
  101. task('release.promptVersion', (done: Function) => {
  102. prompt([
  103. {
  104. type: 'list',
  105. name: 'release',
  106. message: 'What type of release is this?',
  107. choices: [
  108. {
  109. name: 'Major: Incompatible API changes',
  110. value: 'major'
  111. }, {
  112. name: 'Minor: Backwards-compatible functionality',
  113. value: 'minor'
  114. }, {
  115. name: 'Patch: Backwards-compatible bug fixes',
  116. value: 'patch'
  117. }, {
  118. name: 'Premajor',
  119. value: 'premajor'
  120. }, {
  121. name: 'Preminor',
  122. value: 'preminor'
  123. }, {
  124. name: 'Prepatch',
  125. value: 'prepatch'
  126. }, {
  127. name: 'Prerelease',
  128. value: 'prerelease'
  129. }
  130. ]
  131. }, {
  132. type: 'list',
  133. name: 'confirmRelease',
  134. default: 'no',
  135. choices: [
  136. {
  137. name: 'Yes',
  138. value: 'yes'
  139. }, {
  140. name: 'Abort release',
  141. value: 'no'
  142. }
  143. ],
  144. message: function(answers) {
  145. var SEP = '---------------------------------';
  146. console.log('\n' + SEP + '\n' + getVersion(answers) + '\n' + SEP + '\n');
  147. return 'Are you sure you want to proceed with the release version above?';
  148. }
  149. }
  150. ]).then(function (answers) {
  151. // Continue with the release if version was confirmed
  152. promptAnswers = answers;
  153. done();
  154. });
  155. });
  156. function getVersion(answers) {
  157. const sourcePackageJSON = require(`${PROJECT_ROOT}/package.json`);
  158. return semver.inc(sourcePackageJSON.version, answers.release, true);
  159. }
  160. task('release.copyProdVersion', () => {
  161. // Increment the version and update the source package file
  162. const sourcePackageJSON = require(`${PROJECT_ROOT}/package.json`);
  163. sourcePackageJSON.version = semver.inc(sourcePackageJSON.version, promptAnswers.release, true);
  164. const sourcePrettyPrintedJson = JSON.stringify(sourcePackageJSON, null, 2);
  165. writeFileSync(`${PROJECT_ROOT}/package.json`, sourcePrettyPrintedJson);
  166. // Copy the source package version and update it in the build package file
  167. const packageJsonToUpdate = require(`${DIST_BUILD_ROOT}/package.json`);
  168. packageJsonToUpdate.version = sourcePackageJSON.version;
  169. const prettyPrintedJson = JSON.stringify(packageJsonToUpdate, null, 2);
  170. writeFileSync(`${DIST_BUILD_ROOT}/package.json`, prettyPrintedJson);
  171. });
  172. task('release.prepareReleasePackage', (done: (err: any) => void) => {
  173. runSequence('clean',
  174. 'release.polyfill',
  175. 'compile.release',
  176. 'release.copyTemplates',
  177. 'release.copyNpmInfo',
  178. 'release.preparePackageJsonTemplate',
  179. 'release.nightlyPackageJson',
  180. 'release.compileSass',
  181. 'release.fonts',
  182. 'release.sass',
  183. 'release.createUmdBundle',
  184. done);
  185. });
  186. task('release.createUmdBundle', (done: Function) => {
  187. return rollup({
  188. entry: DIST_BUILD_UMD_BUNDLE_ENTRYPOINT,
  189. plugins: [
  190. nodeResolve({
  191. module: true,
  192. jsnext: true,
  193. main: true
  194. }),
  195. commonjs()
  196. ]
  197. }).then((bundle) => {
  198. return bundle.write({
  199. format: 'umd',
  200. moduleName: 'ionicBundle',
  201. dest: `${DIST_BUNDLE_ROOT}/ionic.umd.js`
  202. });
  203. });
  204. });
  205. task('release.polyfill', (done: Function) => {
  206. writePolyfills('dist/ionic-angular/polyfills').then(() => {
  207. done();
  208. }).catch(err => {
  209. done(err);
  210. });
  211. });
  212. task('release.publishNightly', (done: Function) => {
  213. const npmCmd = spawn('npm', ['publish', '--tag=nightly', DIST_BUILD_ROOT]);
  214. npmCmd.stdout.on('data', function (data) {
  215. console.log(data.toString());
  216. });
  217. npmCmd.stderr.on('data', function (data) {
  218. console.log('npm err: ' + data.toString());
  219. });
  220. npmCmd.on('close', function() {
  221. done();
  222. });
  223. });
  224. task('release.compileSass', () => {
  225. return compileSass(`${DIST_BUILD_ROOT}/css`);
  226. });
  227. task('release.fonts', () => {
  228. return copyFonts(`${DIST_BUILD_ROOT}/fonts`);
  229. });
  230. task('release.sass', () => {
  231. return src([`${SRC_ROOT}/**/*.scss`, `!${SRC_ROOT}/components/*/test/**/*`, `!${SRC_ROOT}/util/test/*`]).pipe(dest(`${DIST_BUILD_ROOT}`));
  232. });
  233. task('release.pullLatest', (done: Function) => {
  234. exec('git status --porcelain', (err: Error, stdOut: string) => {
  235. if (err) {
  236. done(err);
  237. } else if ( stdOut && stdOut.length > 0) {
  238. done(new Error('There are uncommited changes. Please commit or stash changes.'));
  239. } else {
  240. const gitPullResult = spawnSync('git', ['pull', 'origin', 'master']);
  241. if (gitPullResult.status !== 0) {
  242. done(new Error('Error running git pull'));
  243. }
  244. done();
  245. }
  246. });
  247. });
  248. task('release.prepareChangelog', () => {
  249. const changelog = require('gulp-conventional-changelog');
  250. return src(`${PROJECT_ROOT}/CHANGELOG.md`)
  251. .pipe(changelog({
  252. preset: 'angular'
  253. }))
  254. .pipe(dest(`${PROJECT_ROOT}`));
  255. });
  256. task('release.copyTemplates', () => {
  257. return src([`${SCRIPTS_ROOT}/templates/**/*`]).pipe(dest(`${DIST_BUILD_ROOT}/templates`));
  258. });
  259. task('release.copyNpmInfo', () => {
  260. return src([`${PROJECT_ROOT}/scripts/npm/.npmignore`, `${PROJECT_ROOT}/scripts/npm/README.md`]).pipe(dest(DIST_BUILD_ROOT));
  261. });
  262. task('release.preparePackageJsonTemplate', () => {
  263. let templatePackageJSON = require(`${PROJECT_ROOT}/scripts/npm/package.json`);
  264. const sourcePackageJSON = require(`${PROJECT_ROOT}/package.json`);
  265. // copy source package.json data to template
  266. templatePackageJSON.version = sourcePackageJSON.version;
  267. templatePackageJSON.description = sourcePackageJSON.description;
  268. templatePackageJSON.keywords = sourcePackageJSON.keywords;
  269. // copy source dependencies versions to the template's peerDependencies
  270. // only copy dependencies that show up as peerDependencies in the template
  271. for (let dependency in sourcePackageJSON.dependencies) {
  272. // if the dependency is in both, AND the value of the entry is empty, copy it over
  273. if (dependency in templatePackageJSON.peerDependencies && templatePackageJSON.peerDependencies[dependency] === '') {
  274. templatePackageJSON.peerDependencies[dependency] = sourcePackageJSON.dependencies[dependency];
  275. }
  276. }
  277. writeFileSync(`${DIST_BUILD_ROOT}` + '/package.json', JSON.stringify(templatePackageJSON, null, 2));
  278. });
  279. task('release.nightlyPackageJson', () => {
  280. const packageJson: any = require(`${DIST_BUILD_ROOT}/package.json`);
  281. packageJson.version = packageJson.version.split('-')
  282. .slice(0, 2)
  283. .concat(createTimestamp())
  284. .join('-');
  285. writeFileSync(`${DIST_BUILD_ROOT}/package.json`, JSON.stringify(packageJson, null, 2));
  286. setSassIonicVersion(packageJson.version);
  287. });