diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts index eeb4163d849a..e32ca9346643 100644 --- a/packages/@angular/cli/commands/build.ts +++ b/packages/@angular/cli/commands/build.ts @@ -28,7 +28,7 @@ export const baseBuildCommandOptions: any = [ { name: 'i18n-format', type: String }, { name: 'locale', type: String }, { name: 'extract-css', type: Boolean, aliases: ['ec'] }, - { name: 'watch', type: Boolean, aliases: ['w'] }, + { name: 'watch', type: Boolean, default: false, aliases: ['w'] }, { name: 'output-hashing', type: String, @@ -60,12 +60,6 @@ const BuildCommand = Command.extend({ run: function (commandOptions: BuildTaskOptions) { const project = this.project; - const additionalDefaults: any = { - watch: false - }; - - commandOptions = Object.assign({}, additionalDefaults, commandOptions); - // Check angular version. Version.assertAngularVersionIs2_3_1OrHigher(project.root); diff --git a/packages/@angular/cli/commands/e2e.ts b/packages/@angular/cli/commands/e2e.ts index 80432b0ec236..cc69ce9ff081 100644 --- a/packages/@angular/cli/commands/e2e.ts +++ b/packages/@angular/cli/commands/e2e.ts @@ -1,7 +1,9 @@ const SilentError = require('silent-error'); +import { overrideOptions } from '../utilities/override-options'; import { CliConfig } from '../models/config'; import { ServeTaskOptions, baseServeCommandOptions } from './serve'; +import { checkPort } from '../utilities/check-port'; const Command = require('../ember-cli/lib/models/command'); @@ -13,31 +15,27 @@ export interface E2eTaskOptions extends ServeTaskOptions { elementExplorer: boolean; } -export const e2eCommandOptions = baseServeCommandOptions.concat([ - { name: 'config', type: String, aliases: ['c'] }, - { name: 'specs', type: Array, default: [], aliases: ['sp'] }, - { name: 'element-explorer', type: Boolean, default: false, aliases: ['ee'] }, - { name: 'webdriver-update', type: Boolean, default: true, aliases: ['wu'] }, - { name: 'serve', type: Boolean, default: true, aliases: ['s'] } -]); - - const E2eCommand = Command.extend({ name: 'e2e', aliases: ['e'], description: 'Run e2e tests in existing project', works: 'insideProject', - availableOptions: e2eCommandOptions, + availableOptions: overrideOptions( + baseServeCommandOptions.concat([ + { name: 'config', type: String, aliases: ['c'] }, + { name: 'specs', type: Array, default: [], aliases: ['sp'] }, + { name: 'element-explorer', type: Boolean, default: false, aliases: ['ee'] }, + { name: 'webdriver-update', type: Boolean, default: true, aliases: ['wu'] }, + { name: 'serve', type: Boolean, default: true, aliases: ['s'] } + ]), [ + { name: 'port', default: 0 }, + { name: 'watch', default: false }, + ] + ), run: function (commandOptions: E2eTaskOptions) { const E2eTask = require('../tasks/e2e').E2eTask; this.project.ngConfig = this.project.ngConfig || CliConfig.fromProject(); - const additionalDefaults: any = { - watch: false - }; - - commandOptions = Object.assign({}, additionalDefaults, commandOptions); - const e2eTask = new E2eTask({ ui: this.ui, project: this.project @@ -72,7 +70,9 @@ const E2eCommand = Command.extend({ } } - serve.run(commandOptions, rebuildCb) + checkPort(commandOptions.port, commandOptions.host) + .then((port: number) => commandOptions.port = port) + .then(() => serve.run(commandOptions, rebuildCb)) .catch(reject); }); } else { diff --git a/packages/@angular/cli/commands/serve.ts b/packages/@angular/cli/commands/serve.ts index 5ddb9c36b770..53bc97583541 100644 --- a/packages/@angular/cli/commands/serve.ts +++ b/packages/@angular/cli/commands/serve.ts @@ -4,6 +4,8 @@ import { baseBuildCommandOptions } from './build'; import { CliConfig } from '../models/config'; import { Version } from '../upgrade/version'; import { ServeTaskOptions } from './serve'; +import { checkPort } from '../utilities/check-port'; +import { overrideOptions } from '../utilities/override-options'; const SilentError = require('silent-error'); const PortFinder = require('portfinder'); @@ -60,53 +62,52 @@ const ServeCommand = Command.extend({ description: 'Builds and serves your app, rebuilding on file changes.', aliases: ['server', 's'], - availableOptions: baseServeCommandOptions.concat([ - { name: 'live-reload', type: Boolean, default: true, aliases: ['lr'] }, - { - name: 'live-reload-host', - type: String, - aliases: ['lrh'], - description: 'Defaults to host' - }, - { - name: 'live-reload-base-url', - type: String, - aliases: ['lrbu'], - description: 'Defaults to baseURL' - }, - { - name: 'live-reload-port', - type: Number, - aliases: ['lrp'], - description: '(Defaults to port number within [49152...65535])' - }, - { - name: 'live-reload-live-css', - type: Boolean, - default: true, - description: 'Whether to live reload CSS (default true)' - }, - { - name: 'hmr', - type: Boolean, - default: false, - description: 'Enable hot module replacement', - } - ]), + availableOptions: overrideOptions( + baseServeCommandOptions.concat([ + { name: 'live-reload', type: Boolean, default: true, aliases: ['lr'] }, + { + name: 'live-reload-host', + type: String, + aliases: ['lrh'], + description: 'Defaults to host' + }, + { + name: 'live-reload-base-url', + type: String, + aliases: ['lrbu'], + description: 'Defaults to baseURL' + }, + { + name: 'live-reload-port', + type: Number, + aliases: ['lrp'], + description: '(Defaults to port number within [49152...65535])' + }, + { + name: 'live-reload-live-css', + type: Boolean, + default: true, + description: 'Whether to live reload CSS (default true)' + }, + { + name: 'hmr', + type: Boolean, + default: false, + description: 'Enable hot module replacement', + } + ]), [ + { name: 'watch', default: true }, + ] + ), run: function (commandOptions: ServeTaskOptions) { const ServeTask = require('../tasks/serve').default; - const additionalDefaults: any = { - watch: true - }; - - commandOptions = Object.assign({}, additionalDefaults, commandOptions); - Version.assertAngularVersionIs2_3_1OrHigher(this.project.root); commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host; - return checkExpressPort(commandOptions) + return checkPort(commandOptions.port, commandOptions.host) + .then((port: number) => commandOptions.port = port) .then(() => autoFindLiveReloadPort(commandOptions)) .then((opts: ServeTaskOptions) => { const serve = new ServeTask({ diff --git a/packages/@angular/cli/utilities/check-port.ts b/packages/@angular/cli/utilities/check-port.ts new file mode 100644 index 000000000000..91e0b3d986b4 --- /dev/null +++ b/packages/@angular/cli/utilities/check-port.ts @@ -0,0 +1,24 @@ +import * as denodeify from 'denodeify'; + +const SilentError = require('silent-error'); +const PortFinder = require('portfinder'); +const getPort = denodeify(PortFinder.getPort); + +PortFinder.basePort = 49152; + + +export function checkPort(port: number, host: string) { + return getPort({ port, host }) + .then((foundPort: number) => { + + // If the port isn't available and we weren't looking for any port, throw error. + if (port !== foundPort && port !== 0) { + throw new SilentError( + `Port ${port} is already in use. Use '--port' to specify a different port.` + ); + } + + // Otherwise, our found port is good. + return foundPort; + }); +} diff --git a/packages/@angular/cli/utilities/override-options.ts b/packages/@angular/cli/utilities/override-options.ts new file mode 100644 index 000000000000..4c027abc824c --- /dev/null +++ b/packages/@angular/cli/utilities/override-options.ts @@ -0,0 +1,12 @@ +const cloneDeep = require('lodash/cloneDeep'); + +export function overrideOptions(original: any[], overrides: any[]) { + let copy = cloneDeep(original); + overrides.forEach(override => { + const option = copy.find((opt: any) => opt.name == override.name); + if (option) { + Object.assign(option, override); + } + }); + return copy; +} diff --git a/tests/e2e/tests/test/e2e.ts b/tests/e2e/tests/test/e2e.ts index 772f7d587b30..197e7afaf017 100644 --- a/tests/e2e/tests/test/e2e.ts +++ b/tests/e2e/tests/test/e2e.ts @@ -2,6 +2,7 @@ import { ng, npm, execAndWaitForOutputToMatch, + silentExecAndWaitForOutputToMatch, killAllProcesses } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; @@ -40,6 +41,9 @@ export default function () { .then(() => killAllProcesses(), (err: any) => { killAllProcesses(); throw err; - }); - + }) + // Should run side-by-side with `ng serve` + .then(() => silentExecAndWaitForOutputToMatch('ng', ['serve'], + /webpack: Compiled successfully./)) + .then(() => ng('e2e')); }