@angular/cli 14.0.0-rc.0 → 14.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli/index.js CHANGED
@@ -73,7 +73,7 @@ async function default_1(options) {
73
73
  // Log nothing.
74
74
  }
75
75
  else {
76
- logger.fatal('An unexpected error occurred: ' + JSON.stringify(err));
76
+ logger.fatal(`An unexpected error occurred: ${'toString' in err ? err.toString() : JSON.stringify(err)}`);
77
77
  }
78
78
  return 1;
79
79
  }
@@ -742,6 +742,9 @@
742
742
  "path": {
743
743
  "type": "string",
744
744
  "format": "path",
745
+ "$default": {
746
+ "$source": "workingDirectory"
747
+ },
745
748
  "description": "The path at which to create the class, relative to the workspace root.",
746
749
  "visible": false
747
750
  },
@@ -773,6 +776,9 @@
773
776
  "path": {
774
777
  "type": "string",
775
778
  "format": "path",
779
+ "$default": {
780
+ "$source": "workingDirectory"
781
+ },
776
782
  "description": "The path at which to create the component file, relative to the current workspace. Default is a folder with the same name as the component in the project root.",
777
783
  "visible": false
778
784
  },
@@ -929,6 +935,9 @@
929
935
  "path": {
930
936
  "type": "string",
931
937
  "format": "path",
938
+ "$default": {
939
+ "$source": "workingDirectory"
940
+ },
932
941
  "description": "The path at which to create the interface that defines the directive, relative to the workspace root.",
933
942
  "visible": false
934
943
  },
@@ -1012,6 +1021,9 @@
1012
1021
  "path": {
1013
1022
  "type": "string",
1014
1023
  "format": "path",
1024
+ "$default": {
1025
+ "$source": "workingDirectory"
1026
+ },
1015
1027
  "description": "The path at which to create the enum definition, relative to the current workspace.",
1016
1028
  "visible": false
1017
1029
  },
@@ -1057,6 +1069,9 @@
1057
1069
  "path": {
1058
1070
  "type": "string",
1059
1071
  "format": "path",
1072
+ "$default": {
1073
+ "$source": "workingDirectory"
1074
+ },
1060
1075
  "description": "The path at which to create the interface that defines the guard, relative to the current workspace.",
1061
1076
  "visible": false
1062
1077
  },
@@ -1106,6 +1121,9 @@
1106
1121
  "path": {
1107
1122
  "type": "string",
1108
1123
  "format": "path",
1124
+ "$default": {
1125
+ "$source": "workingDirectory"
1126
+ },
1109
1127
  "description": "The path at which to create the interceptor, relative to the workspace root.",
1110
1128
  "visible": false
1111
1129
  },
@@ -1147,6 +1165,9 @@
1147
1165
  "path": {
1148
1166
  "type": "string",
1149
1167
  "format": "path",
1168
+ "$default": {
1169
+ "$source": "workingDirectory"
1170
+ },
1150
1171
  "description": "The path at which to create the interface, relative to the workspace root.",
1151
1172
  "visible": false
1152
1173
  },
@@ -1236,6 +1257,9 @@
1236
1257
  "path": {
1237
1258
  "type": "string",
1238
1259
  "format": "path",
1260
+ "$default": {
1261
+ "$source": "workingDirectory"
1262
+ },
1239
1263
  "description": "The path at which to create the pipe, relative to the workspace root.",
1240
1264
  "visible": false
1241
1265
  },
@@ -1466,6 +1490,9 @@
1466
1490
  "path": {
1467
1491
  "type": "string",
1468
1492
  "format": "path",
1493
+ "$default": {
1494
+ "$source": "workingDirectory"
1495
+ },
1469
1496
  "description": "The path at which to create the interface that defines the resolver, relative to the current workspace.",
1470
1497
  "visible": false
1471
1498
  },
@@ -1495,7 +1522,9 @@
1495
1522
  },
1496
1523
  "path": {
1497
1524
  "type": "string",
1498
- "format": "path",
1525
+ "$default": {
1526
+ "$source": "workingDirectory"
1527
+ },
1499
1528
  "description": "The path at which to create the service, relative to the workspace root.",
1500
1529
  "visible": false
1501
1530
  },
@@ -1528,6 +1557,9 @@
1528
1557
  "path": {
1529
1558
  "type": "string",
1530
1559
  "format": "path",
1560
+ "$default": {
1561
+ "$source": "workingDirectory"
1562
+ },
1531
1563
  "description": "The path at which to create the worker file, relative to the current workspace.",
1532
1564
  "visible": false
1533
1565
  },
package/lib/init.js CHANGED
@@ -120,7 +120,7 @@ const version_1 = require("../src/utilities/version");
120
120
  });
121
121
  })
122
122
  .then((exitCode) => {
123
- process.exit(exitCode);
123
+ process.exitCode = exitCode;
124
124
  })
125
125
  .catch((err) => {
126
126
  // eslint-disable-next-line no-console
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/cli",
3
- "version": "14.0.0-rc.0",
3
+ "version": "14.0.0-rc.3",
4
4
  "description": "CLI tool for Angular",
5
5
  "main": "lib/cli/index.js",
6
6
  "bin": {
@@ -25,10 +25,10 @@
25
25
  },
26
26
  "homepage": "https://github.com/angular/angular-cli",
27
27
  "dependencies": {
28
- "@angular-devkit/architect": "0.1400.0-rc.0",
29
- "@angular-devkit/core": "14.0.0-rc.0",
30
- "@angular-devkit/schematics": "14.0.0-rc.0",
31
- "@schematics/angular": "14.0.0-rc.0",
28
+ "@angular-devkit/architect": "0.1400.0-rc.3",
29
+ "@angular-devkit/core": "14.0.0-rc.3",
30
+ "@angular-devkit/schematics": "14.0.0-rc.3",
31
+ "@schematics/angular": "14.0.0-rc.3",
32
32
  "@yarnpkg/lockfile": "1.1.0",
33
33
  "ansi-colors": "4.1.1",
34
34
  "debug": "4.3.4",
@@ -49,12 +49,12 @@
49
49
  "ng-update": {
50
50
  "migrations": "@schematics/angular/migrations/migration-collection.json",
51
51
  "packageGroup": {
52
- "@angular/cli": "14.0.0-rc.0",
53
- "@angular-devkit/architect": "0.1400.0-rc.0",
54
- "@angular-devkit/build-angular": "14.0.0-rc.0",
55
- "@angular-devkit/build-webpack": "0.1400.0-rc.0",
56
- "@angular-devkit/core": "14.0.0-rc.0",
57
- "@angular-devkit/schematics": "14.0.0-rc.0"
52
+ "@angular/cli": "14.0.0-rc.3",
53
+ "@angular-devkit/architect": "0.1400.0-rc.3",
54
+ "@angular-devkit/build-angular": "14.0.0-rc.3",
55
+ "@angular-devkit/build-webpack": "0.1400.0-rc.3",
56
+ "@angular-devkit/core": "14.0.0-rc.3",
57
+ "@angular-devkit/schematics": "14.0.0-rc.3"
58
58
  }
59
59
  },
60
60
  "engines": {
@@ -148,9 +148,8 @@ class CommandModule {
148
148
  * **Note:** This method should be called from the command bundler method.
149
149
  */
150
150
  addSchemaOptionsToCommand(localYargs, options) {
151
- const workingDir = (0, core_1.normalize)(path.relative(this.context.root, process.cwd()));
152
151
  for (const option of options) {
153
- const { default: defaultVal, positional, deprecated, description, alias, userAnalytics, type, hidden, name, choices, format, } = option;
152
+ const { default: defaultVal, positional, deprecated, description, alias, userAnalytics, type, hidden, name, choices, } = option;
154
153
  const sharedOptions = {
155
154
  alias,
156
155
  hidden,
@@ -160,10 +159,6 @@ class CommandModule {
160
159
  // This should only be done when `--help` is used otherwise default will override options set in angular.json.
161
160
  ...(this.context.args.options.help ? { default: defaultVal } : {}),
162
161
  };
163
- // Special case for schematics
164
- if (workingDir && format === 'path' && name === 'path' && hidden) {
165
- sharedOptions.default = workingDir;
166
- }
167
162
  if (positional === undefined) {
168
163
  localYargs = localYargs.option(core_1.strings.dasherize(name), {
169
164
  type,
@@ -40,6 +40,7 @@ exports.SchematicsCommandModule = exports.DEFAULT_SCHEMATICS_COLLECTION = void 0
40
40
  const core_1 = require("@angular-devkit/core");
41
41
  const schematics_1 = require("@angular-devkit/schematics");
42
42
  const tools_1 = require("@angular-devkit/schematics/tools");
43
+ const path_1 = require("path");
43
44
  const config_1 = require("../utilities/config");
44
45
  const memoize_1 = require("../utilities/memoize");
45
46
  const tty_1 = require("../utilities/tty");
@@ -119,8 +120,10 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
119
120
  engineHostCreator: (options) => new schematic_engine_host_1.SchematicEngineHost(options.resolvePaths),
120
121
  });
121
122
  workflow.registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
122
- workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName());
123
123
  workflow.registry.useXDeprecatedProvider((msg) => logger.warn(msg));
124
+ workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName());
125
+ const workingDir = (0, core_1.normalize)((0, path_1.relative)(this.context.root, process.cwd()));
126
+ workflow.registry.addSmartDefaultProvider('workingDirectory', () => workingDir === '' ? undefined : workingDir);
124
127
  let shouldReportAnalytics = true;
125
128
  workflow.engineHost.registerOptionsTransform(async (schematic, options) => {
126
129
  var _a;
@@ -134,6 +137,28 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
134
137
  schematic.name.replace(/\//g, '_'),
135
138
  ]);
136
139
  }
140
+ // TODO: The below should be removed in version 15 when we change 1P schematics to use the `workingDirectory smart default`.
141
+ // Handle `"format": "path"` options.
142
+ const schema = schematic === null || schematic === void 0 ? void 0 : schematic.schemaJson;
143
+ if (!options || !schema || !(0, core_1.isJsonObject)(schema)) {
144
+ return options;
145
+ }
146
+ if (!('path' in options && options['path'] === undefined)) {
147
+ return options;
148
+ }
149
+ const properties = schema === null || schema === void 0 ? void 0 : schema['properties'];
150
+ if (!properties || !(0, core_1.isJsonObject)(properties)) {
151
+ return options;
152
+ }
153
+ const property = properties['path'];
154
+ if (!property || !(0, core_1.isJsonObject)(property)) {
155
+ return options;
156
+ }
157
+ if (property['format'] === 'path' && !property['$default']) {
158
+ options['path'] = workingDir || undefined;
159
+ this.context.logger.warn(`The 'path' option in '${schematic === null || schematic === void 0 ? void 0 : schematic.schema}' is using deprecated behaviour.` +
160
+ `'workingDirectory' smart default provider should be used instead.`);
161
+ }
137
162
  return options;
138
163
  });
139
164
  if (options.interactive !== false && (0, tty_1.isTTY)()) {
@@ -209,16 +234,20 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
209
234
  }
210
235
  async getSchematicCollections() {
211
236
  var _a;
237
+ // Resolve relative collections from the location of `angular.json`
238
+ const resolveRelativeCollection = (collectionName) => collectionName.charAt(0) === '.'
239
+ ? (0, path_1.resolve)(this.context.root, collectionName)
240
+ : collectionName;
212
241
  const getSchematicCollections = (configSection) => {
213
242
  if (!configSection) {
214
243
  return undefined;
215
244
  }
216
245
  const { schematicCollections, defaultCollection } = configSection;
217
246
  if (Array.isArray(schematicCollections)) {
218
- return new Set(schematicCollections);
247
+ return new Set(schematicCollections.map((c) => resolveRelativeCollection(c)));
219
248
  }
220
249
  else if (typeof defaultCollection === 'string') {
221
- return new Set([defaultCollection]);
250
+ return new Set([resolveRelativeCollection(defaultCollection)]);
222
251
  }
223
252
  return undefined;
224
253
  };
@@ -275,11 +304,11 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
275
304
  if (err instanceof schematics_1.UnsuccessfulWorkflowExecution) {
276
305
  // "See above" because we already printed the error.
277
306
  logger.fatal('The Schematic workflow failed. See above.');
278
- return 1;
279
307
  }
280
308
  else {
281
- throw err;
309
+ logger.fatal(err.message);
282
310
  }
311
+ return 1;
283
312
  }
284
313
  finally {
285
314
  unsubscribe();
@@ -299,9 +328,9 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
299
328
  if (typeof defaultProjectName === 'string' && defaultProjectName) {
300
329
  if (!this.defaultProjectDeprecationWarningShown) {
301
330
  logger.warn(core_1.tags.oneLine `
302
- DEPRECATED: The 'defaultProject' workspace option has been deprecated.
303
- The project to use will be determined from the current working directory.
304
- `);
331
+ DEPRECATED: The 'defaultProject' workspace option has been deprecated.
332
+ The project to use will be determined from the current working directory.
333
+ `);
305
334
  this.defaultProjectDeprecationWarningShown = true;
306
335
  }
307
336
  return defaultProjectName;
@@ -41,6 +41,13 @@ Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your termi
41
41
 
42
42
  ${color_1.colors.yellow('source <(ng completion script)')}
43
43
  `.trim());
44
+ if ((await (0, completion_1.hasGlobalCliInstall)()) === false) {
45
+ this.context.logger.warn('Setup completed successfully, but there does not seem to be a global install of the' +
46
+ ' Angular CLI. For autocompletion to work, the CLI will need to be on your `$PATH`, which' +
47
+ ' is typically done with the `-g` flag in `npm install -g @angular/cli`.' +
48
+ '\n\n' +
49
+ 'For more information, see https://angular.io/cli/completion#global-install');
50
+ }
44
51
  return 0;
45
52
  }
46
53
  }
@@ -1,5 +1,70 @@
1
- To enable Bash and Zsh real-time type-ahead autocompletion, run
2
- `ng completion` and restart your terminal.
1
+ Setting up autocompletion configures your terminal, so pressing the `<TAB>` key while in the middle
2
+ of typing will display various commands and options available to you. This makes it very easy to
3
+ discover and use CLI commands without lots of memorization.
3
4
 
4
- Alternatively, append `source <(ng completion script)` to the appropriate `.bashrc`,
5
- `.bash_profile`, `.zshrc`, `.zsh_profile`, or `.profile` file.
5
+ ![A demo of Angular CLI autocompletion in a terminal. The user types several partial `ng` commands,
6
+ using autocompletion to finish several arguments and list contextual options.
7
+ ](generated/images/guide/cli/completion.gif)
8
+
9
+ ## Automated setup
10
+
11
+ The CLI should prompt and ask to set up autocompletion for you the first time you use it (v14+).
12
+ Simply answer "Yes" and the CLI will take care of the rest.
13
+
14
+ ```
15
+ $ ng serve
16
+ ? Would you like to enable autocompletion? This will set up your terminal so pressing TAB while typing Angular CLI commands will show possible options and autocomplete arguments. (Enabling autocompletion will modify configuration files in your home directory.) Yes
17
+ Appended `source <(ng completion script)` to `/home/my-username/.bashrc`. Restart your terminal or run:
18
+
19
+ source <(ng completion script)
20
+
21
+ to autocomplete `ng` commands.
22
+
23
+ # Serve output...
24
+ ```
25
+
26
+ If you already refused the prompt, it won't ask again. But you can run `ng completion` to
27
+ do the same thing automatically.
28
+
29
+ This modifies your terminal environment to load Angular CLI autocompletion, but can't update your
30
+ current terminal session. Either restart it or run `source <(ng completion script)` directly to
31
+ enable autocompletion in your current session.
32
+
33
+ Test it out by typing `ng ser<TAB>` and it should autocomplete to `ng serve`. Ambiguous arguments
34
+ will show all possible options and their documentation, such as `ng generate <TAB>`.
35
+
36
+ ## Manual setup
37
+
38
+ Some users may have highly customized terminal setups, possibly with configuration files checked
39
+ into source control with an opinionated structure. `ng completion` only ever appends Angular's setup
40
+ to an existing configuration file for your current shell, or creates one if none exists. If you want
41
+ more control over exactly where this configuration lives, you can manually set it up by having your
42
+ shell run at startup:
43
+
44
+ ```bash
45
+ source <(ng completion script)
46
+ ```
47
+
48
+ This is equivalent to what `ng completion` will automatically set up, and gives power users more
49
+ flexibility in their environments when desired.
50
+
51
+ ## Platform support
52
+
53
+ Angular CLI supports autocompletion for the Bash and Zsh shells on MacOS and Linux operating
54
+ systems.
55
+
56
+ Windows does not support autocompletion in native shells, such as Cmd and Powershell. However,
57
+ the Angular CLI supports Git Bash and
58
+ [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) using Bash or Zsh.
59
+
60
+ ## Global install
61
+
62
+ Autocompletion works by configuring your terminal to invoke the Angular CLI on startup to load the
63
+ setup script. This means the terminal must be able to find and execute the Angular CLI, typically
64
+ through a global install that places the binary on the user's `$PATH`. If you get
65
+ `command not found: ng`, make sure the CLI is installed globally which you can do with the `-g`
66
+ flag:
67
+
68
+ ```bash
69
+ npm install -g @angular/cli
70
+ ```
@@ -93,8 +93,8 @@ class DocCommandModule extends command_module_1.CommandModule {
93
93
  catch { }
94
94
  }
95
95
  await (0, open_1.default)(options.search
96
- ? `https://${domain}/api?query=${options.keyword}`
97
- : `https://${domain}/docs?search=${options.keyword}`);
96
+ ? `https://${domain}/docs?search=${options.keyword}`
97
+ : `https://${domain}/api?query=${options.keyword}`);
98
98
  }
99
99
  }
100
100
  exports.DocCommandModule = DocCommandModule;
@@ -122,7 +122,7 @@ class UpdateCommandModule extends command_module_1.CommandModule {
122
122
  alias: ['C'],
123
123
  default: false,
124
124
  })
125
- .check(({ packages, next, 'allow-dirty': allowDirty, 'migrate-only': migrateOnly }) => {
125
+ .check(({ packages, 'allow-dirty': allowDirty, 'migrate-only': migrateOnly }) => {
126
126
  const { logger } = this.context;
127
127
  // This allows the user to easily reset any changes from the update.
128
128
  if ((packages === null || packages === void 0 ? void 0 : packages.length) && !this.checkCleanGit()) {
@@ -137,9 +137,6 @@ class UpdateCommandModule extends command_module_1.CommandModule {
137
137
  if ((packages === null || packages === void 0 ? void 0 : packages.length) !== 1) {
138
138
  throw new command_module_1.CommandModuleError(`A single package must be specified when using the 'migrate-only' option.`);
139
139
  }
140
- if (next) {
141
- logger.warn(`'next' option has no effect when using 'migrate-only' option.`);
142
- }
143
140
  }
144
141
  return true;
145
142
  })
@@ -513,7 +510,23 @@ class UpdateCommandModule extends command_module_1.CommandModule {
513
510
  });
514
511
  }
515
512
  catch { }
516
- const installationSuccess = await this.context.packageManager.installAll(options.force ? ['--force'] : [], this.context.root);
513
+ let forceInstall = options.force;
514
+ // npm 7+ can fail due to it incorrectly resolving peer dependencies that have valid SemVer
515
+ // ranges during an update. Update will set correct versions of dependencies within the
516
+ // package.json file. The force option is set to workaround these errors.
517
+ // Example error:
518
+ // npm ERR! Conflicting peer dependency: @angular/compiler-cli@14.0.0-rc.0
519
+ // npm ERR! node_modules/@angular/compiler-cli
520
+ // npm ERR! peer @angular/compiler-cli@"^14.0.0 || ^14.0.0-rc" from @angular-devkit/build-angular@14.0.0-rc.0
521
+ // npm ERR! node_modules/@angular-devkit/build-angular
522
+ // npm ERR! dev @angular-devkit/build-angular@"~14.0.0-rc.0" from the root project
523
+ if (this.context.packageManager.name === workspace_schema_1.PackageManager.Npm &&
524
+ this.context.packageManager.version &&
525
+ semver.gte(this.context.packageManager.version, '7.0.0', { includePrerelease: true })) {
526
+ logVerbose('NPM 7+ detected -- enabling force option for package installation');
527
+ forceInstall = true;
528
+ }
529
+ const installationSuccess = await this.context.packageManager.installAll(forceInstall ? ['--force'] : [], this.context.root);
517
530
  if (!installationSuccess) {
518
531
  return 1;
519
532
  }
@@ -584,8 +597,9 @@ class UpdateCommandModule extends command_module_1.CommandModule {
584
597
  }
585
598
  }
586
599
  const result = await this.executeMigrations(workflow, migration.package, migrations, migration.from, migration.to, options.createCommits);
587
- if (!result) {
588
- return 0;
600
+ // A non-zero value is a failure for the package's migrations
601
+ if (result !== 0) {
602
+ return result;
589
603
  }
590
604
  }
591
605
  }
@@ -20,3 +20,11 @@ export declare function considerSettingUpAutocompletion(command: string, logger:
20
20
  * @return The full path of the configuration file modified.
21
21
  */
22
22
  export declare function initializeAutocomplete(): Promise<string>;
23
+ /**
24
+ * Returns whether the user has a global CLI install or `undefined` if this can't be determined.
25
+ * Execution from `npx` is *not* considered a global CLI install.
26
+ *
27
+ * This does *not* mean the current execution is from a global CLI install, only that a global
28
+ * install exists on the system.
29
+ */
30
+ export declare function hasGlobalCliInstall(): Promise<boolean | undefined>;
@@ -30,8 +30,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
30
30
  return result;
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.initializeAutocomplete = exports.considerSettingUpAutocompletion = void 0;
33
+ exports.hasGlobalCliInstall = exports.initializeAutocomplete = exports.considerSettingUpAutocompletion = void 0;
34
34
  const core_1 = require("@angular-devkit/core");
35
+ const child_process_1 = require("child_process");
35
36
  const fs_1 = require("fs");
36
37
  const path = __importStar(require("path"));
37
38
  const process_1 = require("process");
@@ -80,6 +81,13 @@ Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your termi
80
81
 
81
82
  ${color_1.colors.yellow(`source <(ng completion script)`)}
82
83
  `.trim());
84
+ if ((await hasGlobalCliInstall()) === false) {
85
+ logger.warn('Setup completed successfully, but there does not seem to be a global install of the' +
86
+ ' Angular CLI. For autocompletion to work, the CLI will need to be on your `$PATH`, which' +
87
+ ' is typically done with the `-g` flag in `npm install -g @angular/cli`.' +
88
+ '\n\n' +
89
+ 'For more information, see https://angular.io/cli/completion#global-install');
90
+ }
83
91
  // Save configuration to remember that the user was prompted.
84
92
  await setCompletionConfig({ ...completionConfig, prompted: true });
85
93
  return undefined;
@@ -136,6 +144,11 @@ async function shouldPromptForAutocompletionSetup(command, config) {
136
144
  if (!rcFiles) {
137
145
  return false; // Unknown shell.
138
146
  }
147
+ // Don't prompt if the user is missing a global CLI install. Autocompletion won't work after setup
148
+ // anyway and could be annoying for users running one-off commands via `npx` or using `npm start`.
149
+ if ((await hasGlobalCliInstall()) === false) {
150
+ return false;
151
+ }
139
152
  // Check each RC file if they already use `ng completion script` in any capacity and don't prompt.
140
153
  for (const rcFile of rcFiles) {
141
154
  const contents = await fs_1.promises.readFile(rcFile, 'utf-8').catch(() => undefined);
@@ -217,3 +230,53 @@ function getShellRunCommandCandidates(shell, home) {
217
230
  return undefined;
218
231
  }
219
232
  }
233
+ /**
234
+ * Returns whether the user has a global CLI install or `undefined` if this can't be determined.
235
+ * Execution from `npx` is *not* considered a global CLI install.
236
+ *
237
+ * This does *not* mean the current execution is from a global CLI install, only that a global
238
+ * install exists on the system.
239
+ */
240
+ async function hasGlobalCliInstall() {
241
+ var _a;
242
+ // List all binaries with the `ng` name on the user's `$PATH`.
243
+ const proc = (0, child_process_1.execFile)('which', ['-a', 'ng']);
244
+ let stdout = '';
245
+ (_a = proc.stdout) === null || _a === void 0 ? void 0 : _a.addListener('data', (content) => {
246
+ stdout += content;
247
+ });
248
+ const exitCode = await new Promise((resolve) => {
249
+ proc.addListener('exit', (exitCode) => {
250
+ resolve(exitCode);
251
+ });
252
+ });
253
+ switch (exitCode) {
254
+ case 0:
255
+ // Successfully listed all `ng` binaries on the `$PATH`. Look for at least one line which is a
256
+ // global install. We can't easily identify global installs, but local installs are typically
257
+ // placed in `node_modules/.bin` by NPM / Yarn. `npx` also currently caches files at
258
+ // `~/.npm/_npx/*/node_modules/.bin/`, so the same logic applies.
259
+ const lines = stdout.split('\n').filter((line) => line !== '');
260
+ const hasGlobalInstall = lines.some((line) => {
261
+ // A binary is a local install if it is a direct child of a `node_modules/.bin/` directory.
262
+ const parent = path.parse(path.parse(line).dir);
263
+ const grandparent = path.parse(parent.dir);
264
+ const localInstall = grandparent.base === 'node_modules' && parent.base === '.bin';
265
+ return !localInstall;
266
+ });
267
+ return hasGlobalInstall;
268
+ case 1:
269
+ // No instances of `ng` on the user's `$PATH`.
270
+ return false;
271
+ case null:
272
+ // `which` was killed by a signal and did not exit gracefully. Maybe it hung or something else
273
+ // went very wrong, so treat this as inconclusive.
274
+ return undefined;
275
+ default:
276
+ // `which` returns exit code 2 if an invalid option is specified and `-a` doesn't appear to be
277
+ // supported on all systems. Other exit codes mean unknown errors occurred. Can't tell whether
278
+ // CLI is globally installed, so treat this as inconclusive.
279
+ return undefined;
280
+ }
281
+ }
282
+ exports.hasGlobalCliInstall = hasGlobalCliInstall;
@@ -61,24 +61,20 @@ class PackageManagerUtils {
61
61
  /** Install a single package. */
62
62
  async install(packageName, save = true, extraArgs = [], cwd) {
63
63
  const packageManagerArgs = this.getArguments();
64
- const installArgs = [
65
- packageManagerArgs.install,
66
- packageName,
67
- packageManagerArgs.silent,
68
- ];
64
+ const installArgs = [packageManagerArgs.install, packageName];
69
65
  if (save === 'devDependencies') {
70
66
  installArgs.push(packageManagerArgs.saveDev);
71
67
  }
72
- return this.run([...installArgs, ...extraArgs], cwd);
68
+ return this.run([...installArgs, ...extraArgs], { cwd, silent: true });
73
69
  }
74
70
  /** Install all packages. */
75
71
  async installAll(extraArgs = [], cwd) {
76
72
  const packageManagerArgs = this.getArguments();
77
- const installArgs = [packageManagerArgs.silent];
73
+ const installArgs = [];
78
74
  if (packageManagerArgs.installAll) {
79
75
  installArgs.push(packageManagerArgs.installAll);
80
76
  }
81
- return this.run([...installArgs, ...extraArgs], cwd);
77
+ return this.run([...installArgs, ...extraArgs], { cwd, silent: true });
82
78
  }
83
79
  /** Install a single package temporary. */
84
80
  async installTemp(packageName, extraArgs) {
@@ -123,7 +119,6 @@ class PackageManagerUtils {
123
119
  switch (this.name) {
124
120
  case workspace_schema_1.PackageManager.Yarn:
125
121
  return {
126
- silent: '--silent',
127
122
  saveDev: '--dev',
128
123
  install: 'add',
129
124
  prefix: '--modules-folder',
@@ -131,7 +126,6 @@ class PackageManagerUtils {
131
126
  };
132
127
  case workspace_schema_1.PackageManager.Pnpm:
133
128
  return {
134
- silent: '--silent',
135
129
  saveDev: '--save-dev',
136
130
  install: 'add',
137
131
  installAll: 'install',
@@ -140,7 +134,6 @@ class PackageManagerUtils {
140
134
  };
141
135
  default:
142
136
  return {
143
- silent: '--quiet',
144
137
  saveDev: '--save-dev',
145
138
  install: 'install',
146
139
  installAll: 'install',
@@ -149,14 +142,16 @@ class PackageManagerUtils {
149
142
  };
150
143
  }
151
144
  }
152
- async run(args, cwd = process.cwd()) {
145
+ async run(args, options = {}) {
146
+ const { cwd = process.cwd(), silent = false } = options;
153
147
  const spinner = new spinner_1.Spinner();
154
148
  spinner.start('Installing packages...');
155
149
  return new Promise((resolve) => {
156
150
  var _a, _b;
157
151
  const bufferedOutput = [];
158
152
  const childProcess = (0, child_process_1.spawn)(this.name, args, {
159
- stdio: 'pipe',
153
+ // Always pipe stderr to allow for failures to be reported
154
+ stdio: silent ? ['ignore', 'ignore', 'pipe'] : 'pipe',
160
155
  shell: true,
161
156
  cwd,
162
157
  }).on('close', (code) => {