diff --git a/package-lock.json b/package-lock.json index 706b838fd76a..f067b57f493a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,14 +5,14 @@ "requires": true, "dependencies": { "@angular-devkit/architect": { - "version": "github:angular/angular-devkit-architect-builds#3ea1cf219eb913308da8a8a96e23abf65f535523", + "version": "github:angular/angular-devkit-architect-builds#a0080f89c91fdb1405be09109bad2f05c529787c", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "rxjs": "6.0.0-smoosh.2" }, "dependencies": { "@angular-devkit/core": { - "version": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "version": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", @@ -34,7 +34,7 @@ } }, "@angular-devkit/core": { - "version": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "version": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", @@ -56,15 +56,15 @@ } }, "@angular-devkit/schematics": { - "version": "github:angular/angular-devkit-schematics-builds#1397f3aeb8d2da1f88989a2413f4d82763ac2fc4", + "version": "github:angular/angular-devkit-schematics-builds#a5d4ee664144de524599fcfd82b3a75fd8fb2e9f", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "@ngtools/json-schema": "1.1.0", "rxjs": "6.0.0-smoosh.2" }, "dependencies": { "@angular-devkit/core": { - "version": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "version": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", @@ -91,15 +91,15 @@ "integrity": "sha1-w6DFRNYjkqzCgTpCyKDcb1j4aSI=" }, "@schematics/angular": { - "version": "github:angular/schematics-angular-builds#65313b642dfc9fb8394ac61be0aa997f49abd73f", + "version": "github:angular/schematics-angular-builds#3f942fe5eabf487f982b6d5ce02a03b3b7f17d17", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", - "@angular-devkit/schematics": "github:angular/angular-devkit-schematics-builds#1397f3aeb8d2da1f88989a2413f4d82763ac2fc4", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", + "@angular-devkit/schematics": "github:angular/angular-devkit-schematics-builds#a5d4ee664144de524599fcfd82b3a75fd8fb2e9f", "typescript": "2.6.2" }, "dependencies": { "@angular-devkit/core": { - "version": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "version": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", @@ -108,9 +108,9 @@ } }, "@angular-devkit/schematics": { - "version": "github:angular/angular-devkit-schematics-builds#1397f3aeb8d2da1f88989a2413f4d82763ac2fc4", + "version": "github:angular/angular-devkit-schematics-builds#a5d4ee664144de524599fcfd82b3a75fd8fb2e9f", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "@ngtools/json-schema": "1.1.0", "rxjs": "6.0.0-smoosh.2" } @@ -129,17 +129,17 @@ } }, "@schematics/update": { - "version": "github:angular/schematics-update-builds#1b91cc652817f975792a8fc24e8712f91f4604f4", + "version": "github:angular/schematics-update-builds#5bb7137948593afc5275a3403290789eed570bc8", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", - "@angular-devkit/schematics": "github:angular/angular-devkit-schematics-builds#1397f3aeb8d2da1f88989a2413f4d82763ac2fc4", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", + "@angular-devkit/schematics": "github:angular/angular-devkit-schematics-builds#a5d4ee664144de524599fcfd82b3a75fd8fb2e9f", "rxjs": "6.0.0-smoosh.2", "semver": "5.5.0", "semver-intersect": "1.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "version": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", @@ -148,9 +148,9 @@ } }, "@angular-devkit/schematics": { - "version": "github:angular/angular-devkit-schematics-builds#1397f3aeb8d2da1f88989a2413f4d82763ac2fc4", + "version": "github:angular/angular-devkit-schematics-builds#a5d4ee664144de524599fcfd82b3a75fd8fb2e9f", "requires": { - "@angular-devkit/core": "github:angular/angular-devkit-core-builds#d35b9ae56969ef21a4d0e8e5b307f25e89cdf3f6", + "@angular-devkit/core": "github:angular/angular-devkit-core-builds#5ad66cab1dbf1d699d2da15803460c3b861cac45", "@ngtools/json-schema": "1.1.0", "rxjs": "6.0.0-smoosh.2" } @@ -1447,6 +1447,11 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, + "ember-cli-string-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz", + "integrity": "sha1-ObZ3/CgF9VFzc1N2/O8njqpEUqE=" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1720,7 +1725,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -3970,8 +3975,7 @@ "lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", - "dev": true + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -4428,7 +4432,7 @@ }, "onetime": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "dev": true }, diff --git a/packages/@angular/cli/models/command-runner.ts b/packages/@angular/cli/models/command-runner.ts index 76bea2d1b326..c8ae1f6840fe 100644 --- a/packages/@angular/cli/models/command-runner.ts +++ b/packages/@angular/cli/models/command-runner.ts @@ -59,9 +59,58 @@ export async function runCommand(commandMap: CommandMap, } if (!Cmd) { - logger.error(tags.oneLine`The specified command (${commandName}) is invalid. - For a list of available options, run \`ng help\`.`); - throw ''; + function levenshtein(a: string, b: string): number { + if (a.length === 0) { + return b.length; + } + if (b.length === 0) { + return a.length; + } + + if (a.length > b.length) { + let tmp = a; + a = b; + b = tmp; + } + + const row = Array(a.length); + for (let i = 0; i <= a.length; i++) { + row[i] = i; + } + + let result: number; + for (let i = 1; i <= b.length; i++) { + result = i; + + for (let j = 1; j <= a.length; j++) { + let tmp = row[j - 1]; + row[j - 1] = result; + result = b[i - 1] === a[j - 1] + ? tmp + : Math.min(tmp + 1, Math.min(result + 1, row[j] + 1)); + } + } + + return result; + } + + const commandsDistance = {} as { [name: string]: number }; + const allCommands = listAllCommandNames(commandMap).sort((a, b) => { + if (!(a in commandsDistance)) { + commandsDistance[a] = levenshtein(a, commandName); + } + if (!(b in commandsDistance)) { + commandsDistance[b] = levenshtein(b, commandName); + } + return commandsDistance[a] - commandsDistance[b]; + }); + + throw new SilentError(tags.stripIndent` + The specified command ("${commandName}") is invalid. For a list of available options, + run "ng help". + + Did you mean "${allCommands[0]}"? + `); } const command = new Cmd(context, logger); @@ -167,8 +216,7 @@ export function parseOptions( } // Find a command. -function findCommand( - map: CommandMap, name: string): CommandConstructor | null { +function findCommand(map: CommandMap, name: string): CommandConstructor | null { let Cmd: CommandConstructor = map[name]; if (!Cmd) { @@ -194,6 +242,20 @@ function findCommand( return Cmd; } +function listAllCommandNames(map: CommandMap): string[] { + return Object.keys(map).concat( + Object.keys(map) + .reduce((acc, key) => { + if (!map[key].aliases) { + return acc; + } + + return acc.concat(map[key].aliases); + }, [] as string[]), + ); +} + + function verifyCommandInScope(command: Command, scope = CommandScope.everywhere): void { if (!command) { return;