diff --git a/gulpfile.js b/gulpfile.js index 855aa479b0..0ab2b179be 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,1016 +1,10 @@ -var buildConfig = require('./scripts/build/config'); -var gulp = require('gulp'); -var path = require('path'); -var argv = require('yargs').argv; -var del = require('del'); -var rename = require('gulp-rename'); -var through2 = require('through2'); -var runSequence = require('run-sequence'); -var watch = require('gulp-watch'); -var tsc = require('gulp-typescript'); -var cache = require('gulp-cached'); -var remember = require('gulp-remember'); -var minimist = require('minimist'); -var connect = require('gulp-connect'); -var docsConfig = require('./scripts/config.json'); +'use strict'; -var flagConfig = { - string: ['port'], - boolean: ['debug', 'typecheck'], - alias: {'p': 'port'}, - default: { 'port': 8000, 'debug': true, 'typecheck': false } -}; -var flags = minimist(process.argv.slice(2), flagConfig); +const path = require('path'); -var DEBUG = flags.debug; -var TYPECHECK = flags.typecheck; - -function getTscOptions(name) { - var opts = { - emitDecoratorMetadata: true, - experimentalDecorators: true, - target: "es5", - module: "commonjs", - isolatedModules: true, - typescript: require('typescript') - } - - if (name === "typecheck") { - opts.declaration = true; - delete opts.isolatedModules; - } else if (name === "es6") { - opts.target = "es6"; - delete opts.module; - } - return opts; -} - -var tscReporter = { - error: function (error) { - // TODO - // suppress type errors until we convert everything to TS - // console.error(error.message); - } -}; - -// We use Babel to easily create named System.register modules -// See: https://github.com/Microsoft/TypeScript/issues/4801 -// and https://github.com/ivogabe/gulp-typescript/issues/211 -var babelOptions = { - presets: ['es2015'], - plugins: ['transform-es2015-modules-systemjs'], - moduleIds: true, - getModuleId: function(name) { - return 'ionic-angular/' + name; - } -} - -/** - * Builds Ionic sources to dist. When the '--typecheck' flag is specified, - * generates .d.ts definitions and does typechecking. - */ -gulp.task('build', function(done){ - runSequence( - 'copy.libs', - ['bundle', 'sass', 'fonts', 'copy.scss'], - done - ); +// Register TS compilation. +require('ts-node').register({ + project: path.join(__dirname, 'scripts/gulp') }); -/** - * Builds sources to dist and watches for changes. Runs 'transpile' for .ts - * changes and 'sass' for .scss changes. - */ -gulp.task('watch', ['build'], function() { - watchTask('transpile'); -}); - -function watchTask(task){ - watch([ - 'src/**/*.ts', - '!src/components/*/test/**/*', - '!src/util/test/*' - ], - function(file) { - if (file.event === "unlink") { - deleteFile(file); - } else { - gulp.start(task); - } - } - ); - - watch('src/**/*.scss', function() { - gulp.start('sass'); - }); - - gulp.start('serve'); - - function deleteFile(file) { - //TODO - // var basePath = file.base.substring(0, file.base.lastIndexOf("ionic/")); - // var relativePath = file.history[0].replace(file.base, '').replace('.ts', '.js'); - // - // var filePath = basePath + 'dist/' + relativePath; - // var typingPath = filePath.replace('.js', '.d.ts'); - // - // delete cache.caches['no-typecheck'][file.history[0]]; - // remember.forget('no-typecheck', file.history[0]); - // - // del([filePath, typingPath], function(){ - // gulp.start('bundle.system'); - // }); - } -} - -gulp.task('serve', function() { - connect.server({ - port: flags.port, - livereload: { - port: 35700 - } - }); -}); - -gulp.task('clean', function(done) { - del(['dist/**', '!dist'], done); -}); - - -/** - * Source build tasks - */ - -/** - * Creates CommonJS and SystemJS bundles from Ionic source files. - */ -gulp.task('bundle', ['bundle.cjs', 'bundle.system']); - -/** - * Creates CommonJS bundle from Ionic source files. - */ -gulp.task('bundle.cjs', ['transpile', 'copy.libs'], function(done){ - var config = require('./scripts/npm/ionic.webpack.config.js'); - bundle({ config: config, stats: true }); - - // build minified bundle - var minConfig = require('./scripts/npm/ionic.min.webpack.config.js'); - bundle({ config: minConfig, cb: finished, stats: true }); - - var outputPaths = [ - config.output.path + path.sep + config.output.filename, - minConfig.output.path + path.sep + minConfig.output.filename - ]; - - function bundle(args) { - var webpack = require('webpack'); - var path = require('path'); - - webpack(args.config, function(err, stats){ - if (args.stats) { - var statsOptions = { - 'colors': true, - 'modules': false, - 'chunks': false, - 'exclude': ['node_module'], - 'errorDetails': true - } - console.log(stats.toString(statsOptions)); - } - - args.cb && args.cb(); - }) - } - - function finished(){ - gulp.src(outputPaths) - .pipe(connect.reload()) - .on('end', done); - } -}); - -/** - * Creates SystemJS bundle from Ionic source files. - */ -gulp.task('bundle.system', function(){ - var babel = require('gulp-babel'); - var concat = require('gulp-concat'); - var gulpif = require('gulp-if'); - var stripDebug = require('gulp-strip-debug'); - var merge = require('merge2'); - - var tsResult = tsCompile(getTscOptions('es6'), 'system') - .pipe(babel(babelOptions)); - - var swiper = gulp.src('src/components/slides/swiper-widget.system.js'); - - return merge([tsResult, swiper]) - .pipe(remember('system')) - .pipe(gulpif(!DEBUG, stripDebug())) - .pipe(concat('ionic.system.js')) - .pipe(gulp.dest('dist/bundles')) - .pipe(connect.reload()) -}); - -/** - * Transpiles TypeScript sources to ES5 in the CommonJS module format and outputs - * them to dist. When the '--typecheck' flag is specified, generates .d.ts - * definitions and does typechecking. - */ -gulp.task('transpile', function(){ - var gulpif = require('gulp-if'); - var stripDebug = require('gulp-strip-debug'); - - var tscOpts = getTscOptions(TYPECHECK ? 'typecheck' : undefined); - var tsResult = tsCompile(tscOpts, 'transpile') - .on('error', function(err) { - console.log(err.message); - }); - - if (TYPECHECK) { - tsResult.on('error', function(err) { - process.exit(1); - }); - var merge = require('merge2'); - var js = tsResult.js; - var dts = tsResult.dts; - if (!DEBUG) js = js.pipe(stripDebug()); - // merge definition and source streams - return merge([js, dts]) - .pipe(gulp.dest('dist')); - } - - if (!DEBUG) tsResult = tsResult.pipe(stripDebug()); - return tsResult.pipe(gulp.dest('dist')); -}); - -function tsCompile(options, cacheName){ - return gulp.src([ - 'typings/main.d.ts', - 'src/**/*.ts', - '!src/**/*.d.ts', - '!src/components/*/test/**/*', - '!src/util/test/*', - '!src/config/test/*', - '!src/platform/test/*', - '!src/**/*.spec.ts' - ]) - .pipe(cache(cacheName, { optimizeMemory: true })) - .pipe(tsc(options, undefined, tscReporter)); -} - -gulp.task('bundle.es6', function() { - var babel = require('gulp-babel'); - - gulp.src([ - 'src/components/slides/swiper-widget.js' - ]) - .pipe(gulp.dest('dist/esm/components/slides')); - - return tsCompile(getTscOptions('es6'), 'bundle.es6') - .pipe(babel({ - presets: ['es2015-native-modules'] - })) - .pipe(gulp.dest('dist/esm')); -}); - -/** - * Compiles Ionic Sass sources to stylesheets and outputs them to dist/bundles. - */ -gulp.task('sass', function() { - var sass = require('gulp-sass'); - var autoprefixer = require('gulp-autoprefixer'); - var minifyCss = require('gulp-minify-css'); - - gulp.src([ - 'src/ionic.ios.scss', - 'src/ionic.md.scss', - 'src/ionic.wp.scss', - 'src/ionic.scss' - ]) - .pipe(sass({ - includePaths: [__dirname + '/node_modules/ionicons/dist/scss/'], - }).on('error', sass.logError) - ) - .pipe(autoprefixer(buildConfig.autoprefixer)) - .pipe(gulp.dest('dist/bundles/')) - .pipe(minifyCss()) - .pipe(rename({ extname: '.min.css' })) - .pipe(gulp.dest('dist/bundles/')); -}); - -/** - * Creates Ionic themes for testing. - */ -gulp.task('sass.themes', function() { - var sass = require('gulp-sass'); - var autoprefixer = require('gulp-autoprefixer'); - - function buildTheme(mode) { - gulp.src([ - 'scripts/e2e/ionic.' + mode + '.dark.scss' - ]) - .pipe(sass({ - includePaths: [__dirname + '/node_modules/ionicons/dist/scss/'], - }).on('error', sass.logError) - ) - .pipe(autoprefixer(buildConfig.autoprefixer)) - .pipe(gulp.dest('dist/bundles/')); - } - - buildTheme('ios'); - buildTheme('md'); - buildTheme('wp'); -}); - -/** - * Copies fonts and Ionicons to dist/fonts - */ -gulp.task('fonts', function() { - gulp.src([ - 'src/fonts/*.+(ttf|woff|woff2)', - 'node_modules/ionicons/dist/fonts/*.+(ttf|woff|woff2)' - ]) - .pipe(gulp.dest('dist/fonts')); -}); - -/** - * Copies Ionic Sass sources to dist - */ -gulp.task('copy.scss', function() { - return gulp.src([ - 'src/**/*.scss', - '!src/components/*/test/**/*', - '!src/util/test/*' - ]) - .pipe(gulp.dest('dist')); -}); - -/** - * Lint the scss files using a ruby gem - */ -gulp.task('lint.scss', function() { - var scsslint = require('gulp-scss-lint'); - - return gulp.src([ - 'src/**/*.scss', - '!src/components/*/test/**/*', - '!src/util/test/*' - ]) - .pipe(scsslint()) - .pipe(scsslint.failReporter()); -}); - -/** - * Copies miscellaneous scripts to dist. - */ -gulp.task('copy.libs', function() { - var merge = require('merge2'); - var extModules = gulp.src([ - 'node_modules/es6-shim/es6-shim.min.js', - 'node_modules/systemjs/node_modules/es6-module-loader/dist/es6-module-loader.src.js', //npm2 - 'node_modules/es6-module-loader/dist/es6-module-loader.src.js', //npm3 - 'node_modules/systemjs/dist/system.src.js', - 'node_modules/rxjs/bundles/Rx.js', - 'node_modules/zone.js/dist/zone.js', - 'node_modules/reflect-metadata/Reflect.js' - ]) - .pipe(gulp.dest('dist/js')); - - // for swiper-widget - var libs = gulp.src([ - 'src/**/*.js', - 'src/**/*.d.ts' - ]) - .pipe(gulp.dest('dist')); - - return merge([extModules, libs]); -}); - - -/** - * Test build tasks - */ - - /** - * Builds e2e tests to dist/e2e and watches for changes. Runs 'bundle.system' or - * 'sass' on Ionic source changes and 'e2e.build' for e2e test changes. - */ -gulp.task('watch.e2e', ['e2e'], function() { - watchTask('bundle.system'); - - watch('src/components/*/test/**/*', function(file) { - gulp.start('e2e.build'); - }); -}); - -/** - * Builds Ionic e2e tests to dist/e2e and creates the necessary files for tests - * to run. - */ -gulp.task('e2e', ['e2e.build', 'bundle.system', 'copy.libs', 'sass', 'fonts']); - -/** - * Builds Ionic e2e tests to dist/e2e. - */ -gulp.task('e2e.build', function() { - var gulpif = require('gulp-if'); - var merge = require('merge2'); - var _ = require('lodash'); - var fs = require('fs'); - var VinylFile = require('vinyl'); - - var indexTemplate = _.template( - fs.readFileSync('scripts/e2e/e2e.template.html') - )({ - buildConfig: buildConfig - }); - var testTemplate = _.template(fs.readFileSync('scripts/e2e/e2e.template.js')); - - var platforms = [ - 'android', - 'ios', - 'windows' - ]; - - // Get each test folder with gulp.src - var tsResult = gulp.src([ - 'src/components/*/test/*/**/*.ts', - '!src/components/*/test/*/**/*.spec.ts' - ]) - .pipe(cache('e2e.ts')) - .pipe(tsc(getTscOptions(), undefined, tscReporter)) - .on('error', function(error) { - console.log(error.message); - }) - .pipe(gulpif(/index.js$/, createIndexHTML())) - .pipe(gulpif(/e2e.js$/, createPlatformTests())) - - var testFiles = gulp.src([ - 'src/components/*/test/*/**/*', - '!src/components/*/test/*/**/*.ts' - ]) - .pipe(cache('e2e.files')) - - return merge([ - tsResult, - testFiles - ]) - .pipe(rename(function(file) { - var sep = path.sep; - file.dirname = file.dirname.replace(sep + 'test' + sep, sep); - })) - .pipe(gulp.dest('dist/e2e/')) - .pipe(connect.reload()); - - function createIndexHTML() { - return through2.obj(function(file, enc, next) { - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: path.join(path.dirname(file.path), 'index.html'), - })); - next(null, file); - }); - } - - function createPlatformTests(file) { - return through2.obj(function(file, enc, next) { - var self = this; - var relativePath = path.dirname(file.path.replace(/^.*?src(\/|\\)components(\/|\\)/, '')); - relativePath = relativePath.replace('/test/', '/'); - var contents = file.contents.toString(); - platforms.forEach(function(platform) { - var platformContents = testTemplate({ - contents: contents, - buildConfig: buildConfig, - relativePath: relativePath, - platform: platform - }); - self.push(new VinylFile({ - base: file.base, - contents: new Buffer(platformContents), - path: file.path.replace(/e2e.js$/, platform + '.e2e.js') - })); - }) - next(); - }) - } -}); - -/** - * Builds Ionic unit tests to dist/tests. - */ -gulp.task('tests', function() { - return gulp.src('src/**/test/**/*.spec.ts') - .pipe(cache('tests')) - .pipe(tsc(getTscOptions(), undefined, tscReporter)) - .pipe(rename(function(file) { - var regex = new RegExp(path.sep + 'test(' + path.sep + '|$)'); - file.dirname = file.dirname.replace(regex, path.sep); - })) - .pipe(gulp.dest('dist/tests')) -}); - -gulp.task('watch.tests', ['tests'], function(){ - watch('src/**/test/**/*.spec.ts', function(){ - gulp.start('tests'); - }); -}); - - -/** - * Demos - */ - - /** - * Builds Ionic demos to dist/demos, copies them to ../ionic-site and watches - * for changes. - */ -//TODO, decide on workflow for site demos (dev and prod), vs local dev (in dist) -var LOCAL_DEMOS = false; -gulp.task('watch.demos', function(done) { - LOCAL_DEMOS = true; - runSequence( - ['build.demos', 'transpile', 'copy.libs', 'sass', 'fonts'], - function(){ - watchTask('bundle.system'); - - watch('demos/**/*', function(file) { - gulp.start('build.demos'); - }); - - done(); - } - ); -}); - -/** - * Copies bundled demos from dist/demos to ../ionic-site/docs/v2 (assumes there is a - * sibling repo to this one named 'ionic-site') - */ -gulp.task('demos', ['bundle.demos'], function() { - var merge = require('merge2'); - - var demosStream = gulp.src([ - 'dist/demos/**/*', - '!dist/demos/**/*.scss', - ]) - .pipe(gulp.dest(docsConfig.docsDest + '/demos/')); - - var cssStream = gulp.src('dist/bundles/**/*.css') - .pipe(gulp.dest(docsConfig.sitePath + '/dist/bundles')); - - return merge([demosStream, cssStream]); - }); - - /** - * Builds necessary files for each demo then bundles them using webpack. Unlike - * e2e tests, demos are bundled for performance (but have a slower build). - */ -gulp.task('bundle.demos', ['build.demos', 'transpile', 'copy.libs', 'sass', 'fonts'], function(done) { - var glob = require('glob'); - var webpack = require('webpack'); - var path = require('path'); - - return glob('dist/demos/**/index.js', function(err, files){ - var numTasks = files.length; - files.forEach(function(file){ - var config = require('./scripts/demos/webpack.config.js'); - - // add our bundle entry, removing previous if necessary - // since config is cached - if (config.entry.length > 3) { - config.entry.pop(); - } - config.entry.push('./' + file); - config.output = { - filename: path.dirname(file) + '/bundle.js' - } - - webpack(config, function(err, stats){ - var statsOptions = { - 'colors': true, - 'modules': false, - 'chunks': false, - 'exclude': ['node_modules'], - 'errorDetails': true - } - console.log(stats.toString(statsOptions)); - if (--numTasks === 0) done(); - }) - }) - - }); -}); - -/** - * Transpiles and copies Ionic demo sources to dist/demos. - */ -gulp.task('build.demos', function() { - var gulpif = require('gulp-if'); - var merge = require('merge2'); - var _ = require('lodash'); - var fs = require('fs'); - var VinylFile = require('vinyl'); - - var indexTemplateName = LOCAL_DEMOS ? 'index.template.dev.html' : 'index.template.html'; - var baseIndexTemplate = _.template(fs.readFileSync('scripts/demos/' + indexTemplateName))(); - - console.log(flags); - if (flags.production) { - buildDemoSass(true); - } else { - buildDemoSass(false); - } - - var tsResult = gulp.src(['demos/**/*.ts']) - .pipe(cache('demos.ts')) - .pipe(tsc(getTscOptions(), undefined, tscReporter)) - .on('error', function(error) { - console.log(error.message); - }) - .pipe(gulpif(/index.js$/, createIndexHTML())) //TSC changes .ts to .js - - var demoFiles = gulp.src([ - 'demos/**/*', - '!demos/**/*.ts' - ]) - .pipe(cache('demos.files')); - - return merge([ - tsResult, - demoFiles - ]) - .pipe(gulp.dest('dist/demos')) - .pipe(connect.reload()); - - function createIndexHTML() { - return through2.obj(function(file, enc, next) { - var indexTemplate = baseIndexTemplate; - var customTemplateFp = file.path.split('/').slice(0, -1).join('/') + '/index.html'; - if (fs.existsSync(customTemplateFp)) { - indexTemplate = _.template(fs.readFileSync(customTemplateFp))(); - } - this.push(new VinylFile({ - base: file.base, - contents: new Buffer(indexTemplate), - path: path.join(path.dirname(file.path), 'index.html'), - })); - next(null, file); - }); - } -}); - -function buildDemoSass(isProductionMode) { - var sass = require('gulp-sass'); - var autoprefixer = require('gulp-autoprefixer'); - var minifyCss = require('gulp-minify-css'); - var concat = require('gulp-concat'); - - var sassVars = isProductionMode ? 'demos/app.variables.production.scss': 'demos/app.variables.local.scss'; - - (function combineSass() { - gulp.src([ - sassVars, - 'demos/app.variables.scss', - 'demos/app.ios.scss' - ]) - .pipe(concat('output.ios.scss')) - .pipe(gulp.dest('demos/')) - - gulp.src([ - sassVars, - 'demos/app.variables.scss', - 'demos/app.md.scss' - ]) - .pipe(concat('output.md.scss')) - .pipe(gulp.dest('demos/')) - - gulp.src([ - sassVars, - 'demos/app.variables.scss', - 'demos/app.wp.scss' - ]) - .pipe(concat('output.wp.scss')) - .pipe(gulp.dest('demos/')) - - })(); - - gulp.src([ - 'demos/output.ios.scss', - 'demos/output.md.scss', - 'demos/output.wp.scss' - ]) - - .pipe(sass({ - includePaths: [__dirname + '/node_modules/ionicons/dist/scss/'], - }).on('error', sass.logError) - ) - .pipe(autoprefixer(buildConfig.autoprefixer)) - .pipe(gulp.dest('dist/demos/')) - .pipe(minifyCss()) - .pipe(rename({ extname: '.min.css' })) - .pipe(gulp.dest('dist/bundles/')); -} - - -/** - * Tests - */ -require('./scripts/snapshot/snapshot.task')(gulp, argv, buildConfig); - -// requires bundle.system to be run once -gulp.task('karma', ['tests'], function(done) { - var karma = require('karma').server; - karma.start({ - configFile: __dirname + '/scripts/karma/karma.conf.js' - }, function(result) { - if (result > 0) { - return done(new Error('Karma exited with an error')); - } - done(); - }); -}); - -gulp.task('karma-watch', ['watch.tests', 'bundle.system'], function() { - watchTask('bundle.system'); - var karma = require('karma').server; - return karma.start({ configFile: __dirname + '/scripts/karma/karma-watch.conf.js'}) -}); - - -/** - * Release - */ - - /** - * Builds Ionic sources to dist with typechecking and d.ts definitions, does - * some prerelease magic (see 'prepare') and copies npm package and tooling - * files to dist. - */ -gulp.task('prerelease', function(done){ - runSequence( - 'validate', - 'prepare', - 'package', - done - ); -}); - -/** - * Publishes to npm and creates a new tag and release on GitHub. - */ -gulp.task('release', ['publish.npm', 'publish.github']); - -/** - * Pulls latest, ensures there are no unstaged/uncommitted changes, updates - * package.json minor version and generates CHANGELOG for release. - */ -gulp.task('prepare', ['git-pull-latest'], function(){ - var semver = require('semver'); - var fs = require('fs'); - var changelog = require('gulp-conventional-changelog'); - - //Update package.json version - var packageJSON = require('./package.json'); - packageJSON.version = semver.inc(packageJSON.version, 'prerelease', 'beta'); - fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); - - //Update changelog - return gulp.src('./CHANGELOG.md') - .pipe(changelog({ - preset: 'angular' - })) - .pipe(gulp.dest('./')); -}); - - -gulp.task('git-pull-latest', function() { - var execSync = require('child_process').execSync; - var spawnSync = require('child_process').spawnSync; - - function fail(context, msg) { - // remove gulp's 'Finished 'task' after 10ms' message - context.removeAllListeners('task_stop'); - console.error('Prepare aborted.'); - console.error(msg); - } - - //Check for uncommitted changes - var gitStatusResult = execSync('git status --porcelain'); - if (gitStatusResult.toString().length > 0) { - return fail(this, 'You have uncommitted changes, please stash or commit them before running prepare'); - } - - //Pull latest - var gitPullResult = spawnSync('git', ['pull', 'origin', 'master']); - if (gitPullResult.status !== 0) { - fail('There was an error running \'git pull\':\n' + gitPullResult.stderr.toString()); - } -}); - -/** - * Copies npm package and tooling files to dist. - */ -gulp.task('package', function(done){ - var fs = require('fs'); - var distDir = 'dist'; - - gulp.src([ - 'scripts/npm/.npmignore', - 'scripts/npm/README.md', - '*tooling/**/*' - ]) - .pipe(gulp.dest(distDir)); - - var templatePackageJSON = require('./scripts/npm/package.json'); - var sourcePackageJSON = require('./package.json'); - var sourceDependencies = sourcePackageJSON.dependencies; - - // copy source package.json data to template - templatePackageJSON.version = sourcePackageJSON.version - templatePackageJSON.description = sourcePackageJSON.description - templatePackageJSON.keywords = sourcePackageJSON.keywords - - // copy source dependencies versions to the template's peerDependencies - // only copy dependencies that show up as peerDependencies in the template - for (var dependency in sourceDependencies) { - if (dependency in templatePackageJSON.peerDependencies) { - templatePackageJSON.peerDependencies[dependency] = sourceDependencies[dependency]; - } - } - - fs.writeFileSync(distDir + '/package.json', JSON.stringify(templatePackageJSON, null, 2)); - done(); -}); - -/** - * Creates a new tag and release on GitHub. - */ -gulp.task('publish.github', function(done){ - var changelog = require('conventional-changelog'); - var GithubApi = require('github'); - var packageJSON = require('./package.json'); - - var github = new GithubApi({ - version: '3.0.0' - }); - - github.authenticate({ - type: 'oauth', - token: process.env.GH_TOKEN - }); - - return changelog({ - preset: 'angular' - }) - .pipe(through2.obj(function(file, enc, cb){ - github.releases.createRelease({ - owner: 'driftyco', - repo: 'ionic', - target_commitish: 'master', - tag_name: 'v' + packageJSON.version, - name: packageJSON.version, - body: file.toString(), - prerelease: true - }, done); - })); -}); - -/** - * Publishes to npm. - */ -gulp.task('publish.npm', function(done) { - var spawn = require('child_process').spawn; - - var npmCmd = spawn('npm', ['publish', './dist']); - - npmCmd.stdout.on('data', function (data) { - console.log(data.toString()); - }); - - npmCmd.stderr.on('data', function (data) { - console.log('npm err: ' + data.toString()); - }); - - npmCmd.on('close', function() { - done(); - }); -}); - -/** - * Execute this task to validate current code and then - */ -gulp.task('publish.nightly', function(done){ - runSequence('git-pull-latest', 'validate', 'nightly', done); -}); - -/** - * Publishes a new tag to npm with a nightly tag. - * This will only update the dist package.json file. - */ -gulp.task('nightly', ['package'], function(done) { - var fs = require('fs'); - var spawn = require('child_process').spawn; - var packageJSON = require('./dist/package.json'); - - // Generate a unique id formatted from current timestamp - function createTimestamp() { - // YYYYMMDDHHMM - var d = new Date(); - return d.getUTCFullYear() + // YYYY - ('0' + (d.getUTCMonth() + 1)).slice(-2) + // MM - ('0' + (d.getUTCDate())).slice(-2) + // DD - ('0' + (d.getUTCHours())).slice(-2) + // HH - ('0' + (d.getUTCMinutes())).slice(-2); // MM - } - - /** - * Split the version on dash so that we can add nightly - * to all production, beta, and nightly releases - * 0.1.0 -becomes- 0.1.0-r8e7684t - * 0.1.0-beta.0 -becomes- 0.1.0-beta.0-r8e7684t - * 0.1.0-beta.0-t5678e3v -becomes- 0.1.0-beta.0-r8e7684t - */ - packageJSON.version = packageJSON.version.split('-') - .slice(0, 2) - .concat(createTimestamp()) - .join('-'); - - fs.writeFileSync('./dist/package.json', JSON.stringify(packageJSON, null, 2)); - - var npmCmd = spawn('npm', ['publish', '--tag=nightly', './dist']); - npmCmd.stdout.on('data', function (data) { - console.log(data.toString()); - }); - - npmCmd.stderr.on('data', function (data) { - console.log('npm err: ' + data.toString()); - }); - - npmCmd.on('close', function() { - done(); - }); -}); - -/** - * Build Ionic sources, with typechecking, .d.ts definition generation and debug - * statements removed. - */ -gulp.task('build.release', function(done){ - DEBUG = false; - TYPECHECK = true; - runSequence( - 'clean', - 'copy.libs', - ['bundle', 'bundle.es6', 'sass', 'fonts', 'copy.scss'], - done - ); -}); - - -/** - * Docs - */ -require('./scripts/docs/gulp-tasks')(gulp, flags) - -/** - * Tooling - */ -gulp.task('tooling', function(){ - gulp.src('*tooling/**/*') - .pipe(gulp.dest('dist')); - - watch('tooling/**/*', function(){ - gulp.src('*tooling/**/*') - .pipe(gulp.dest('dist')); - }) -}); - -/** - * Validate Task - * - This task - */ -gulp.task('validate', function(done) { - runSequence( - 'lint.scss', - 'tslint', - 'build.release', - 'karma', - done - ); -}); - - -/** - * TS LINT - */ -gulp.task('tslint', function() { - var tslint = require('gulp-tslint'); - return gulp.src([ - 'src/**/*.ts', - '!src/**/test/**/*', - ]).pipe(tslint()) - .pipe(tslint.report('verbose')); -}); +require('./scripts/gulp/gulpfile'); diff --git a/scripts/gulp/constants.ts b/scripts/gulp/constants.ts new file mode 100644 index 0000000000..c9e4438f9b --- /dev/null +++ b/scripts/gulp/constants.ts @@ -0,0 +1,40 @@ +import { join } from 'path'; + + +// Names +export const COMPONENTS_NAME = 'components'; +export const DIST_NAME = 'dist'; +export const E2E_NAME = 'e2e'; +export const PACKAGE_NAME = 'ionic-angular'; +export const SCRIPTS_NAME = 'scripts'; +export const BUILD_NAME = 'build'; +export const SRC_NAME = 'src'; +export const VENDOR_NAME = 'vendor'; +export const NODE_MODULES = 'node_modules'; +export const COMMONJS_MODULE = 'commonjs'; +export const ES_MODULE = 'es2015'; + + +// File Paths +export const PROJECT_ROOT = join(__dirname, '../..'); +export const DIST_ROOT = join(PROJECT_ROOT, DIST_NAME); +export const DIST_E2E_ROOT = join(DIST_ROOT, E2E_NAME); +export const DIST_E2E_COMPONENTS_ROOT = join(DIST_E2E_ROOT, COMPONENTS_NAME); +export const DIST_BUILD_ROOT = join(DIST_ROOT, PACKAGE_NAME); +export const DIST_BUILD_COMMONJS_ROOT = join(DIST_BUILD_ROOT, COMMONJS_MODULE); +export const DIST_VENDOR_ROOT = join(DIST_ROOT, VENDOR_NAME); +export const NODE_MODULES_ROOT = join(PROJECT_ROOT, NODE_MODULES); +export const SCRIPTS_ROOT = join(PROJECT_ROOT, SCRIPTS_NAME); +export const SRC_ROOT = join(PROJECT_ROOT, SRC_NAME); +export const SRC_COMPONENTS_ROOT = join(SRC_ROOT, COMPONENTS_NAME); + + +// NPM +export const NPM_VENDOR_FILES = [ + '@angular', 'core-js/client', 'rxjs', 'systemjs/dist', 'zone.js/dist' +]; + + +// SERVER +export const LOCAL_SERVER_PORT = 8080; + diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts new file mode 100644 index 0000000000..79a0f3b90d --- /dev/null +++ b/scripts/gulp/gulpfile.ts @@ -0,0 +1,9 @@ +import './tasks/build'; +import './tasks/clean'; +import './tasks/default'; +import './tasks/e2e'; +import './tasks/lint'; +import './tasks/release'; +import './tasks/snapshot'; +import './tasks/test'; +//import './tasks/theme'; diff --git a/scripts/gulp/tasks/build.ts b/scripts/gulp/tasks/build.ts new file mode 100644 index 0000000000..596d7e2df2 --- /dev/null +++ b/scripts/gulp/tasks/build.ts @@ -0,0 +1,51 @@ +import { COMMONJS_MODULE, DIST_BUILD_ROOT, DIST_BUILD_COMMONJS_ROOT, ES_MODULE } from '../constants'; +import { task } from 'gulp'; +import { copySourceToDest, copySwiperToPath, createTempTsConfig, deleteFiles, runNgc} from '../util'; + + +export function buildIonicAngularCommonJs(excludeSpec: boolean, done: Function) { + const stream = copySourceToDest(DIST_BUILD_COMMONJS_ROOT, excludeSpec, true); + stream.on('end', () => { + // the source files are copied, copy over a tsconfig from + createTempTsConfig(['./**/*.ts'], COMMONJS_MODULE, `${DIST_BUILD_COMMONJS_ROOT}/tsconfig.json`); + runNgc(`${DIST_BUILD_COMMONJS_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + + copySwiperToPath(`${DIST_BUILD_COMMONJS_ROOT}/components/slides`, COMMONJS_MODULE); + // clean up any .ts files that remain, as well as unneeded swiper stuff + deleteFiles([`${DIST_BUILD_COMMONJS_ROOT}/**/*.ts`, `!${DIST_BUILD_COMMONJS_ROOT}/**/*.ngfactory.ts`, `!${DIST_BUILD_COMMONJS_ROOT}/**/*.d.ts`], done); + }); + }); +} + +export function buildIonicAngularEsm(done: Function) { + const stream = copySourceToDest(DIST_BUILD_ROOT, true, true); + stream.on('end', () => { + // the source files are copied, copy over a tsconfig from + createTempTsConfig(['./**/*.ts'], ES_MODULE, `${DIST_BUILD_ROOT}/tsconfig.json`); + runNgc(`${DIST_BUILD_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + copySwiperToPath(`${DIST_BUILD_ROOT}/components/slides`, ES_MODULE); + // clean up any .ts files that remain + deleteFiles([`${DIST_BUILD_ROOT}/**/*.ts`, `!${DIST_BUILD_ROOT}/**/*.ngfactory.ts`, `!${DIST_BUILD_ROOT}/**/*.d.ts`], done); + }); + }); +} + +/* this task builds out the necessary stuff for karma */ +task('compile.karma', (done: Function) => { + buildIonicAngularCommonJs(false, done); +}); + +/* this task builds out the ionic-angular (commonjs and esm) directories for release */ +task('compile.release', (done: Function) => { + buildIonicAngularEsm(() => { + buildIonicAngularCommonJs(true, done); + }); +}); diff --git a/scripts/gulp/tasks/clean.ts b/scripts/gulp/tasks/clean.ts new file mode 100644 index 0000000000..44bea66c0c --- /dev/null +++ b/scripts/gulp/tasks/clean.ts @@ -0,0 +1,12 @@ +import { task } from 'gulp'; + + +task('clean', (done: () => void) => { + const del = require('del'); + del(['dist/**'], done); +}); + +task('clean.src', (done: () => void) => { + const del = require('del'); + del(['src/**/*.js', 'src/**/*.d.ts', '!src/components/slides/swiper-widget.*'], done); +}); diff --git a/scripts/gulp/tasks/default.ts b/scripts/gulp/tasks/default.ts new file mode 100644 index 0000000000..9a549dd381 --- /dev/null +++ b/scripts/gulp/tasks/default.ts @@ -0,0 +1,17 @@ +const gulp = require('gulp'); + + +gulp.task('default', help); + +gulp.task('help', help); + +function help() { + const taskList = Object.keys(gulp.tasks) + .filter(taskName => taskName !== 'default' && taskName !== 'help') + .sort() + .map(taskName => taskName = ' ' + taskName); + + console.log(taskList.join('\n')); +} + +gulp.task('validate', ['lint', 'test']); diff --git a/scripts/gulp/tasks/e2e.ts b/scripts/gulp/tasks/e2e.ts new file mode 100644 index 0000000000..4e5604d9ef --- /dev/null +++ b/scripts/gulp/tasks/e2e.ts @@ -0,0 +1,278 @@ +import { COMMONJS_MODULE, DIST_E2E_COMPONENTS_ROOT, DIST_E2E_ROOT, DIST_NAME, E2E_NAME, LOCAL_SERVER_PORT, PROJECT_ROOT, SCRIPTS_ROOT, SRC_COMPONENTS_ROOT, SRC_ROOT } from '../constants'; +import {dest, src, start, task} from 'gulp'; +import * as path from 'path'; +import * as fs from 'fs'; + +import { compileSass, copyFonts, createTempTsConfig, deleteFiles, runNgc, runWebpack, setSassIonicVersion, createTimestamp } from '../util'; + + + +task('e2e', e2eBuild); + +function e2eBuild(done: Function) { + const runSequence = require('run-sequence'); + runSequence('e2e.copySource', 'e2e.compileTests', 'e2e.copyExternalDependencies', 'e2e.sass', 'e2e.fonts', 'e2e.beforeWebpack', 'e2e.runWebpack', done); +} + +task('e2e.copyAndCompile', (done: Function) => { + const runSequence = require('run-sequence'); + runSequence('e2e.copySource', 'e2e.compileTests', 'e2e.beforeWebpack', 'e2e.runWebpack', done); +}); + +task('e2e.copySource', (done: Function) => { + const gulpif = require('gulp-if'); + const _ = require('lodash'); + const VinylFile = require('vinyl'); + const through2 = require('through2'); + const buildConfig = require('../../build/config'); + + const stream = src([`${SRC_ROOT}/**/*`, `!${SRC_ROOT}/**/*.spec.ts`]) + .pipe(gulpif(/app-module.ts$/, createIndexHTML())) + .pipe(gulpif(/e2e.ts$/, createPlatformTests())) + .pipe(dest(DIST_E2E_ROOT)); + + stream.on('end', done); + + function createIndexHTML() { + const indexTemplate = fs.readFileSync('scripts/e2e/index.html'); + const indexTs = fs.readFileSync('scripts/e2e/entry.ts'); + + return through2.obj(function(file, enc, next) { + this.push(new VinylFile({ + base: file.base, + contents: new Buffer(indexTemplate), + path: path.join(path.dirname(file.path), 'index.html'), + })); + this.push(new VinylFile({ + base: file.base, + contents: new Buffer(indexTs), + path: path.join(path.dirname(file.path), 'entry.ts'), + })); + next(null, file); + }); + } + + function createPlatformTests() { + let platforms = [ + 'android', + 'ios', + 'windows' + ]; + + let testTemplate = _.template(fs.readFileSync('scripts/e2e/e2e.template.js')); + + return through2.obj(function(file, enc, next) { + let self = this; + let relativePath = path.dirname(file.path.replace(/^.*?src(\/|\\)components(\/|\\)/, '')); + + let contents = file.contents.toString(); + platforms.forEach(function(platform) { + let platformContents = testTemplate({ + contents: contents, + buildConfig: buildConfig, + relativePath: relativePath, + platform: platform + }); + self.push(new VinylFile({ + base: file.base, + contents: new Buffer(platformContents), + path: file.path.replace(/e2e.ts$/, platform + '.e2e.js') + })); + }); + next(); + }); + } +}); + +task('e2e.compileTests', (done: Function) => { + let folderInfo = getFolderInfo(); + buildE2ETests(folderInfo, done); +}); + +function buildE2ETests(folderInfo: any, done: Function) { + let includeGlob = ['./components/*/test/*/app-module.ts', './components/*/test/*/entry.ts']; + if (folderInfo.componentName && folderInfo.componentTest) { + includeGlob = [ + `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app-module.ts`, + `./components/${folderInfo.componentName}/test/${folderInfo.componentTest}/entry.ts`, + ]; + } + createTempTsConfig(includeGlob, COMMONJS_MODULE, `${DIST_E2E_ROOT}/tsconfig.json`); + runNgc(`${DIST_E2E_ROOT}/tsconfig.json`, (err) => { + if (err) { + done(err); + return; + } + // clean up any .ts files that remain + deleteFiles([`${DIST_E2E_ROOT}/**/*.ts`, `!${DIST_E2E_ROOT}/**/*.ngfactory.ts`, `!${DIST_E2E_ROOT}/**/*.d.ts`], done); + }); +} + +function getFolderInfo() { + const argv = require('yargs').argv; + let componentName: string = null; + let componentTest: string = null; + const folder: string = argv.folder || argv.f; + if (folder && folder.length) { + const folderSplit = folder.split('/'); + componentName = folderSplit[0]; + componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic'); + } + return { + componentName: componentName, + componentTest: componentTest + }; +} + +task('e2e.copyExternalDependencies', () => { + src([`${SCRIPTS_ROOT}/e2e/*.css`]).pipe(dest(`${DIST_E2E_ROOT}/css`)); +}); + +task('e2e.sass', () => { + // ensure there is a version.scss file + setSassIonicVersion(`E2E-${createTimestamp()}`); + return compileSass(`${DIST_E2E_ROOT}/css`); +}); + +task('e2e.fonts', () => { + return copyFonts(`${DIST_E2E_ROOT}/fonts`); +}); + +task('e2e.beforeWebpack', (done) => { + /** + * Find all AppModule.ts files because the act as the entry points + * for each e2e test. + */ + let glob = require('glob'); + let includeGlob = `${DIST_E2E_ROOT}/components/*/test/*/app-module.js`; + let folderInfo = getFolderInfo(); + if (folderInfo.componentName && folderInfo.componentTest) { + includeGlob = `${DIST_E2E_ROOT}/components/${folderInfo.componentName}/test/${folderInfo.componentTest}/app-module.js`; + } + glob(includeGlob, {}, function(er, files) { + var directories = files.map(function(file) { + return path.dirname(file); + }); + + var webpackEntryPoints = directories.reduce(function(endObj, dir) { + let relativePath = dir.replace(process.cwd() + '/', './'); + endObj[relativePath + '/index'] = relativePath + '/entry'; + return endObj; + }, {}); + + let indexFileContents = directories.map(function(dir) { + let testName = dir.replace(`${DIST_E2E_ROOT}/components/`, ''); + let fileName = dir.replace(`${PROJECT_ROOT}`, ''); + return `

${testName}

`; + }, []); + + fs.writeFileSync('./scripts/e2e/webpackEntryPoints.json', JSON.stringify(webpackEntryPoints, null, 2)); + fs.writeFileSync(`${DIST_E2E_ROOT}/index.html`, + '\n' + + indexFileContents.join('\n') + + '' + ); + done(); + }); +}); + +task('e2e.runWebpack', (done: Function) => { + const webpackConfigPath = `${SCRIPTS_ROOT}/e2e/webpack.config.js`; + runWebpack(webpackConfigPath, done); +}); + + +task('e2e.watch', ['e2e.copyExternalDependencies', 'e2e.sass', 'e2e.fonts'], (done: Function) => { + const folderInfo = getFolderInfo(); + if (! folderInfo.componentName || ! folderInfo.componentTest) { + done(new Error('Passing in a folder to watch is required for this command. Use the --folder or -f option.')); + return; + } + + const e2eTestPath = path.join(SRC_COMPONENTS_ROOT, folderInfo.componentName, 'test', folderInfo.componentTest, 'app-module.ts'); + + try { + fs.accessSync(e2eTestPath, fs.F_OK); + } catch (e) { + done(new Error(`Could not find e2e test: ${e2eTestPath}`)); + return; + } + + if (e2eComponentsExists()) { + // already generated the e2e directory + e2eWatch(folderInfo.componentName, folderInfo.componentTest); + + } else { + // generate the e2e directory + console.log('Generated e2e builds first...'); + e2eBuild(() => { + e2eWatch(folderInfo.componentName, folderInfo.componentTest); + }); + } +}); + +function e2eWatch(componentName: string, componentTest: string) { + const watch = require('gulp-watch'); + const webpack = require('webpack'); + const WebpackDevServer = require('webpack-dev-server'); + const config = require('../../e2e/webpack.config.js'); + + config.output.path = path.join(__dirname, '../../../'); + config.entry = { + 'dist/e2e/vendor': './scripts/e2e/vendor', + 'dist-e2e/polyfills': './scripts/e2e/polyfills' + }; + config.entry[`./dist/e2e/components/${componentName}/test/${componentTest}/index`] = `./dist/e2e/components/${componentName}/test/${componentTest}/entry`; + + const compiler = webpack(config); + + // If any tests change within components then run e2e.resources. + watch([ + 'src/components/*/test/**/*' + ], + function(file) { + console.log('start e2e.resources - ' + JSON.stringify(file.history, null, 2)); + start('e2e.copyAndCompile'); + }); + + // If any src files change except for tests then transpile only the source ionic files + watch([ + 'src/**/*.ts', + '!src/components/*/test/**/*', + '!src/util/test/*' + ], + function(file) { + console.log('start e2e.ngcSource - ' + JSON.stringify(file.history, null, 2)); + start('e2e.copyAndCompile'); + }); + + // If any scss files change then recompile all sass + watch(['src/**/*.scss'], (file) => { + console.log('start sass - ' + JSON.stringify(file.history, null, 2)); + start('e2e.sass'); + }); + + new WebpackDevServer(compiler, { + noInfo: true, + quiet: false, + watchOptions: { + aggregateTimeout: 2000, + poll: 1000 + } + }).listen(LOCAL_SERVER_PORT, 'localhost', function(err) { + if (err) { + throw err; + } + console.log(`http://localhost:${LOCAL_SERVER_PORT}/${DIST_NAME}/${E2E_NAME}/components/${componentName}/test/${componentTest}/`); + }); +} + +function e2eComponentsExists(): boolean { + try { + fs.accessSync(DIST_E2E_COMPONENTS_ROOT, fs.F_OK); + } catch (e) { + return false; + } + return true; +} + diff --git a/scripts/gulp/tasks/lint.ts b/scripts/gulp/tasks/lint.ts new file mode 100644 index 0000000000..f7820bc8d9 --- /dev/null +++ b/scripts/gulp/tasks/lint.ts @@ -0,0 +1,25 @@ +import { task, src } from 'gulp'; + + +task('lint', ['lint.sass', 'lint.ts']); + + +task('lint.ts', () => { + const tslint = require('gulp-tslint'); + return src([ + 'src/**/*.ts' + ]).pipe(tslint()) + .pipe(tslint.report('verbose')); +}); + + +task('lint.sass', function() { + const scsslint = require('gulp-scss-lint'); + return src([ + 'src/**/*.scss', + '!src/components/*/test/**/*', + '!src/util/test/*' + ]) + .pipe(scsslint()) + .pipe(scsslint.failReporter()); +}); diff --git a/scripts/gulp/tasks/release.ts b/scripts/gulp/tasks/release.ts new file mode 100644 index 0000000000..7ac8e7817d --- /dev/null +++ b/scripts/gulp/tasks/release.ts @@ -0,0 +1,119 @@ +import { dest, src, task } from 'gulp'; +import { DIST_BUILD_ROOT, SRC_ROOT, PROJECT_ROOT } from '../constants'; +import { compileSass, copyFonts, setSassIonicVersion, createTimestamp } from '../util'; + + +task('nightly', (done: Function) => { + const runSequence = require('run-sequence'); + runSequence('release.nightlyPackage', 'release.publishNightly', done); +}); + +task('release.prepareNightly', (done: Function) => { + const runSequence = require('run-sequence'); + runSequence(/*'release.pullLatest', 'validate', */'release.copyTools', 'release.copyNpmInfo', 'release.preparePackageJsonTemplate', 'release.nightlyPackageJson', done); +}); + +task('release.nightlyPackage', (done: Function) => { + const runSequence = require('run-sequence'); + runSequence('clean', /*'release.prepareNightly',*/ 'compile.release', 'release.prepareNightly', 'release.compileSass', 'release.fonts', 'release.scss', done); +}); + +task('release.publishNightly', (done: Function) => { + const spawn = require('child_process').spawn; + + const npmCmd = spawn('npm', ['publish', '--tag=nightly', DIST_BUILD_ROOT]); + npmCmd.stdout.on('data', function (data) { + console.log(data.toString()); + }); + + npmCmd.stderr.on('data', function (data) { + console.log('npm err: ' + data.toString()); + }); + + npmCmd.on('close', function() { + done(); + }); +}); + +task('release.compileSass', () => { + return compileSass(`${DIST_BUILD_ROOT}/css`); +}); + +task('release.fonts', () => { + return copyFonts(`${DIST_BUILD_ROOT}/fonts`); +}); + +task('release.scss', () => { + return src([`${SRC_ROOT}/**/*.scss`, `!${SRC_ROOT}/components/*/test/**/*`, `!${SRC_ROOT}/util/test/*`]).pipe(dest(`${DIST_BUILD_ROOT}`)); +}); + +task('release.pullLatest', (done: Function) => { + const gulpGit = require('gulp-git'); + gulpGit.pull('origin', ['master'], err => { + done(err); + }); +}); + +task('release.prepareChangelog', () => { + const changelog = require('gulp-conventional-changelog'); + return gulp.src(`${PROJECT_ROOT}/CHANGELOG.md`) + .pipe(changelog({ + preset: 'angular' + })) + .pipe(gulp.dest(`${PROJECT_ROOT}`)); +}); + + +task('release.prepareRootPackageJson', () => { + const semver = require('semver'); + const fs = require('fs'); + + let packageJSON = require('./package.json'); + packageJSON.version = semver.inc(packageJSON.version, 'prerelease', 'beta'); + fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); +}); + +task('release.copyTools', () => { + return src([`${PROJECT_ROOT}/tooling/**/*`]).pipe(dest(`${DIST_BUILD_ROOT}/tooling`)); +}); + +task('release.copyNpmInfo', () => { + return src([`${PROJECT_ROOT}/scripts/npm/.npmignore`, `${PROJECT_ROOT}/scripts/npm/README.md`]).pipe(dest(DIST_BUILD_ROOT)); +}); + +task('release.preparePackageJsonTemplate', () => { + const fs = require('fs'); + let templatePackageJSON = require(`${PROJECT_ROOT}/scripts/npm/package.json`); + const sourcePackageJSON = require(`${PROJECT_ROOT}/package.json`); + // copy source package.json data to template + templatePackageJSON.version = sourcePackageJSON.version; + templatePackageJSON.description = sourcePackageJSON.description; + templatePackageJSON.keywords = sourcePackageJSON.keywords; + + // copy source dependencies versions to the template's peerDependencies + // only copy dependencies that show up as peerDependencies in the template + for (let dependency in sourcePackageJSON.dependencies) { + // if the dependency is in both, AND the value of the entry is empty, copy it over + if (dependency in templatePackageJSON.dependencies && templatePackageJSON.dependencies[dependency] === '') { + templatePackageJSON.dependencies[dependency] = sourcePackageJSON.dependencies[dependency]; + } else if (dependency === 'rxjs') { + const value = sourcePackageJSON.dependencies[dependency]; + templatePackageJSON.dependencies['rxjs-es'] = value; + } + } + + fs.writeFileSync(`${DIST_BUILD_ROOT}` + '/package.json', JSON.stringify(templatePackageJSON, null, 2)); +}); + +task('release.nightlyPackageJson', () => { + const fs = require('fs'); + const packageJson: any = require(`${DIST_BUILD_ROOT}/package.json`); + + packageJson.version = packageJson.version.split('-') + .slice(0, 2) + .concat(createTimestamp()) + .join('-'); + + fs.writeFileSync(`${DIST_BUILD_ROOT}/package.json`, JSON.stringify(packageJson, null, 2)); + setSassIonicVersion(packageJson.version); +}); diff --git a/scripts/gulp/tasks/test.ts b/scripts/gulp/tasks/test.ts new file mode 100644 index 0000000000..dd22f38ee3 --- /dev/null +++ b/scripts/gulp/tasks/test.ts @@ -0,0 +1,64 @@ +import { DIST_VENDOR_ROOT, NPM_VENDOR_FILES, PROJECT_ROOT, SCRIPTS_ROOT } from '../constants'; +import path = require('path'); +import { dest, src, task } from 'gulp'; + + +task('test', ['test.assembleVendorJs', 'compile.karma'], (done: Function) => { + karmaTest(false, done); +}); + +task('test.watch', ['test.assembleVendorJs', 'compile.karma'], (done: Function) => { + karmaTest(true, done); +}); + +task('test.coverage', ['test.assembleVendorJs', 'compile.karma'], (done: Function) => { + karmaTest(false, () => { + createKarmaCoverageReport(done); + }); +}); + +function karmaTest(watch: boolean, done: Function) { + const karma = require('karma'); + const argv = require('yargs').argv; + + let karmaConfig = { + configFile: path.join(SCRIPTS_ROOT, 'karma/karma.conf.js'), + }; + + if (watch) { + (karmaConfig as any).singleRun = false; + } + + if (argv.testGrep) { + (karmaConfig).client = { + args: ['--grep', argv.testGrep] + }; + } + + new karma.Server(karmaConfig, done).start(); +} + + +task('test.assembleVendorJs', () => { + const files = NPM_VENDOR_FILES.map((root) => { + const glob = path.join(root, '**/*.+(js|js.map)'); + return src(path.join('node_modules', glob)) + .pipe(dest(path.join(DIST_VENDOR_ROOT, root))); + }); + const gulpMerge = require('merge2'); + return gulpMerge(files); +}); + + +/* creates a karma code coverage report */ +function createKarmaCoverageReport(done: Function) { + console.log('Generating Unit Test Coverage Report...'); + + let exec = require('child_process').exec; + let command = `node_modules/.bin/remap-istanbul -i coverage/coverage-final.json -o coverage -t html`; + + exec(command, function(err: any, stdout: any, stderr: any) { + console.log(`file://${PROJECT_ROOT}/coverage/index.html`); + done(err); + }); +} diff --git a/scripts/gulp/tasks/theme.ts b/scripts/gulp/tasks/theme.ts new file mode 100644 index 0000000000..27b0ea25fb --- /dev/null +++ b/scripts/gulp/tasks/theme.ts @@ -0,0 +1,117 @@ +import { task } from 'gulp'; +import { SRC_ROOT, SRC_COMPONENTS_ROOT } from '../constants'; +import * as path from 'path'; +import * as fs from 'fs'; + + + +task('theme', (done: () => void) => { + let opts: GenerateThemeOptions = { + src: path.join(SRC_COMPONENTS_ROOT), + dest: path.join(SRC_ROOT, 'ionic-generate.scss') + }; + generateThemeSource(opts); +}); + + +export function generateThemeSource(opts: GenerateThemeOptions) { + console.log(`[theme] src: ${opts.src}`); + console.log(`[theme] desc: ${opts.dest}`); + + let components = getSourceComponents(opts); + generateManifest(opts, components); +} + + +function generateManifest(opts: GenerateThemeOptions, components: Component[]) { + + components.forEach(c => { + console.log(c.name); + + c.modes.forEach(m => { + console.log(` ${m.mode} ${m.src}`); + }); + + }); + +} + + +function getSourceComponents(opts: GenerateThemeOptions) { + let components: Component[] = []; + + function readFiles(src: string, fillFiles: string[]) { + fs.readdirSync(src).forEach((file, index) => { + var filePath = path.join(src, file); + var fsStats = fs.statSync(filePath); + if (fsStats.isDirectory()) { + readFiles(filePath, fillFiles); + } else if (fsStats.isFile()) { + fillFiles.push(filePath); + } + }); + } + + let files: string[] = []; + readFiles(opts.src, files); + + files = files.filter(f => f.slice(-5) === '.scss'); + files.sort(); + + files.forEach(f => { + var componentRoot = f.replace(opts.src + '/', ''); + var fileSplit = componentRoot.split('/'); + var componentName = fileSplit[0]; + var fileName = fileSplit[1]; + + var component = components.find(c => c.name === componentName); + if (!component) { + component = { + name: componentName, + modes: [] + }; + components.push(component); + } + + fileSplit = fileName.split('.'); + if (fileSplit.length === 3) { + component.modes.push({ + src: f, + mode: fileSplit[1] + }); + + } else { + component.modes.unshift({ + src: f, + mode: DEFAULT_MODE + }); + } + + }); + + console.log(`[theme] components: ${components.length}`); + + return components; +} + + +export interface GenerateThemeOptions { + src: string; + dest: string; + includeModes?: string[]; + excludeModes?: string[]; + includeComponents?: string[]; + excludeComponents?: string[]; +} + +export interface Component { + name: string; + modes: FileDetails[]; +} + +export interface FileDetails { + src: string; + mode: string; +} + +const DEFAULT_MODE = '*'; diff --git a/scripts/gulp/tsconfig.json b/scripts/gulp/tsconfig.json new file mode 100644 index 0000000000..c528a2058d --- /dev/null +++ b/scripts/gulp/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "declaration": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es6", "es2015"], + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noImplicitAny": false, + "outDir": "../../dist/gulp", + "rootDir": ".", + "sourceMap": true, + "target": "es5", + "inlineSources": true, + "stripInternal": true, + "baseUrl": "", + "typeRoots": [ + "../../node_modules/@types" + ], + "types": [ + "node" + ] + } +} \ No newline at end of file diff --git a/scripts/gulp/util.ts b/scripts/gulp/util.ts new file mode 100644 index 0000000000..419fda8840 --- /dev/null +++ b/scripts/gulp/util.ts @@ -0,0 +1,175 @@ +import { COMMONJS_MODULE, ES_MODULE, NODE_MODULES_ROOT, PROJECT_ROOT, SRC_ROOT, SRC_COMPONENTS_ROOT } from './constants'; +import { src, dest } from 'gulp'; +import * as path from 'path'; +import * as fs from 'fs'; + +export function mergeObjects(obj1: any, obj2: any ) { + if (! obj1) { + obj1 = {}; + } + if (! obj2) { + obj2 = {}; + } + var obj3 = {}; + for (var attrname in obj1) { + (obj3)[attrname] = obj1[attrname]; + } + for (var attrname in obj2) { + (obj3)[attrname] = obj2[attrname]; + } + return obj3; +} + +function getRootTsConfig(): any { + const json = fs.readFileSync(`${PROJECT_ROOT}/tsconfig.json`); + + let tsConfig = JSON.parse(json.toString()); + return tsConfig; +} + +export function createTempTsConfig(includeGlob: string[], moduleType: String, pathToWriteFile: string): any { + let config = getRootTsConfig(); + if (!config.compilerOptions) { + config.compilerOptions = {}; + } + // for now, we only compiling to same directory (no outdir) + if (config.compilerOptions && config.compilerOptions.outDir) { + delete config.compilerOptions.outDir; + } + if (config.compilerOptions) { + config.compilerOptions.module = moduleType; + } + config.include = includeGlob; + let json = JSON.stringify(config, null, 2); + fs.writeFileSync(pathToWriteFile, json); +} + +export function copySourceToDest(destinationPath: string, excludeSpecs: boolean = true, excludeE2e: boolean = true) { + let glob = [`${SRC_ROOT}/**/*.ts`]; + if (excludeSpecs) { + glob.push(`!${SRC_ROOT}/**/*.spec.ts`); + } else { + glob.push(`${SRC_ROOT}/**/*.spec.ts`); + } + if (excludeE2e) { + glob.push(`!${SRC_ROOT}/components/*/test/*/*.ts`); + } + return src(glob) + .pipe(dest(destinationPath)); +} + +export function copyGlobToDest(sourceGlob: string[], destPath: string) { + return src(sourceGlob).pipe(dest(destPath)); +} + +export function copyFonts(destinationPath: string) { + return src([ + 'src/fonts/*.+(ttf|woff|woff2)', + 'node_modules/ionicons/dist/fonts/*.+(ttf|woff|woff2)' + ]) + .pipe(dest(destinationPath)); +} + +export function compileSass(destinationPath: string) { + let sass = require('gulp-sass'); + let autoprefixer = require('gulp-autoprefixer'); + let cleanCSS = require('gulp-clean-css'); + let rename = require('gulp-rename'); + let buildConfig = require('../build/config'); + + let ioniconsPath = path.join(NODE_MODULES_ROOT, 'ionicons/dist/scss/'); + + return src([ + path.join(SRC_ROOT, 'themes/ionic.build.default.scss'), + path.join(SRC_ROOT, 'themes/ionic.build.dark.scss') + ]) + .pipe(sass({ + includePaths: [ioniconsPath] + }).on('error', sass.logError) + ) + .pipe(autoprefixer(buildConfig.autoprefixer)) + + .pipe(rename(function (path) { + path.basename = path.basename.replace('.default', ''); + path.basename = path.basename.replace('.build', ''); + })) + + .pipe(dest(destinationPath)) + + .pipe(cleanCSS()) + + .pipe(rename({ + extname: '.min.css' + })) + + .pipe(dest(destinationPath)); +} + +export function setSassIonicVersion(version: string) { + fs.writeFileSync(path.join(SRC_ROOT, 'themes/version.scss'), `$ionic-version: "${version}";`); +} + +export function copyFile(srcPath: string, destPath: string) { + const sourceData = fs.readFileSync(srcPath); + fs.writeFileSync(destPath, sourceData); +} + +export function copySwiperToPath(distPath: string, moduleType: string) { + copyFile(`${SRC_COMPONENTS_ROOT}/slides/swiper-widget.d.ts`, `${distPath}/swiper-widget.d.ts`); + if (!moduleType || moduleType === COMMONJS_MODULE) { + copyFile(`${SRC_COMPONENTS_ROOT}/slides/swiper-widget.js`, `${distPath}/swiper-widget.js`); + } else if (moduleType === ES_MODULE) { + copyFile(`${SRC_COMPONENTS_ROOT}/slides/swiper-widget.es2015.js`, `${distPath}/swiper-widget.js`); + } else { + copyFile(`${SRC_COMPONENTS_ROOT}/slides/swiper-widget.system.js`, `${distPath}/swiper-widget.system.js`); + } +} + +export function runNgc(pathToConfigFile: string, done: Function) { + let exec = require('child_process').exec; + var shellCommand = `node --max_old_space_size=8096 ${PROJECT_ROOT}/node_modules/.bin/ngc -p ${pathToConfigFile}`; + + exec(shellCommand, function(err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + done(err); + }); +} + +export function runTsc(pathToConfigFile: string, done: Function) { + let exec = require('child_process').exec; + var shellCommand = `node --max_old_space_size=8096 ${PROJECT_ROOT}/node_modules/.bin/tsc -p ${pathToConfigFile}`; + + exec(shellCommand, function(err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + done(err); + }); +} + +export function runWebpack(pathToWebpackConfig: string, done: Function) { + let exec = require('child_process').exec; + let shellCommand = `node --max_old_space_size=8096 ./node_modules/.bin/webpack --config ${pathToWebpackConfig} --display-error-details`; + + exec(shellCommand, function(err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + done(err); + }); +} + +export function deleteFiles(glob: string[], done: Function) { + let del = require('del'); + del.sync(glob); + done(); +} + +export function createTimestamp() { + // YYYYMMDDHHMM + var d = new Date(); + return d.getUTCFullYear() + // YYYY + ('0' + (d.getUTCMonth() + 1)).slice(-2) + // MM + ('0' + (d.getUTCDate())).slice(-2) + // DD + ('0' + (d.getUTCHours())).slice(-2) + // HH + ('0' + (d.getUTCMinutes())).slice(-2); // MM +}