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/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.ts b/addon/ng2/commands/install.ts new file mode 100644 index 000000000000..6038e7f0b68f --- /dev/null +++ b/addon/ng2/commands/install.ts @@ -0,0 +1,30 @@ +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', + + 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 + }); + } +}); + +module.exports.overrideCore = true; diff --git a/addon/ng2/commands/uninstall.ts b/addon/ng2/commands/uninstall.ts new file mode 100644 index 000000000000..6c93eaec7d5d --- /dev/null +++ b/addon/ng2/commands/uninstall.ts @@ -0,0 +1,30 @@ +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', + + 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 + }); + } +}); + +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.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/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 new file mode 100644 index 000000000000..1855233d60c4 --- /dev/null +++ b/addon/ng2/utilities/systemjs-helper.ts @@ -0,0 +1,16 @@ +/// +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 jsonContent: any = JSON.stringify(json, null, '\t'); + const writeContents: any = 'System.config(' + jsonContent + ');'; + + fs.writeFileSync(systemPath, writeContents, 'utf8'); +} diff --git a/lib/broccoli/angular-broccoli-compass.js b/lib/broccoli/angular-broccoli-compass.js new file mode 100644 index 000000000000..297b3983bebc --- /dev/null +++ b/lib/broccoli/angular-broccoli-compass.js @@ -0,0 +1,79 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + 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 fse = require('fs-extra'); + const path = require('path'); + const Funnel = require('broccoli-funnel'); + + 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'); + } + } + + 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/angular-broccoli-less.js b/lib/broccoli/angular-broccoli-less.js new file mode 100644 index 000000000000..b2df55fadcbf --- /dev/null +++ b/lib/broccoli/angular-broccoli-less.js @@ -0,0 +1,69 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + 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 Funnel = require('broccoli-funnel'); + + class LESSPlugin extends Plugin { + constructor(inputNodes, options) { + super(inputNodes, {}); + + options = options || {}; + Plugin.call(this, inputNodes, { + cacheInclude: [/(.*?).less$/] + }); + 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 less.render(content) + .then(output => { + let filePath = fileName.replace(inputPath, outputPath).replace(/\.less$/, '.css'); + fse.outputFileSync(filePath, output.css, 'utf8'); + }); + } + } + + exports.makeBroccoliTree = (sourceDir) => { + let lessSrcTree = new Funnel(sourceDir, { + include: ['**/*.less'], + allowEmpty: true + }); + + return new LESSPlugin([lessSrcTree]); + }; +} catch (e) { + exports.makeBroccoliTree = () => { + return null; + }; +} diff --git a/lib/broccoli/angular-broccoli-sass.js b/lib/broccoli/angular-broccoli-sass.js new file mode 100644 index 000000000000..61e5c0fd1f51 --- /dev/null +++ b/lib/broccoli/angular-broccoli-sass.js @@ -0,0 +1,72 @@ +/* jshint node: true, esversion: 6 */ +'use strict'; + +try { + 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 fse = require('fs-extra'); + const path = require('path'); + const Funnel = require('broccoli-funnel'); + + 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'); + } + } + + 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 22927ca5782d..66e05c310e1e 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'); @@ -78,7 +83,14 @@ Angular2App.prototype.toTree = function () { var assetTree = new Funnel(sourceDir, { include: ['**/*.*'], - exclude: ['**/*.ts', '**/*.js'], + exclude: [ + '**/*.ts', + '**/*.js', + '**/*.scss', + '**/*.sass', + '**/*.less', + '**/*.styl' + ], allowEmpty: true }); @@ -102,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/package.json b/package.json index cb7c3b048b10..b46c26d5397c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "keywords": [], "scripts": { "test": "node tests/runner", - "lint": "eslint -c eslint.json './**/*.js' " + "lint": "eslint -c eslint.json './**/*.js'" }, "repository": { "type": "git", @@ -49,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 bd980e5fee7f..438ef33c097b 100644 --- a/tests/e2e/e2e_workflow.spec.js +++ b/tests/e2e/e2e_workflow.spec.js @@ -77,11 +77,9 @@ describe('Basic end-to-end Workflow', 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'); + var lines = fs.readFileSync(manifestPath, 'utf8').trim(); - // Check that a few critical files have been detected. - // expect(lines).to.include(`${path.sep}index.html`); + expect(lines).to.include('index.html'); }); it('Perform `ng test` after initial build', function () { @@ -176,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); @@ -193,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); @@ -203,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(); + }); }); });