From b2dbad8c2940dc7507775bbeb9bb00b124b28bee Mon Sep 17 00:00:00 2001 From: jkuri Date: Fri, 11 Mar 2016 01:25:44 +0100 Subject: [PATCH 1/6] feat(install): 1. and 3. phase from design docs --- addon/ng2/blueprints/ng2/files/package.json | 2 +- .../ng2/files/src/config/system.config.js | 8 ++ addon/ng2/blueprints/ng2/files/src/index.html | 9 +- addon/ng2/commands/install.js | 39 +++++++++ addon/ng2/commands/uninstall.js | 39 +++++++++ addon/ng2/index.js | 4 +- addon/ng2/tasks/install.js | 82 +++++++++++++++++++ addon/ng2/tasks/typings-install.js | 16 ++++ addon/ng2/tasks/typings-uninstall.js | 16 ++++ addon/ng2/tasks/uninstall.js | 66 +++++++++++++++ addon/ng2/utilities/systemjs-helper.ts | 15 ++++ lib/broccoli/angular2-app.js | 41 ++++++++-- package.json | 3 +- tests/e2e/e2e_workflow.spec.js | 8 +- 14 files changed, 326 insertions(+), 22 deletions(-) create mode 100644 addon/ng2/blueprints/ng2/files/src/config/system.config.js create mode 100644 addon/ng2/commands/install.js create mode 100644 addon/ng2/commands/uninstall.js create mode 100644 addon/ng2/tasks/install.js create mode 100644 addon/ng2/tasks/typings-install.js create mode 100644 addon/ng2/tasks/typings-uninstall.js create mode 100644 addon/ng2/tasks/uninstall.js create mode 100644 addon/ng2/utilities/systemjs-helper.ts diff --git a/addon/ng2/blueprints/ng2/files/package.json b/addon/ng2/blueprints/ng2/files/package.json index 6a651e19e1c4..1ee59b875faa 100644 --- a/addon/ng2/blueprints/ng2/files/package.json +++ b/addon/ng2/blueprints/ng2/files/package.json @@ -35,6 +35,6 @@ "ts-node": "^0.5.5", "tslint": "^3.6.0", "typescript": "^1.8.7", - "typings": "^0.6.6" + "typings": "^0.7.7" } } diff --git a/addon/ng2/blueprints/ng2/files/src/config/system.config.js b/addon/ng2/blueprints/ng2/files/src/config/system.config.js new file mode 100644 index 000000000000..6096d7d11527 --- /dev/null +++ b/addon/ng2/blueprints/ng2/files/src/config/system.config.js @@ -0,0 +1,8 @@ +System.config({ + "packages": { + "app": { + "format": "register", + "defaultExtension": "js" + } + } +}); diff --git a/addon/ng2/blueprints/ng2/files/src/index.html b/addon/ng2/blueprints/ng2/files/src/index.html index 197237d92520..b864c0b8d0ef 100644 --- a/addon/ng2/blueprints/ng2/files/src/index.html +++ b/addon/ng2/blueprints/ng2/files/src/index.html @@ -32,15 +32,8 @@ + diff --git a/addon/ng2/commands/install.js b/addon/ng2/commands/install.js new file mode 100644 index 000000000000..97cec99fc09c --- /dev/null +++ b/addon/ng2/commands/install.js @@ -0,0 +1,39 @@ +/* jshint node: true, esnext: true */ +'use strict'; + +const Command = require('ember-cli/lib/models/command'); +const SilentError = require('silent-error'); +const Promise = require('ember-cli/lib/ext/promise'); +const InstallTask = require('../tasks/install'); + +module.exports = Command.extend({ + name: 'install', + description: 'Adds 3rd party library to existing project', + works: 'insideProject', + + availableOptions: [ + { name: 'typings', type: String, aliases: ['t'], description: 'Installs specified typings' } + ], + + run: function (commandOptions, rawArgs) { + if (!rawArgs.length) { + const msg = 'The `ng install` command must take an argument with ' + + 'a package name.'; + + return Promise.reject(new SilentError(msg)); + } + + const installTask = new InstallTask({ + ui: this.ui, + analytics: this.analytics, + project: this.project + }); + + return installTask.run({ + packages: rawArgs, + typings: commandOptions.typings || null + }); + } +}); + +module.exports.overrideCore = true; diff --git a/addon/ng2/commands/uninstall.js b/addon/ng2/commands/uninstall.js new file mode 100644 index 000000000000..295d3dc1e5ee --- /dev/null +++ b/addon/ng2/commands/uninstall.js @@ -0,0 +1,39 @@ +/* jshint node: true, esnext: true */ +'use strict'; + +const Command = require('ember-cli/lib/models/command'); +const SilentError = require('silent-error'); +const Promise = require('ember-cli/lib/ext/promise'); +const UninstallTask = require('../tasks/uninstall'); + +module.exports = Command.extend({ + name: 'uninstall', + description: 'Removes 3rd party library from existing project', + works: 'insideProject', + + availableOptions: [ + { name: 'typings', type: String, aliases: ['t'], description: 'Removes specified typings' } + ], + + run: function (commandOptions, rawArgs) { + if (!rawArgs.length) { + const msg = 'The `ng uninstall` command must take an argument with ' + + 'a package name.'; + + return Promise.reject(new SilentError(msg)); + } + + const uninstallTask = new UninstallTask({ + ui: this.ui, + analytics: this.analytics, + project: this.project + }); + + return uninstallTask.run({ + packages: rawArgs, + typings: commandOptions.typings || null + }); + } +}); + +module.exports.overrideCore = true; diff --git a/addon/ng2/index.js b/addon/ng2/index.js index d9c98fd81adc..3f75178c02c2 100644 --- a/addon/ng2/index.js +++ b/addon/ng2/index.js @@ -14,7 +14,9 @@ module.exports = { 'format': require('./commands/format'), 'version': require('./commands/version'), 'completion': require('./commands/completion'), - 'doc': require('./commands/doc') + 'doc': require('./commands/doc'), + 'install': require('./commands/install'), + 'uninstall': require('./commands/uninstall') }; } }; diff --git a/addon/ng2/tasks/install.js b/addon/ng2/tasks/install.js new file mode 100644 index 000000000000..e28a60d8f192 --- /dev/null +++ b/addon/ng2/tasks/install.js @@ -0,0 +1,82 @@ +/* jshint node: true, esnext: true */ +'use strict'; + +const Promise = require('ember-cli/lib/ext/promise'); +const Task = require('ember-cli/lib/models/task'); +const npmTask = require('ember-cli/lib/tasks/npm-task'); +const chalk = require('chalk'); +const path = require('path'); +const fs = require('fs'); +const fse = require('fs-extra'); +const packageJSON = path.resolve(process.cwd(), 'package.json'); +const nodeModules = path.resolve(process.cwd(), 'node_modules'); +const typingsInstall = require('./typings-install'); +const systemJS = require('../utilities/systemjs-helper.ts'); +const ts = require('typescript'); +const glob = require('glob'); +const _ = require('lodash'); + +module.exports = Task.extend({ + completionOKMessage: 'Successfully installed.', + completionErrorMessage: 'Error installing package.', + + run: function(options) { + this.packages = options.packages; + this.typings = options.typings; + + if (this.packages.indexOf('sass') !== -1) { + this.packages[this.packages.indexOf('sass')] = 'node-sass'; + } + + if (this.packages.indexOf('compass') !== -1) { + this.packages[this.packages.indexOf('compass')] = 'compass-importer'; + if (this.packages.indexOf('sass') === -1 || this.packages.indexOf('node-sass')) { + this.packages.push('node-sass'); + } + } + + return this.installProcedure(); + }, + + installProcedure: function() { + const NpmTask = new npmTask({ + command: 'install', + ui: this.ui, + analytics: this.analytics, + project: this.project, + startProgressMessage: `Installing packages: ${this.packages}`, + completionMessage: `Packages successfully installed.` + }); + + return NpmTask.run({ + packages: this.packages, + verbose: false, + }) + .then(() => this.storeInSystemJSConfig(this.packages)) + .then(() => { + const typings = this.typings; + if (typings) { + this.ui.startProgress(chalk.green(`Installing typings: ${typings}`), chalk.green(`.`)); + return typingsInstall(typings).then(() => { + this.ui.stopProgress(); + this.ui.writeLine(chalk.green(`Typings successfully installed.`)); + }); + } + }.bind(this)); + }, + + storeInSystemJSConfig: function(packages) { + const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); + let json = systemJS.loadSystemJson(systemPath); + + let mappings = json.map || {}; + packages.forEach(pkg => { + mappings[pkg] = `libs/${pkg}/${pkg}.js`; + }); + json.map = mappings; + systemJS.saveSystemJson(systemPath, json); + + return Promise.resolve(); + } + +}); diff --git a/addon/ng2/tasks/typings-install.js b/addon/ng2/tasks/typings-install.js new file mode 100644 index 000000000000..571180e30894 --- /dev/null +++ b/addon/ng2/tasks/typings-install.js @@ -0,0 +1,16 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +const Promise = require('ember-cli/lib/ext/promise'); +const exec = Promise.denodeify(require('child_process').exec); + +module.exports = function(pkg) { + let cmd = ['typings install']; + const options = [ + '--ambient', + '--save' + ]; + cmd.push(pkg); + + return exec(cmd.concat(options).join(' ')); +}; diff --git a/addon/ng2/tasks/typings-uninstall.js b/addon/ng2/tasks/typings-uninstall.js new file mode 100644 index 000000000000..4b4267119626 --- /dev/null +++ b/addon/ng2/tasks/typings-uninstall.js @@ -0,0 +1,16 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +const Promise = require('ember-cli/lib/ext/promise'); +const exec = Promise.denodeify(require('child_process').exec); + +module.exports = function(pkg) { + let cmd = ['typings uninstall']; + const options = [ + '--ambient', + '--save' + ]; + cmd.push(pkg); + + return exec(cmd.concat(options).join(' ')); +}; diff --git a/addon/ng2/tasks/uninstall.js b/addon/ng2/tasks/uninstall.js new file mode 100644 index 000000000000..b5f0449d3126 --- /dev/null +++ b/addon/ng2/tasks/uninstall.js @@ -0,0 +1,66 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +const Promise = require('ember-cli/lib/ext/promise'); +const Task = require('ember-cli/lib/models/task'); +const npmTask = require('ember-cli/lib/tasks/npm-task'); +const chalk = require('chalk'); +const path = require('path'); +const fs = require('fs'); +const fse = require('fs-extra'); +const exec = Promise.denodeify(require('child_process').exec); +const packageJSON = path.resolve(process.cwd(), 'package.json'); +const nodeModules = path.resolve(process.cwd(), 'node_modules'); +const typingsUninstall = require('./typings-uninstall'); +const systemJS = require('../utilities/systemjs-helper.ts'); + +module.exports = Task.extend({ + run: function(options) { + this.packages = options.packages; + this.typings = options.typings; + + return this.uninstallProcedure(); + }, + + uninstallProcedure: function() { + const NpmTask = new npmTask({ + command: 'uninstall', + ui: this.ui, + analytics: this.analytics, + project: this.project, + startProgressMessage: `Uninstalling packages: ${this.packages}`, + completionMessage: `Packages successfully uninstalled.` + }); + + return NpmTask.run({ + packages: this.packages, + verbose: false + }) + .then(() => this.removeFromSystemJSConfig(this.packages)) + .then(() => { + const typings = this.typings; + if (typings) { + this.ui.startProgress(chalk.green(`Uninstalling typings: ${typings}`), chalk.green(`.`)); + return typingsUninstall(typings).then(() => { + this.ui.stopProgress(); + this.ui.writeLine(chalk.green(`Typings successfully uninstalled.`)); + }); + } + }.bind(this)); + }, + + removeFromSystemJSConfig: function(packages) { + const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); + + let json = systemJS.loadSystemJson(systemPath); + let mappings = json.map || {}; + packages.forEach(pkg => { + delete mappings[pkg]; + }); + json.map = mappings; + systemJS.saveSystemJson(systemPath, json); + + return Promise.resolve(); + } + +}); diff --git a/addon/ng2/utilities/systemjs-helper.ts b/addon/ng2/utilities/systemjs-helper.ts new file mode 100644 index 000000000000..1b3e38f29495 --- /dev/null +++ b/addon/ng2/utilities/systemjs-helper.ts @@ -0,0 +1,15 @@ +/// +import fs = require('fs'); + +export function loadSystemJson(systemPath: string): { [name: string]: any } { + const systemContents: any = fs.readFileSync(systemPath, 'utf8'); + const jsonContent: any = systemContents.match(/^[^\{]*([\s\S]+)\);.*$/m)[1]; + + return JSON.parse(jsonContent); +} + +export function saveSystemJson(systemPath: string, json: { [name: string]: any }) { + const writeContents: any = 'System.config(' + JSON.stringify(json, null, '\t') + ');'; + + fs.writeFileSync(systemPath, writeContents, 'utf8'); +} diff --git a/lib/broccoli/angular2-app.js b/lib/broccoli/angular2-app.js index 22927ca5782d..947015ae292c 100644 --- a/lib/broccoli/angular2-app.js +++ b/lib/broccoli/angular2-app.js @@ -1,3 +1,8 @@ +'use strict'; + +process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; +require('module').Module._initPaths(); + var path = require('path'); var configReplace = require('./broccoli-config-replace'); var compileWithTypescript = require('./broccoli-typescript'); @@ -6,6 +11,10 @@ var fs = require('fs'); var Funnel = require('broccoli-funnel'); var mergeTrees = require('broccoli-merge-trees'); var Project = require('ember-cli/lib/models/project'); +var sass = require('./broccoli-sass'); +var less = require('./broccoli-less'); +var stylus = require('./broccoli-stylus'); +var compass = require('./broccoli-compass'); module.exports = Angular2App; @@ -59,6 +68,21 @@ Angular2App.prototype.toTree = function () { } }); + var tsconfig = JSON.parse(fs.readFileSync('src/tsconfig.json', 'utf-8')); + // Add all spec files to files. We need this because spec files are their own entry + // point. + fs.readdirSync(sourceDir).forEach(function addPathRecursive(name) { + const filePath = path.join(sourceDir, name); + if (filePath.match(/\.spec\.[jt]s$/)) { + tsconfig.files.push(name); + } else if (fs.statSync(filePath).isDirectory()) { + // Recursively call this function with the full sub-path. + fs.readdirSync(filePath).forEach(function(n) { + addPathRecursive(path.join(name, n)); + }); + } + }); + // Because the tsconfig does not include the source directory, add this as the first path // element. tsconfig.files = tsconfig.files.map(name => path.join(sourceDir, name)); @@ -72,14 +96,21 @@ Angular2App.prototype.toTree = function () { }); var jsTree = new Funnel(sourceDir, { - include: ['**/*.js'], - allowEmpty: true + include: ['**/*.js'], + allowEmpty: true }); var assetTree = new Funnel(sourceDir, { - include: ['**/*.*'], - exclude: ['**/*.ts', '**/*.js'], - allowEmpty: true + include: ['**/*.*'], + exclude: [ + '**/*.ts', + '**/*.js', + '**/*.scss', + '**/*.sass', + '**/*.less', + '**/*.styl' + ], + allowEmpty: true }); var vendorNpmTree = new Funnel('node_modules', { diff --git a/package.json b/package.json index cb7c3b048b10..1ec32860b2b2 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "keywords": [], "scripts": { "test": "node tests/runner", - "lint": "eslint -c eslint.json './**/*.js' " + "lint": "eslint -c eslint.json './**/*.js'", + "postinstall": "typings install" }, "repository": { "type": "git", diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index bd980e5fee7f..e487976f25c0 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -76,12 +76,8 @@ describe('Basic end-to-end Workflow', function () { it('Produces a service worker manifest after initial build', function () { var manifestPath = path.join(process.cwd(), 'dist', 'manifest.appcache'); expect(existsSync(manifestPath)).to.be.equal(true); - // Read the worker. - //TODO: Commenting this out because it makes eslint fail(need to figure out why this expect was commented out) - // var lines = fs.readFileSync(manifestPath, {encoding: 'utf8'}).trim().split('\n'); - - // Check that a few critical files have been detected. - // expect(lines).to.include(`${path.sep}index.html`); + var lines = fs.readFileSync(manifestPath, {encoding: 'utf8'}).trim(); + expect(lines).to.include(`${path.sep}index.html`); }); it('Perform `ng test` after initial build', function () { From d21635f33bb874e32dd33512dc362082adb5f75d Mon Sep 17 00:00:00 2001 From: jkuri Date: Thu, 24 Mar 2016 06:49:18 +0100 Subject: [PATCH 2/6] feat(build): broccoli sass plugin --- lib/broccoli/broccoli-sass.js | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/broccoli/broccoli-sass.js diff --git a/lib/broccoli/broccoli-sass.js b/lib/broccoli/broccoli-sass.js new file mode 100644 index 000000000000..9e1ee86bcf29 --- /dev/null +++ b/lib/broccoli/broccoli-sass.js @@ -0,0 +1,57 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('node-sass'); + + const Plugin = require('broccoli-caching-writer'); + const fs = require('fs'); + const fse = require('fs-extra'); + const path = require('path'); + const sass = require('node-sass'); + + class SASSPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).scss$/, /(.*?).sass$/] + }); + this.options = options; + this.fileRegistry = []; + } + + build() { + let entries = this.listEntries(); + let rootFileNames = entries.map(e => { + return path.resolve(e.basePath, e.relativePath); + }); + + rootFileNames.forEach(fileName => { + this.compile(fileName, this.inputPaths[0], this.outputPath); + }); + } + + compile(fileName, inputPath, outputPath) { + let sassOptions = { + file: path.join(fileName), + includePaths: this.inputPaths + }; + + let result = sass.renderSync(sassOptions); + let filePath = fileName.replace(inputPath, outputPath) + .replace(/\.scss$/, '.css') + .replace(/\.sass$/, '.css'); + + fse.outputFileSync(filePath, result.css, 'utf8'); + } + } + + module.exports = SASSPlugin; +} +catch (e) { + module.exports = null; +} From ef3334fa1fd78019a984ee836a506d97a91b0398 Mon Sep 17 00:00:00 2001 From: jkuri Date: Thu, 24 Mar 2016 06:49:34 +0100 Subject: [PATCH 3/6] feat(build): broccoli less plugin --- lib/broccoli/broccoli-less.js | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 lib/broccoli/broccoli-less.js diff --git a/lib/broccoli/broccoli-less.js b/lib/broccoli/broccoli-less.js new file mode 100644 index 000000000000..3125a48d4bd1 --- /dev/null +++ b/lib/broccoli/broccoli-less.js @@ -0,0 +1,67 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('less'); + + const Plugin = require('broccoli-caching-writer'); + const fs = require('fs'); + const fse = require('fs-extra'); + const path = require('path'); + const less = require('less'); + + class LESSPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).less$/] + }); + this.options = options; + this.fileRegistry = []; + } + + build() { + const that = this; + let entries = this.listEntries(); + let rootFileNames = entries.map(e => { + return path.resolve(e.basePath, e.relativePath); + }); + + let promises = []; + + rootFileNames.forEach(fileName => { + let p = new Promise(resolve => { + that.compile(fileName, that.inputPaths[0], that.outputPath) + .then(() => { + resolve(); + }); + }); + + promises.push(p); + }); + + return Promise.all(promises); + } + + compile(fileName, inputPath, outputPath) { + return new Promise(resolve => { + let content = fs.readFileSync(fileName, 'utf8'); + + less.render(content) + .then(output => { + let filePath = fileName.replace(inputPath, outputPath).replace(/\.less$/, '.css'); + fse.outputFileSync(filePath, output.css, 'utf8'); + resolve(); + }); + }); + } + } + + module.exports = LESSPlugin; +} catch (e) { + module.exports = null; +} From 33833b820764945b45ad61f15e94cd89ecc672ff Mon Sep 17 00:00:00 2001 From: jkuri Date: Thu, 24 Mar 2016 06:49:57 +0100 Subject: [PATCH 4/6] feat(build): broccoli stylus plugin --- lib/broccoli/broccoli-stylus.js | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 lib/broccoli/broccoli-stylus.js diff --git a/lib/broccoli/broccoli-stylus.js b/lib/broccoli/broccoli-stylus.js new file mode 100644 index 000000000000..e27b8fcbd577 --- /dev/null +++ b/lib/broccoli/broccoli-stylus.js @@ -0,0 +1,67 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('stylus'); + + const Plugin = require('broccoli-caching-writer'); + const fs = require('fs'); + const fse = require('fs-extra'); + const path = require('path'); + const stylus = require('stylus'); + + class StylusPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).styl$/] + }); + this.options = options; + this.fileRegistry = []; + } + + build() { + const that = this; + let entries = this.listEntries(); + let rootFileNames = entries.map(e => { + return path.resolve(e.basePath, e.relativePath); + }); + + let promises = []; + + rootFileNames.forEach(fileName => { + let p = new Promise(resolve => { + that.compile(fileName, that.inputPaths[0], that.outputPath) + .then(() => { + resolve(); + }); + }); + + promises.push(p); + }); + + return Promise.all(promises); + } + + compile(fileName, inputPath, outputPath) { + return new Promise(resolve => { + let content = fs.readFileSync(fileName, 'utf8'); + + stylus.render(content, { filename: path.basename(fileName) }, function(err, css) { + let filePath = fileName.replace(inputPath, outputPath).replace(/\.styl$/, '.css'); + fse.outputFileSync(filePath, css, 'utf8'); + resolve(); + }); + }); + } + } + + module.exports = StylusPlugin; +} +catch (e) { + module.exports = null; +} From de3c89b51f2e0a45d927ee46f3950d5139083b51 Mon Sep 17 00:00:00 2001 From: jkuri Date: Thu, 24 Mar 2016 06:50:19 +0100 Subject: [PATCH 5/6] feat(build): broccoli compass plugin --- lib/broccoli/broccoli-compass.js | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/broccoli/broccoli-compass.js diff --git a/lib/broccoli/broccoli-compass.js b/lib/broccoli/broccoli-compass.js new file mode 100644 index 000000000000..eb86c69380b7 --- /dev/null +++ b/lib/broccoli/broccoli-compass.js @@ -0,0 +1,61 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('node-sass'); + require.resolve('compass-importer'); + + const Plugin = require('broccoli-caching-writer'); + const fs = require('fs'); + const fse = require('fs-extra'); + const path = require('path'); + const sass = require('node-sass'); + const compass = require('compass-importer'); + + class CompassPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).scss$/, /(.*?).sass$/] + }); + this.options = options; + this.fileRegistry = []; + } + + build() { + let entries = this.listEntries(); + let rootFileNames = entries.map(e => { + return path.resolve(e.basePath, e.relativePath); + }); + + rootFileNames.forEach(fileName => { + this.compile(fileName, this.inputPaths[0], this.outputPath); + }); + } + + compile(fileName, inputPath, outputPath) { + let sassOptions = { + file: path.join(fileName), + includePaths: this.inputPaths, + data: '@import "compass"; .transition { @include transition(all); }', + importer: compass + }; + + let result = sass.renderSync(sassOptions); + let filePath = fileName.replace(inputPath, outputPath) + .replace(/\.scss$/, '.css') + .replace(/\.sass$/, '.css'); + + fse.outputFileSync(filePath, result.css, 'utf8'); + } + } + + module.exports = CompassPlugin; +} +catch (e) { + module.exports = null; +} From d00177cbc95df61738e0d594259fbcc093435818 Mon Sep 17 00:00:00 2001 From: jkuri Date: Thu, 24 Mar 2016 08:41:52 +0100 Subject: [PATCH 6/6] chore(test): e2e css preprocessor tests --- .eslintignore | 1 + addon/ng2/commands/{install.js => install.ts} | 17 +- .../commands/{uninstall.js => uninstall.ts} | 17 +- addon/ng2/tasks/install.js | 82 --------- addon/ng2/tasks/install.ts | 101 +++++++++++ addon/ng2/tasks/typings-install.js | 16 -- addon/ng2/tasks/typings-uninstall.js | 16 -- addon/ng2/tasks/uninstall.js | 66 ------- addon/ng2/tasks/uninstall.ts | 81 +++++++++ addon/ng2/utilities/systemjs-helper.ts | 3 +- ...compass.js => angular-broccoli-compass.js} | 40 +++-- ...ccoli-less.js => angular-broccoli-less.js} | 54 +++--- ...ccoli-sass.js => angular-broccoli-sass.js} | 33 +++- lib/broccoli/angular-broccoli-stylus.js | 68 ++++++++ lib/broccoli/angular2-app.js | 50 ++---- lib/broccoli/broccoli-stylus.js | 67 ------- package.json | 6 +- tests/e2e/e2e_workflow.spec.js | 165 ++++++++++++++++-- 18 files changed, 513 insertions(+), 370 deletions(-) create mode 100644 .eslintignore rename addon/ng2/commands/{install.js => install.ts} (56%) rename addon/ng2/commands/{uninstall.js => uninstall.ts} (56%) delete mode 100644 addon/ng2/tasks/install.js create mode 100644 addon/ng2/tasks/install.ts delete mode 100644 addon/ng2/tasks/typings-install.js delete mode 100644 addon/ng2/tasks/typings-uninstall.js delete mode 100644 addon/ng2/tasks/uninstall.js create mode 100644 addon/ng2/tasks/uninstall.ts rename lib/broccoli/{broccoli-compass.js => angular-broccoli-compass.js} (57%) rename lib/broccoli/{broccoli-less.js => angular-broccoli-less.js} (51%) rename lib/broccoli/{broccoli-sass.js => angular-broccoli-sass.js} (62%) create mode 100644 lib/broccoli/angular-broccoli-stylus.js delete mode 100644 lib/broccoli/broccoli-stylus.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000000..31a813082e9e --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +addon/ng2/blueprints/ng2/files/src/config/system.config.js diff --git a/addon/ng2/commands/install.js b/addon/ng2/commands/install.ts similarity index 56% rename from addon/ng2/commands/install.js rename to addon/ng2/commands/install.ts index 97cec99fc09c..6038e7f0b68f 100644 --- a/addon/ng2/commands/install.js +++ b/addon/ng2/commands/install.ts @@ -1,20 +1,12 @@ -/* jshint node: true, esnext: true */ -'use strict'; - -const Command = require('ember-cli/lib/models/command'); -const SilentError = require('silent-error'); -const Promise = require('ember-cli/lib/ext/promise'); -const InstallTask = require('../tasks/install'); +import * as Command from 'ember-cli/lib/models/command'; +import * as SlientError from 'silent-error'; +import * as InstallTask from '../tasks/install'; module.exports = Command.extend({ name: 'install', description: 'Adds 3rd party library to existing project', works: 'insideProject', - availableOptions: [ - { name: 'typings', type: String, aliases: ['t'], description: 'Installs specified typings' } - ], - run: function (commandOptions, rawArgs) { if (!rawArgs.length) { const msg = 'The `ng install` command must take an argument with ' + @@ -30,8 +22,7 @@ module.exports = Command.extend({ }); return installTask.run({ - packages: rawArgs, - typings: commandOptions.typings || null + packages: rawArgs }); } }); diff --git a/addon/ng2/commands/uninstall.js b/addon/ng2/commands/uninstall.ts similarity index 56% rename from addon/ng2/commands/uninstall.js rename to addon/ng2/commands/uninstall.ts index 295d3dc1e5ee..6c93eaec7d5d 100644 --- a/addon/ng2/commands/uninstall.js +++ b/addon/ng2/commands/uninstall.ts @@ -1,20 +1,12 @@ -/* jshint node: true, esnext: true */ -'use strict'; - -const Command = require('ember-cli/lib/models/command'); -const SilentError = require('silent-error'); -const Promise = require('ember-cli/lib/ext/promise'); -const UninstallTask = require('../tasks/uninstall'); +import * as Command from 'ember-cli/lib/models/command'; +import * as SilentError from 'silent-error'; +import * as UninstallTask from '../tasks/uninstall'; module.exports = Command.extend({ name: 'uninstall', description: 'Removes 3rd party library from existing project', works: 'insideProject', - availableOptions: [ - { name: 'typings', type: String, aliases: ['t'], description: 'Removes specified typings' } - ], - run: function (commandOptions, rawArgs) { if (!rawArgs.length) { const msg = 'The `ng uninstall` command must take an argument with ' + @@ -30,8 +22,7 @@ module.exports = Command.extend({ }); return uninstallTask.run({ - packages: rawArgs, - typings: commandOptions.typings || null + packages: rawArgs }); } }); diff --git a/addon/ng2/tasks/install.js b/addon/ng2/tasks/install.js deleted file mode 100644 index e28a60d8f192..000000000000 --- a/addon/ng2/tasks/install.js +++ /dev/null @@ -1,82 +0,0 @@ -/* jshint node: true, esnext: true */ -'use strict'; - -const Promise = require('ember-cli/lib/ext/promise'); -const Task = require('ember-cli/lib/models/task'); -const npmTask = require('ember-cli/lib/tasks/npm-task'); -const chalk = require('chalk'); -const path = require('path'); -const fs = require('fs'); -const fse = require('fs-extra'); -const packageJSON = path.resolve(process.cwd(), 'package.json'); -const nodeModules = path.resolve(process.cwd(), 'node_modules'); -const typingsInstall = require('./typings-install'); -const systemJS = require('../utilities/systemjs-helper.ts'); -const ts = require('typescript'); -const glob = require('glob'); -const _ = require('lodash'); - -module.exports = Task.extend({ - completionOKMessage: 'Successfully installed.', - completionErrorMessage: 'Error installing package.', - - run: function(options) { - this.packages = options.packages; - this.typings = options.typings; - - if (this.packages.indexOf('sass') !== -1) { - this.packages[this.packages.indexOf('sass')] = 'node-sass'; - } - - if (this.packages.indexOf('compass') !== -1) { - this.packages[this.packages.indexOf('compass')] = 'compass-importer'; - if (this.packages.indexOf('sass') === -1 || this.packages.indexOf('node-sass')) { - this.packages.push('node-sass'); - } - } - - return this.installProcedure(); - }, - - installProcedure: function() { - const NpmTask = new npmTask({ - command: 'install', - ui: this.ui, - analytics: this.analytics, - project: this.project, - startProgressMessage: `Installing packages: ${this.packages}`, - completionMessage: `Packages successfully installed.` - }); - - return NpmTask.run({ - packages: this.packages, - verbose: false, - }) - .then(() => this.storeInSystemJSConfig(this.packages)) - .then(() => { - const typings = this.typings; - if (typings) { - this.ui.startProgress(chalk.green(`Installing typings: ${typings}`), chalk.green(`.`)); - return typingsInstall(typings).then(() => { - this.ui.stopProgress(); - this.ui.writeLine(chalk.green(`Typings successfully installed.`)); - }); - } - }.bind(this)); - }, - - storeInSystemJSConfig: function(packages) { - const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); - let json = systemJS.loadSystemJson(systemPath); - - let mappings = json.map || {}; - packages.forEach(pkg => { - mappings[pkg] = `libs/${pkg}/${pkg}.js`; - }); - json.map = mappings; - systemJS.saveSystemJson(systemPath, json); - - return Promise.resolve(); - } - -}); diff --git a/addon/ng2/tasks/install.ts b/addon/ng2/tasks/install.ts new file mode 100644 index 000000000000..a9b9cc7a2ab3 --- /dev/null +++ b/addon/ng2/tasks/install.ts @@ -0,0 +1,101 @@ +import * as Task from 'ember-cli/lib/models/task'; +import * as npmTask from 'ember-cli/lib/tasks/npm-task'; +import * as chalk from 'chalk'; +import * as path from 'path'; +import { EventEmitter } from 'events'; +import * as search from 'typings-core/dist/search'; +import * as typings from 'typings-core/dist/install'; +import * as systemJS from '../utilities/systemjs-helper'; + +module.exports = Task.extend({ + completionOKMessage: 'Successfully installed.', + completionErrorMessage: 'Error installing package.', + + run: function(options) { + this.packages = options.packages; + + if (this.packages.indexOf('sass') !== -1) { + this.packages[this.packages.indexOf('sass')] = 'node-sass'; + } + + if (this.packages.indexOf('compass') !== -1) { + this.packages[this.packages.indexOf('compass')] = 'compass-importer'; + if (this.packages.indexOf('sass') === -1 || this.packages.indexOf('node-sass')) { + this.packages.push('node-sass'); + } + } + + return this.installProcedure(); + }, + + installProcedure: function() { + const that = this; + + const NpmTask = new npmTask({ + command: 'install', + ui: this.ui, + analytics: this.analytics, + project: this.project, + startProgressMessage: 'Installing packages: ' + this.packages, + completionMessage: 'Packages successfully installed.' + }); + + return NpmTask.run({ + packages: this.packages, + verbose: false + }) + .then(() => this.storeInSystemJSConfig(this.packages)) + .then(() => this.installTypings()); + }, + + installTypings: function() { + this.ui.startProgress(chalk.green('Searching and installing typings'), chalk.green('.')); + this.packages = this.packages.filter(p => !/node-sass|stylus|less|compass-importer/.test(p)); + + let typingsList = []; + + return Promise.all(this.packages.map(p => { + return new Promise(res => { + search.search({ name: p }).then(resp => { + if (resp.results.length) { + let names = resp.results.map(x => { + if (x.source === 'dt') { return x.name; } + }).filter(x => !!x); + typingsList = typingsList.concat(names); + } + res(); + }); + }); + })) + .then(() => { + return Promise.all(typingsList.map(t => { + return new Promise(res => { + let installOpts = { cwd: process.env.PWD, save: true, ambient: true }; + typings.installDependencyRaw(t, installOpts).then(() => { res(); }); + }); + })) + .then(() => { + return this.ui.stopProgress(); + }) + }); + }, + + storeInSystemJSConfig: function(packages) { + const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); + let json = systemJS.loadSystemJson(systemPath); + + packages = packages.filter(p => { + return (!/node-sass|stylus|less|compass-importer/.test(p)); + }); + + let mappings = json.map || {}; + packages.forEach(pkg => { + mappings[pkg] = 'libs/' + pkg + '/' + pkg + '.js'; + }); + json.map = mappings; + systemJS.saveSystemJson(systemPath, json); + + return Promise.resolve(); + } + +}); diff --git a/addon/ng2/tasks/typings-install.js b/addon/ng2/tasks/typings-install.js deleted file mode 100644 index 571180e30894..000000000000 --- a/addon/ng2/tasks/typings-install.js +++ /dev/null @@ -1,16 +0,0 @@ -/* jshint node: true, esversion: 6 */ -'use strict'; - -const Promise = require('ember-cli/lib/ext/promise'); -const exec = Promise.denodeify(require('child_process').exec); - -module.exports = function(pkg) { - let cmd = ['typings install']; - const options = [ - '--ambient', - '--save' - ]; - cmd.push(pkg); - - return exec(cmd.concat(options).join(' ')); -}; diff --git a/addon/ng2/tasks/typings-uninstall.js b/addon/ng2/tasks/typings-uninstall.js deleted file mode 100644 index 4b4267119626..000000000000 --- a/addon/ng2/tasks/typings-uninstall.js +++ /dev/null @@ -1,16 +0,0 @@ -/* jshint node: true, esversion: 6 */ -'use strict'; - -const Promise = require('ember-cli/lib/ext/promise'); -const exec = Promise.denodeify(require('child_process').exec); - -module.exports = function(pkg) { - let cmd = ['typings uninstall']; - const options = [ - '--ambient', - '--save' - ]; - cmd.push(pkg); - - return exec(cmd.concat(options).join(' ')); -}; diff --git a/addon/ng2/tasks/uninstall.js b/addon/ng2/tasks/uninstall.js deleted file mode 100644 index b5f0449d3126..000000000000 --- a/addon/ng2/tasks/uninstall.js +++ /dev/null @@ -1,66 +0,0 @@ -/* jshint node: true, esversion: 6 */ -'use strict'; - -const Promise = require('ember-cli/lib/ext/promise'); -const Task = require('ember-cli/lib/models/task'); -const npmTask = require('ember-cli/lib/tasks/npm-task'); -const chalk = require('chalk'); -const path = require('path'); -const fs = require('fs'); -const fse = require('fs-extra'); -const exec = Promise.denodeify(require('child_process').exec); -const packageJSON = path.resolve(process.cwd(), 'package.json'); -const nodeModules = path.resolve(process.cwd(), 'node_modules'); -const typingsUninstall = require('./typings-uninstall'); -const systemJS = require('../utilities/systemjs-helper.ts'); - -module.exports = Task.extend({ - run: function(options) { - this.packages = options.packages; - this.typings = options.typings; - - return this.uninstallProcedure(); - }, - - uninstallProcedure: function() { - const NpmTask = new npmTask({ - command: 'uninstall', - ui: this.ui, - analytics: this.analytics, - project: this.project, - startProgressMessage: `Uninstalling packages: ${this.packages}`, - completionMessage: `Packages successfully uninstalled.` - }); - - return NpmTask.run({ - packages: this.packages, - verbose: false - }) - .then(() => this.removeFromSystemJSConfig(this.packages)) - .then(() => { - const typings = this.typings; - if (typings) { - this.ui.startProgress(chalk.green(`Uninstalling typings: ${typings}`), chalk.green(`.`)); - return typingsUninstall(typings).then(() => { - this.ui.stopProgress(); - this.ui.writeLine(chalk.green(`Typings successfully uninstalled.`)); - }); - } - }.bind(this)); - }, - - removeFromSystemJSConfig: function(packages) { - const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); - - let json = systemJS.loadSystemJson(systemPath); - let mappings = json.map || {}; - packages.forEach(pkg => { - delete mappings[pkg]; - }); - json.map = mappings; - systemJS.saveSystemJson(systemPath, json); - - return Promise.resolve(); - } - -}); diff --git a/addon/ng2/tasks/uninstall.ts b/addon/ng2/tasks/uninstall.ts new file mode 100644 index 000000000000..48fc2afbdb69 --- /dev/null +++ b/addon/ng2/tasks/uninstall.ts @@ -0,0 +1,81 @@ +import * as Task from 'ember-cli/lib/models/task'; +import * as npmTask from 'ember-cli/lib/tasks/npm-task'; +import * as chalk from 'chalk'; +import * as path from 'path'; +import * as search from 'typings-core/dist/search'; +import * as typings from 'typings-core/dist/uninstall'; +import * as systemJS from '../utilities/systemjs-helper'; + +module.exports = Task.extend({ + run: function(options) { + this.packages = options.packages; + + return this.uninstallProcedure(); + }, + + uninstallProcedure: function() { + const that = this; + + const NpmTask = new npmTask({ + command: 'uninstall', + ui: this.ui, + analytics: this.analytics, + project: this.project, + startProgressMessage: 'Uninstalling packages: ' + this.packages, + completionMessage: 'Packages successfully uninstalled.' + }); + + return NpmTask.run({ + packages: this.packages, + verbose: false + }) + .then(() => this.removeFromSystemJSConfig(this.packages)) + .then(() => this.uninstallTypings()); + }, + + uninstallTypings: function() { + this.ui.startProgress(chalk.green('Uninstalling typings'), chalk.green('.')); + this.packages = this.packages.filter(p => !/node-sass|stylus|less|compass-importer/.test(p)); + let typingsList = []; + + return Promise.all(this.packages.map(p => { + return new Promise(res => { + search.search({ name: p }).then(resp => { + if (resp.results.length) { + let names = resp.results.map(x => { + if (x.source === 'dt') { return x.name; } + }).filter(x => !!x); + typingsList = typingsList.concat(names); + } + res(); + }); + }); + })) + .then(() => { + return Promise.all(typingsList.map(t => { + return new Promise(res => { + let installOpts = { cwd: process.env.PWD, save: true, ambient: true }; + typings.uninstallDependency(t, installOpts).then(() => { res(); }); + }); + })) + .then(() => { + return this.ui.stopProgress(); + }) + }); + }, + + removeFromSystemJSConfig: function(packages) { + const systemPath = path.resolve(process.cwd(), 'src', 'config', 'system.config.js'); + + let json = systemJS.loadSystemJson(systemPath); + let mappings = json.map || {}; + packages.forEach(pkg => { + delete mappings[pkg]; + }); + json.map = mappings; + systemJS.saveSystemJson(systemPath, json); + + return Promise.resolve(); + } + +}); diff --git a/addon/ng2/utilities/systemjs-helper.ts b/addon/ng2/utilities/systemjs-helper.ts index 1b3e38f29495..1855233d60c4 100644 --- a/addon/ng2/utilities/systemjs-helper.ts +++ b/addon/ng2/utilities/systemjs-helper.ts @@ -9,7 +9,8 @@ export function loadSystemJson(systemPath: string): { [name: string]: any } { } export function saveSystemJson(systemPath: string, json: { [name: string]: any }) { - const writeContents: any = 'System.config(' + JSON.stringify(json, null, '\t') + ');'; + const jsonContent: any = JSON.stringify(json, null, '\t'); + const writeContents: any = 'System.config(' + jsonContent + ');'; fs.writeFileSync(systemPath, writeContents, 'utf8'); } diff --git a/lib/broccoli/broccoli-compass.js b/lib/broccoli/angular-broccoli-compass.js similarity index 57% rename from lib/broccoli/broccoli-compass.js rename to lib/broccoli/angular-broccoli-compass.js index eb86c69380b7..297b3983bebc 100644 --- a/lib/broccoli/broccoli-compass.js +++ b/lib/broccoli/angular-broccoli-compass.js @@ -2,17 +2,27 @@ 'use strict'; try { - process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; - require('module').Module._initPaths(); - require.resolve('node-sass'); - require.resolve('compass-importer'); + let sass; + let compass; + + if (process.platform === 'win32') { + require.resolve(`${process.env.PWD}/node_modules/node-sass`); + require.resolve(`${process.env.PWD}/node_modules/compass-importer`); + sass = require(`${process.env.PWD}/node_modules/node-sass`); + compass = require(`${process.env.PWD}/node_modules/compass-importer`); + } else { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('node-sass'); + require.resolve('compass-importer'); + sass = require('node-sass'); + compass = require('compass-importer'); + } const Plugin = require('broccoli-caching-writer'); - const fs = require('fs'); const fse = require('fs-extra'); const path = require('path'); - const sass = require('node-sass'); - const compass = require('compass-importer'); + const Funnel = require('broccoli-funnel'); class CompassPlugin extends Plugin { constructor(inputNodes, options) { @@ -54,8 +64,16 @@ try { } } - module.exports = CompassPlugin; -} -catch (e) { - module.exports = null; + exports.makeBroccoliTree = (sourceDir) => { + let compassSrcTree = new Funnel(sourceDir, { + include: ['**/*.scss', '**/*.sass'], + allowEmpty: true + }); + + return new CompassPlugin([compassSrcTree]); + }; +} catch (e) { + exports.makeBroccoliTree = () => { + return null; + }; } diff --git a/lib/broccoli/broccoli-less.js b/lib/broccoli/angular-broccoli-less.js similarity index 51% rename from lib/broccoli/broccoli-less.js rename to lib/broccoli/angular-broccoli-less.js index 3125a48d4bd1..b2df55fadcbf 100644 --- a/lib/broccoli/broccoli-less.js +++ b/lib/broccoli/angular-broccoli-less.js @@ -2,15 +2,23 @@ 'use strict'; try { - process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; - require('module').Module._initPaths(); - require.resolve('less'); + let less; + + if (process.platform === 'win32') { + require.resolve(`${process.env.PWD}/node_modules/less`); + less = require(`${process.env.PWD}/node_modules/less`); + } else { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('less'); + less = require('less'); + } const Plugin = require('broccoli-caching-writer'); const fs = require('fs'); const fse = require('fs-extra'); const path = require('path'); - const less = require('less'); + const Funnel = require('broccoli-funnel'); class LESSPlugin extends Plugin { constructor(inputNodes, options) { @@ -25,43 +33,37 @@ try { } build() { - const that = this; let entries = this.listEntries(); let rootFileNames = entries.map(e => { return path.resolve(e.basePath, e.relativePath); }); - let promises = []; - - rootFileNames.forEach(fileName => { - let p = new Promise(resolve => { - that.compile(fileName, that.inputPaths[0], that.outputPath) - .then(() => { - resolve(); - }); - }); - - promises.push(p); - }); - - return Promise.all(promises); + return Promise.all(rootFileNames.map(fileName => { + return this.compile(fileName, this.inputPaths[0], this.outputPath); + })); } compile(fileName, inputPath, outputPath) { - return new Promise(resolve => { - let content = fs.readFileSync(fileName, 'utf8'); + let content = fs.readFileSync(fileName, 'utf8'); - less.render(content) + return less.render(content) .then(output => { let filePath = fileName.replace(inputPath, outputPath).replace(/\.less$/, '.css'); fse.outputFileSync(filePath, output.css, 'utf8'); - resolve(); }); - }); } } - module.exports = LESSPlugin; + exports.makeBroccoliTree = (sourceDir) => { + let lessSrcTree = new Funnel(sourceDir, { + include: ['**/*.less'], + allowEmpty: true + }); + + return new LESSPlugin([lessSrcTree]); + }; } catch (e) { - module.exports = null; + exports.makeBroccoliTree = () => { + return null; + }; } diff --git a/lib/broccoli/broccoli-sass.js b/lib/broccoli/angular-broccoli-sass.js similarity index 62% rename from lib/broccoli/broccoli-sass.js rename to lib/broccoli/angular-broccoli-sass.js index 9e1ee86bcf29..61e5c0fd1f51 100644 --- a/lib/broccoli/broccoli-sass.js +++ b/lib/broccoli/angular-broccoli-sass.js @@ -2,15 +2,22 @@ 'use strict'; try { - process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; - require('module').Module._initPaths(); - require.resolve('node-sass'); + let sass; + + if (process.platform === 'win32') { + require.resolve(`${process.env.PWD}/node_modules/node-sass`); + sass = require(`${process.env.PWD}/node_modules/node-sass`); + } else { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('node-sass'); + sass = require('node-sass'); + } const Plugin = require('broccoli-caching-writer'); - const fs = require('fs'); const fse = require('fs-extra'); const path = require('path'); - const sass = require('node-sass'); + const Funnel = require('broccoli-funnel'); class SASSPlugin extends Plugin { constructor(inputNodes, options) { @@ -50,8 +57,16 @@ try { } } - module.exports = SASSPlugin; -} -catch (e) { - module.exports = null; + exports.makeBroccoliTree = (sourceDir) => { + let sassSrcTree = new Funnel(sourceDir, { + include: ['**/*.sass', '**/*.scss'], + allowEmpty: true + }); + + return new SASSPlugin([sassSrcTree]); + }; +} catch (e) { + exports.makeBroccoliTree = () => { + return null; + }; } diff --git a/lib/broccoli/angular-broccoli-stylus.js b/lib/broccoli/angular-broccoli-stylus.js new file mode 100644 index 000000000000..b4f4c5d0fb3d --- /dev/null +++ b/lib/broccoli/angular-broccoli-stylus.js @@ -0,0 +1,68 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + let stylus; + + if (process.platform === 'win32') { + require.resolve(`${process.env.PWD}/node_modules/stylus`); + stylus = require(`${process.env.PWD}/node_modules/stylus`); + } else { + process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; + require('module').Module._initPaths(); + require.resolve('stylus'); + stylus = require('stylus'); + } + + const Plugin = require('broccoli-caching-writer'); + const fs = require('fs'); + const fse = require('fs-extra'); + const path = require('path'); + const Funnel = require('broccoli-funnel'); + + class StylusPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).styl$/] + }); + this.options = options; + this.fileRegistry = []; + } + + build() { + let entries = this.listEntries(); + let rootFileNames = entries.map(e => { + return path.resolve(e.basePath, e.relativePath); + }); + + return Promise.all(rootFileNames.map(fileName => { + return this.compile(fileName, this.inputPaths[0], this.outputPath); + })); + } + + compile(fileName, inputPath, outputPath) { + let content = fs.readFileSync(fileName, 'utf8'); + + return stylus.render(content, { filename: path.basename(fileName) }, function(err, css) { + let filePath = fileName.replace(inputPath, outputPath).replace(/\.styl$/, '.css'); + fse.outputFileSync(filePath, css, 'utf8'); + }); + } + } + + exports.makeBroccoliTree = (sourceDir) => { + let stylusSrcTree = new Funnel(sourceDir, { + include: ['**/*.styl'], + allowEmpty: true + }); + + return new StylusPlugin([stylusSrcTree]); + }; +} catch (e) { + exports.makeBroccoliTree = () => { + return null; + }; +} diff --git a/lib/broccoli/angular2-app.js b/lib/broccoli/angular2-app.js index 947015ae292c..66e05c310e1e 100644 --- a/lib/broccoli/angular2-app.js +++ b/lib/broccoli/angular2-app.js @@ -11,10 +11,6 @@ var fs = require('fs'); var Funnel = require('broccoli-funnel'); var mergeTrees = require('broccoli-merge-trees'); var Project = require('ember-cli/lib/models/project'); -var sass = require('./broccoli-sass'); -var less = require('./broccoli-less'); -var stylus = require('./broccoli-stylus'); -var compass = require('./broccoli-compass'); module.exports = Angular2App; @@ -68,21 +64,6 @@ Angular2App.prototype.toTree = function () { } }); - var tsconfig = JSON.parse(fs.readFileSync('src/tsconfig.json', 'utf-8')); - // Add all spec files to files. We need this because spec files are their own entry - // point. - fs.readdirSync(sourceDir).forEach(function addPathRecursive(name) { - const filePath = path.join(sourceDir, name); - if (filePath.match(/\.spec\.[jt]s$/)) { - tsconfig.files.push(name); - } else if (fs.statSync(filePath).isDirectory()) { - // Recursively call this function with the full sub-path. - fs.readdirSync(filePath).forEach(function(n) { - addPathRecursive(path.join(name, n)); - }); - } - }); - // Because the tsconfig does not include the source directory, add this as the first path // element. tsconfig.files = tsconfig.files.map(name => path.join(sourceDir, name)); @@ -96,21 +77,21 @@ Angular2App.prototype.toTree = function () { }); var jsTree = new Funnel(sourceDir, { - include: ['**/*.js'], - allowEmpty: true + include: ['**/*.js'], + allowEmpty: true }); var assetTree = new Funnel(sourceDir, { - include: ['**/*.*'], - exclude: [ - '**/*.ts', - '**/*.js', - '**/*.scss', - '**/*.sass', - '**/*.less', - '**/*.styl' - ], - allowEmpty: true + include: ['**/*.*'], + exclude: [ + '**/*.ts', + '**/*.js', + '**/*.scss', + '**/*.sass', + '**/*.less', + '**/*.styl' + ], + allowEmpty: true }); var vendorNpmTree = new Funnel('node_modules', { @@ -133,6 +114,13 @@ Angular2App.prototype.toTree = function () { })); } + allTrees = allTrees.concat( + require('./angular-broccoli-sass').makeBroccoliTree(sourceDir), + require('./angular-broccoli-less').makeBroccoliTree(sourceDir), + require('./angular-broccoli-stylus').makeBroccoliTree(sourceDir), + require('./angular-broccoli-compass').makeBroccoliTree(sourceDir) + ).filter(x => !!x); + var merged = mergeTrees(allTrees, { overwrite: true }); return mergeTrees([merged, new SwManifest([merged])]); diff --git a/lib/broccoli/broccoli-stylus.js b/lib/broccoli/broccoli-stylus.js deleted file mode 100644 index e27b8fcbd577..000000000000 --- a/lib/broccoli/broccoli-stylus.js +++ /dev/null @@ -1,67 +0,0 @@ -/* jshint node: true, esversion: 6 */ -'use strict'; - -try { - process.env.NODE_PATH += `:${process.env.PWD}/node_modules`; - require('module').Module._initPaths(); - require.resolve('stylus'); - - const Plugin = require('broccoli-caching-writer'); - const fs = require('fs'); - const fse = require('fs-extra'); - const path = require('path'); - const stylus = require('stylus'); - - class StylusPlugin extends Plugin { - constructor(inputNodes, options) { - super(inputNodes, {}); - - options = options || {}; - Plugin.call(this, inputNodes, { - cacheInclude: [/(.*?).styl$/] - }); - this.options = options; - this.fileRegistry = []; - } - - build() { - const that = this; - let entries = this.listEntries(); - let rootFileNames = entries.map(e => { - return path.resolve(e.basePath, e.relativePath); - }); - - let promises = []; - - rootFileNames.forEach(fileName => { - let p = new Promise(resolve => { - that.compile(fileName, that.inputPaths[0], that.outputPath) - .then(() => { - resolve(); - }); - }); - - promises.push(p); - }); - - return Promise.all(promises); - } - - compile(fileName, inputPath, outputPath) { - return new Promise(resolve => { - let content = fs.readFileSync(fileName, 'utf8'); - - stylus.render(content, { filename: path.basename(fileName) }, function(err, css) { - let filePath = fileName.replace(inputPath, outputPath).replace(/\.styl$/, '.css'); - fse.outputFileSync(filePath, css, 'utf8'); - resolve(); - }); - }); - } - } - - module.exports = StylusPlugin; -} -catch (e) { - module.exports = null; -} diff --git a/package.json b/package.json index 1ec32860b2b2..b46c26d5397c 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "keywords": [], "scripts": { "test": "node tests/runner", - "lint": "eslint -c eslint.json './**/*.js'", - "postinstall": "typings install" + "lint": "eslint -c eslint.json './**/*.js'" }, "repository": { "type": "git", @@ -50,7 +49,8 @@ "silent-error": "^1.0.0", "symlink-or-copy": "^1.0.1", "typescript": "^1.8.7", - "typings": "^0.7.9" + "typings": "^0.7.9", + "typings-core": "^0.2.14" }, "ember-addon": { "paths": [ diff --git a/tests/e2e/e2e_workflow.spec.js b/tests/e2e/e2e_workflow.spec.js index e487976f25c0..438ef33c097b 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -76,8 +76,10 @@ describe('Basic end-to-end Workflow', function () { it('Produces a service worker manifest after initial build', function () { var manifestPath = path.join(process.cwd(), 'dist', 'manifest.appcache'); expect(existsSync(manifestPath)).to.be.equal(true); - var lines = fs.readFileSync(manifestPath, {encoding: 'utf8'}).trim(); - expect(lines).to.include(`${path.sep}index.html`); + // Read the worker. + var lines = fs.readFileSync(manifestPath, 'utf8').trim(); + + expect(lines).to.include('index.html'); }); it('Perform `ng test` after initial build', function () { @@ -172,6 +174,136 @@ describe('Basic end-to-end Workflow', function () { }); }); + it('Installs sass support successfully via `ng install sass`', function() { + this.timeout(420000); + + return ng(['install', 'sass']) + .then(() => { + return ng(['generate', 'component', 'test-component']) + .then(() => { + let componentPath = path.join(process.cwd(), 'src', 'app', 'test-component'); + let cssFile = path.join(componentPath, 'test-component.css'); + let scssFile = path.join(componentPath, 'test-component.scss'); + + expect(existsSync(componentPath)).to.be.equal(true); + sh.mv(cssFile, scssFile); + expect(existsSync(scssFile)).to.be.equal(true); + expect(existsSync(cssFile)).to.be.equal(false); + let scssExample = '.outer {\n .inner { background: #fff; }\n }'; + fs.writeFileSync(scssFile, scssExample, 'utf8'); + + sh.exec('ng build --silent'); + let destCss = path.join(process.cwd(), 'dist', 'app', 'test-component', 'test-component.css'); + expect(existsSync(destCss)).to.be.equal(true); + let contents = fs.readFileSync(destCss, 'utf8'); + expect(contents).to.include('.outer .inner'); + }); + }); + }); + + it('Uninstalls sass support successfully via `ng uninstall sass`', function(done) { + this.timeout(420000); + + // We skip that test on Windows for now since there is some bug + // in `node-sass` bindings and cannot stop the process (EPERM) + // from the previous task + if (process.platform === 'win32') { + done(); + } else { + return ng(['uninstall', 'node-sass']) + .then(() => { + let sassPath = path.join(process.cwd(), 'node_modules', 'node-sass'); + expect(existsSync(sassPath)).to.be.equal(false); + return ng(['destroy', 'component', 'test-component']) + .then(() => { + sh.rm('-rf', path.join(process.cwd(), 'src', 'app', 'test-component')); + done(); + }); + }); + } + }); + + it('Installs less support successfully via `ng install less`', function() { + this.timeout(420000); + + return ng(['install', 'less']) + .then(() => { + return ng(['generate', 'component', 'test-component']) + .then(() => { + let componentPath = path.join(process.cwd(), 'src', 'app', 'test-component'); + let cssFile = path.join(componentPath, 'test-component.css'); + let lessFile = path.join(componentPath, 'test-component.less'); + + expect(existsSync(componentPath)).to.be.equal(true); + sh.mv(cssFile, lessFile); + expect(existsSync(lessFile)).to.be.equal(true); + expect(existsSync(cssFile)).to.be.equal(false); + let lessExample = '.outer {\n .inner { background: #fff; }\n }'; + fs.writeFileSync(lessFile, lessExample, 'utf8'); + + sh.exec('ng build --silent'); + let destCss = path.join(process.cwd(), 'dist', 'app', 'test-component', 'test-component.css'); + expect(existsSync(destCss)).to.be.equal(true); + let contents = fs.readFileSync(destCss, 'utf8'); + expect(contents).to.include('.outer .inner'); + }); + }); + }); + + it('Uninstalls less support successfully via `ng uninstall less`', function() { + this.timeout(420000); + + return ng(['uninstall', 'less']) + .then(() => { + let lessPath = path.join(process.cwd(), 'node_modules', 'less'); + expect(existsSync(lessPath)).to.be.equal(false); + return ng(['destroy', 'component', 'test-component']) + .then(() => { + sh.rm('-rf', path.join(process.cwd(), 'src', 'app', 'test-component')); + }); + }); + }); + + it('Installs stylus support successfully via `ng install stylus`', function() { + this.timeout(420000); + + return ng(['install', 'stylus']) + .then(() => { + return ng(['generate', 'component', 'test-component']) + .then(() => { + let componentPath = path.join(process.cwd(), 'src', 'app', 'test-component'); + let cssFile = path.join(componentPath, 'test-component.css'); + let stylusFile = path.join(componentPath, 'test-component.styl'); + + sh.mv(cssFile, stylusFile); + expect(existsSync(stylusFile)).to.be.equal(true); + expect(existsSync(cssFile)).to.be.equal(false); + let stylusExample = '.outer {\n .inner { background: #fff; }\n }'; + fs.writeFileSync(stylusFile, stylusExample, 'utf8'); + + sh.exec('ng build --silent'); + let destCss = path.join(process.cwd(), 'dist', 'app', 'test-component', 'test-component.css'); + expect(existsSync(destCss)).to.be.equal(true); + let contents = fs.readFileSync(destCss, 'utf8'); + expect(contents).to.include('.outer .inner'); + }); + }); + }); + + it('Uninstalls stylus support successfully via `ng uninstall stylus`', function() { + this.timeout(420000); + + return ng(['uninstall', 'stylus']) + .then(() => { + let stylusPath = path.join(process.cwd(), 'node_modules', 'stylus'); + expect(existsSync(stylusPath)).to.be.equal(false); + return ng(['destroy', 'component', 'test-component']) + .then(() => { + sh.rm('-rf', path.join(process.cwd(), 'src', 'app', 'test-component')); + }); + }); + }); + it('moves all files that live inside `public` into `dist`', function () { this.timeout(420000); @@ -189,7 +321,7 @@ describe('Basic end-to-end Workflow', function () { }); it('Turn on `noImplicitAny` in tsconfig.json and rebuild', function (done) { - this.timeout(420000); + this.timeout(4200000); const configFilePath = path.join(process.cwd(), 'src', 'tsconfig.json'); let config = require(configFilePath); @@ -199,19 +331,20 @@ describe('Basic end-to-end Workflow', function () { sh.rm('-rf', path.join(process.cwd(), 'dist')); - return ng(['build', '--silent']) - .then(function () { - expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); - }) - .catch(() => { - throw new Error('Build failed.'); - }) - .finally(function () { - // Clean `tmp` folder - process.chdir(path.resolve(root, '..')); - sh.rm('-rf', './tmp'); // tmp.teardown takes too long - done(); - }); + return ng([ + 'build', + '--silent' + ]).then(function() { + expect(existsSync(path.join(process.cwd(), 'dist'))).to.be.equal(true); + }).catch((err) => { + throw new Error('Build failed.', err); + }) + .finally(function () { + // Clean `tmp` folder + process.chdir(path.resolve(root, '..')); + sh.rm('-rf', './tmp'); // tmp.teardown takes too long + done(); + }); }); });