@angular/cli 14.0.0-next.9 → 14.0.0-rc.2

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
  },
@@ -812,6 +818,12 @@
812
818
  "alias": "t",
813
819
  "x-user-analytics": 10
814
820
  },
821
+ "standalone": {
822
+ "description": "Whether the generated component is standalone.",
823
+ "type": "boolean",
824
+ "default": false,
825
+ "x-user-analytics": 15
826
+ },
815
827
  "viewEncapsulation": {
816
828
  "description": "The view encapsulation strategy to use in the new component.",
817
829
  "enum": [
@@ -923,6 +935,9 @@
923
935
  "path": {
924
936
  "type": "string",
925
937
  "format": "path",
938
+ "$default": {
939
+ "$source": "workingDirectory"
940
+ },
926
941
  "description": "The path at which to create the interface that defines the directive, relative to the workspace root.",
927
942
  "visible": false
928
943
  },
@@ -964,6 +979,12 @@
964
979
  "format": "html-selector",
965
980
  "description": "The HTML selector to use for this directive."
966
981
  },
982
+ "standalone": {
983
+ "description": "Whether the generated directive is standalone.",
984
+ "type": "boolean",
985
+ "default": false,
986
+ "x-user-analytics": 15
987
+ },
967
988
  "flat": {
968
989
  "type": "boolean",
969
990
  "description": "When true (the default), creates the new files at the top level of the current project.",
@@ -1000,6 +1021,9 @@
1000
1021
  "path": {
1001
1022
  "type": "string",
1002
1023
  "format": "path",
1024
+ "$default": {
1025
+ "$source": "workingDirectory"
1026
+ },
1003
1027
  "description": "The path at which to create the enum definition, relative to the current workspace.",
1004
1028
  "visible": false
1005
1029
  },
@@ -1045,6 +1069,9 @@
1045
1069
  "path": {
1046
1070
  "type": "string",
1047
1071
  "format": "path",
1072
+ "$default": {
1073
+ "$source": "workingDirectory"
1074
+ },
1048
1075
  "description": "The path at which to create the interface that defines the guard, relative to the current workspace.",
1049
1076
  "visible": false
1050
1077
  },
@@ -1094,6 +1121,9 @@
1094
1121
  "path": {
1095
1122
  "type": "string",
1096
1123
  "format": "path",
1124
+ "$default": {
1125
+ "$source": "workingDirectory"
1126
+ },
1097
1127
  "description": "The path at which to create the interceptor, relative to the workspace root.",
1098
1128
  "visible": false
1099
1129
  },
@@ -1135,6 +1165,9 @@
1135
1165
  "path": {
1136
1166
  "type": "string",
1137
1167
  "format": "path",
1168
+ "$default": {
1169
+ "$source": "workingDirectory"
1170
+ },
1138
1171
  "description": "The path at which to create the interface, relative to the workspace root.",
1139
1172
  "visible": false
1140
1173
  },
@@ -1224,6 +1257,9 @@
1224
1257
  "path": {
1225
1258
  "type": "string",
1226
1259
  "format": "path",
1260
+ "$default": {
1261
+ "$source": "workingDirectory"
1262
+ },
1227
1263
  "description": "The path at which to create the pipe, relative to the workspace root.",
1228
1264
  "visible": false
1229
1265
  },
@@ -1251,6 +1287,12 @@
1251
1287
  "description": "Do not import this pipe into the owning NgModule.",
1252
1288
  "x-user-analytics": 18
1253
1289
  },
1290
+ "standalone": {
1291
+ "description": "Whether the generated pipe is standalone.",
1292
+ "type": "boolean",
1293
+ "default": false,
1294
+ "x-user-analytics": 15
1295
+ },
1254
1296
  "module": {
1255
1297
  "type": "string",
1256
1298
  "description": "The declaring NgModule.",
@@ -1448,6 +1490,9 @@
1448
1490
  "path": {
1449
1491
  "type": "string",
1450
1492
  "format": "path",
1493
+ "$default": {
1494
+ "$source": "workingDirectory"
1495
+ },
1451
1496
  "description": "The path at which to create the interface that defines the resolver, relative to the current workspace.",
1452
1497
  "visible": false
1453
1498
  },
@@ -1477,7 +1522,9 @@
1477
1522
  },
1478
1523
  "path": {
1479
1524
  "type": "string",
1480
- "format": "path",
1525
+ "$default": {
1526
+ "$source": "workingDirectory"
1527
+ },
1481
1528
  "description": "The path at which to create the service, relative to the workspace root.",
1482
1529
  "visible": false
1483
1530
  },
@@ -1510,6 +1557,9 @@
1510
1557
  "path": {
1511
1558
  "type": "string",
1512
1559
  "format": "path",
1560
+ "$default": {
1561
+ "$source": "workingDirectory"
1562
+ },
1513
1563
  "description": "The path at which to create the worker file, relative to the current workspace.",
1514
1564
  "visible": false
1515
1565
  },
@@ -292,6 +292,10 @@ export interface AngularComponentOptionsSchema {
292
292
  * Do not create "spec.ts" test files for the new component.
293
293
  */
294
294
  skipTests?: boolean;
295
+ /**
296
+ * Whether the generated component is standalone.
297
+ */
298
+ standalone?: boolean;
295
299
  /**
296
300
  * The file extension or preprocessor to use for style files, or 'none' to skip generating
297
301
  * the style file.
@@ -369,6 +373,10 @@ export interface AngularDirectiveOptionsSchema {
369
373
  * Do not create "spec.ts" test files for the new class.
370
374
  */
371
375
  skipTests?: boolean;
376
+ /**
377
+ * Whether the generated directive is standalone.
378
+ */
379
+ standalone?: boolean;
372
380
  }
373
381
  /**
374
382
  * Generates a new, generic enum definition for the given or default project.
@@ -640,6 +648,10 @@ export interface AngularPipeOptionsSchema {
640
648
  * Do not create "spec.ts" test files for the new pipe.
641
649
  */
642
650
  skipTests?: boolean;
651
+ /**
652
+ * Whether the generated pipe is standalone.
653
+ */
654
+ standalone?: boolean;
643
655
  }
644
656
  /**
645
657
  * Generates a new, generic resolver definition in the given or default project.
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-next.9",
3
+ "version": "14.0.0-rc.2",
4
4
  "description": "CLI tool for Angular",
5
5
  "main": "lib/cli/index.js",
6
6
  "bin": {
@@ -25,23 +25,23 @@
25
25
  },
26
26
  "homepage": "https://github.com/angular/angular-cli",
27
27
  "dependencies": {
28
- "@angular-devkit/architect": "0.1400.0-next.9",
29
- "@angular-devkit/core": "14.0.0-next.9",
30
- "@angular-devkit/schematics": "14.0.0-next.9",
31
- "@schematics/angular": "14.0.0-next.9",
28
+ "@angular-devkit/architect": "0.1400.0-rc.2",
29
+ "@angular-devkit/core": "14.0.0-rc.2",
30
+ "@angular-devkit/schematics": "14.0.0-rc.2",
31
+ "@schematics/angular": "14.0.0-rc.2",
32
32
  "@yarnpkg/lockfile": "1.1.0",
33
33
  "ansi-colors": "4.1.1",
34
34
  "debug": "4.3.4",
35
35
  "ini": "3.0.0",
36
- "inquirer": "8.2.2",
36
+ "inquirer": "8.2.4",
37
37
  "jsonc-parser": "3.0.0",
38
38
  "npm-package-arg": "9.0.2",
39
39
  "npm-pick-manifest": "7.0.1",
40
40
  "open": "8.4.0",
41
41
  "ora": "5.4.1",
42
- "pacote": "13.1.1",
42
+ "pacote": "13.3.0",
43
43
  "resolve": "1.22.0",
44
- "semver": "7.3.6",
44
+ "semver": "7.3.7",
45
45
  "symbol-observable": "4.0.0",
46
46
  "uuid": "8.3.2",
47
47
  "yargs": "17.4.1"
@@ -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-next.9",
53
- "@angular-devkit/architect": "0.1400.0-next.9",
54
- "@angular-devkit/build-angular": "14.0.0-next.9",
55
- "@angular-devkit/build-webpack": "0.1400.0-next.9",
56
- "@angular-devkit/core": "14.0.0-next.9",
57
- "@angular-devkit/schematics": "14.0.0-next.9"
52
+ "@angular/cli": "14.0.0-rc.2",
53
+ "@angular-devkit/architect": "0.1400.0-rc.2",
54
+ "@angular-devkit/build-angular": "14.0.0-rc.2",
55
+ "@angular-devkit/build-webpack": "0.1400.0-rc.2",
56
+ "@angular-devkit/core": "14.0.0-rc.2",
57
+ "@angular-devkit/schematics": "14.0.0-rc.2"
58
58
  }
59
59
  },
60
60
  "engines": {
@@ -13,7 +13,7 @@ export interface MissingTargetChoice {
13
13
  name: string;
14
14
  value: string;
15
15
  }
16
- export declare abstract class ArchitectBaseCommandModule<T> extends CommandModule<T> implements CommandModuleImplementation<T> {
16
+ export declare abstract class ArchitectBaseCommandModule<T extends object> extends CommandModule<T> implements CommandModuleImplementation<T> {
17
17
  static scope: CommandScope;
18
18
  protected shouldReportAnalytics: boolean;
19
19
  protected readonly missingTargetChoices: MissingTargetChoice[] | undefined;
@@ -42,6 +42,7 @@ const fs_1 = require("fs");
42
42
  const path = __importStar(require("path"));
43
43
  const helpers_1 = require("yargs/helpers");
44
44
  const analytics_1 = require("../analytics/analytics");
45
+ const completion_1 = require("../utilities/completion");
45
46
  const memoize_1 = require("../utilities/memoize");
46
47
  var CommandScope;
47
48
  (function (CommandScope) {
@@ -89,6 +90,12 @@ class CommandModule {
89
90
  for (const [key, value] of Object.entries(options)) {
90
91
  camelCasedOptions[helpers_1.Parser.camelCase(key)] = value;
91
92
  }
93
+ // Set up autocompletion if appropriate.
94
+ const autocompletionExitCode = await (0, completion_1.considerSettingUpAutocompletion)(this.commandName, this.context.logger);
95
+ if (autocompletionExitCode !== undefined) {
96
+ process.exitCode = autocompletionExitCode;
97
+ return;
98
+ }
92
99
  // Gather and report analytics.
93
100
  const analytics = await this.getAnalytics();
94
101
  if (this.shouldReportAnalytics) {
@@ -141,9 +148,8 @@ class CommandModule {
141
148
  * **Note:** This method should be called from the command bundler method.
142
149
  */
143
150
  addSchemaOptionsToCommand(localYargs, options) {
144
- const workingDir = (0, core_1.normalize)(path.relative(this.context.root, process.cwd()));
145
151
  for (const option of options) {
146
- 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;
147
153
  const sharedOptions = {
148
154
  alias,
149
155
  hidden,
@@ -153,10 +159,6 @@ class CommandModule {
153
159
  // This should only be done when `--help` is used otherwise default will override options set in angular.json.
154
160
  ...(this.context.args.options.help ? { default: defaultVal } : {}),
155
161
  };
156
- // Special case for schematics
157
- if (workingDir && format === 'path' && name === 'path' && hidden) {
158
- sharedOptions.default = workingDir;
159
- }
160
162
  if (positional === undefined) {
161
163
  localYargs = localYargs.option(core_1.strings.dasherize(name), {
162
164
  type,
@@ -110,16 +110,11 @@ async function runCommand(args, logger) {
110
110
  }
111
111
  localYargs = (0, command_1.addCommandModuleToYargs)(localYargs, CommandModule, context);
112
112
  }
113
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
- const usageInstance = localYargs.getInternalMethods().getUsageInstance();
115
113
  if (jsonHelp) {
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ const usageInstance = localYargs.getInternalMethods().getUsageInstance();
116
116
  usageInstance.help = () => (0, json_help_1.jsonHelpUsage)();
117
117
  }
118
- if (getYargsCompletions) {
119
- // When in auto completion mode avoid printing description as it causes a slugish
120
- // experience when there are a large set of options.
121
- usageInstance.getDescriptions = () => ({});
122
- }
123
118
  await localYargs
124
119
  .scriptName('ng')
125
120
  // https://github.com/yargs/yargs/blob/main/docs/advanced.md#customizing-yargs-parser
@@ -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");
@@ -109,9 +110,7 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
109
110
  optionTransforms: [
110
111
  // Add configuration file defaults
111
112
  async (schematic, current) => {
112
- const projectName = typeof current.project === 'string'
113
- ? current.project
114
- : this.getProjectName();
113
+ const projectName = typeof (current === null || current === void 0 ? void 0 : current.project) === 'string' ? current.project : this.getProjectName();
115
114
  return {
116
115
  ...(await (0, config_1.getSchematicDefaults)(schematic.collection.name, schematic.name, projectName)),
117
116
  ...current,
@@ -121,8 +120,10 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
121
120
  engineHostCreator: (options) => new schematic_engine_host_1.SchematicEngineHost(options.resolvePaths),
122
121
  });
123
122
  workflow.registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
124
- workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName());
125
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);
126
127
  let shouldReportAnalytics = true;
127
128
  workflow.engineHost.registerOptionsTransform(async (schematic, options) => {
128
129
  var _a;
@@ -136,6 +137,28 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
136
137
  schematic.name.replace(/\//g, '_'),
137
138
  ]);
138
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
+ }
139
162
  return options;
140
163
  });
141
164
  if (options.interactive !== false && (0, tty_1.isTTY)()) {
@@ -211,16 +234,20 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
211
234
  }
212
235
  async getSchematicCollections() {
213
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;
214
241
  const getSchematicCollections = (configSection) => {
215
242
  if (!configSection) {
216
243
  return undefined;
217
244
  }
218
245
  const { schematicCollections, defaultCollection } = configSection;
219
246
  if (Array.isArray(schematicCollections)) {
220
- return new Set(schematicCollections);
247
+ return new Set(schematicCollections.map((c) => resolveRelativeCollection(c)));
221
248
  }
222
249
  else if (typeof defaultCollection === 'string') {
223
- return new Set([defaultCollection]);
250
+ return new Set([resolveRelativeCollection(defaultCollection)]);
224
251
  }
225
252
  return undefined;
226
253
  };
@@ -277,11 +304,11 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
277
304
  if (err instanceof schematics_1.UnsuccessfulWorkflowExecution) {
278
305
  // "See above" because we already printed the error.
279
306
  logger.fatal('The Schematic workflow failed. See above.');
280
- return 1;
281
307
  }
282
308
  else {
283
- throw err;
309
+ logger.fatal(err.message);
284
310
  }
311
+ return 1;
285
312
  }
286
313
  finally {
287
314
  unsubscribe();
@@ -301,9 +328,9 @@ class SchematicsCommandModule extends command_module_1.CommandModule {
301
328
  if (typeof defaultProjectName === 'string' && defaultProjectName) {
302
329
  if (!this.defaultProjectDeprecationWarningShown) {
303
330
  logger.warn(core_1.tags.oneLine `
304
- DEPRECATED: The 'defaultProject' workspace option has been deprecated.
305
- The project to use will be determined from the current working directory.
306
- `);
331
+ DEPRECATED: The 'defaultProject' workspace option has been deprecated.
332
+ The project to use will be determined from the current working directory.
333
+ `);
307
334
  this.defaultProjectDeprecationWarningShown = true;
308
335
  }
309
336
  return defaultProjectName;
@@ -8,6 +8,6 @@
8
8
  import { Argv } from 'yargs';
9
9
  import { CommandContext, CommandModule, CommandModuleImplementation } from '../command-module';
10
10
  export declare const demandCommandFailureMessage = "You need to specify a command before moving on. Use '--help' to view the available commands.";
11
- export declare function addCommandModuleToYargs<T, U extends Partial<CommandModuleImplementation> & {
11
+ export declare function addCommandModuleToYargs<T extends object, U extends Partial<CommandModuleImplementation> & {
12
12
  new (context: CommandContext): Partial<CommandModuleImplementation> & CommandModule;
13
13
  }>(localYargs: Argv<T>, commandModule: U, context: CommandContext): Argv<T>;
@@ -161,7 +161,9 @@ function wrap(schematicFile, schematicDirectory, moduleCache, exportName) {
161
161
  const schematicCode = (0, fs_1.readFileSync)(schematicFile, 'utf8');
162
162
  // `module` is required due to @angular/localize ng-add being in UMD format
163
163
  const headerCode = '(function() {\nvar exports = {};\nvar module = { exports };\n';
164
- const footerCode = exportName ? `\nreturn exports['${exportName}'];});` : '\nreturn exports;});';
164
+ const footerCode = exportName
165
+ ? `\nreturn module.exports['${exportName}'];});`
166
+ : '\nreturn module.exports;});';
165
167
  const script = new vm_1.Script(headerCode + schematicCode + footerCode, {
166
168
  filename: schematicFile,
167
169
  lineOffset: 3,
@@ -12,5 +12,5 @@ export declare class CompletionCommandModule extends CommandModule implements Co
12
12
  describe: string;
13
13
  longDescriptionPath: string;
14
14
  builder(localYargs: Argv): Argv;
15
- run(): void;
15
+ run(): Promise<number>;
16
16
  }
@@ -14,13 +14,51 @@ exports.CompletionCommandModule = void 0;
14
14
  const path_1 = require("path");
15
15
  const yargs_1 = __importDefault(require("yargs"));
16
16
  const command_module_1 = require("../../command-builder/command-module");
17
+ const command_1 = require("../../command-builder/utilities/command");
18
+ const color_1 = require("../../utilities/color");
19
+ const completion_1 = require("../../utilities/completion");
17
20
  class CompletionCommandModule extends command_module_1.CommandModule {
18
21
  constructor() {
19
22
  super(...arguments);
20
23
  this.command = 'completion';
21
- this.describe = 'Generate a bash and zsh real-time type-ahead autocompletion script.';
24
+ this.describe = 'Set up Angular CLI autocompletion for your terminal.';
22
25
  this.longDescriptionPath = (0, path_1.join)(__dirname, 'long-description.md');
23
26
  }
27
+ builder(localYargs) {
28
+ return (0, command_1.addCommandModuleToYargs)(localYargs, CompletionScriptCommandModule, this.context);
29
+ }
30
+ async run() {
31
+ let rcFile;
32
+ try {
33
+ rcFile = await (0, completion_1.initializeAutocomplete)();
34
+ }
35
+ catch (err) {
36
+ this.context.logger.error(err.message);
37
+ return 1;
38
+ }
39
+ this.context.logger.info(`
40
+ Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your terminal or run the following to autocomplete \`ng\` commands:
41
+
42
+ ${color_1.colors.yellow('source <(ng completion script)')}
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
+ }
51
+ return 0;
52
+ }
53
+ }
54
+ exports.CompletionCommandModule = CompletionCommandModule;
55
+ class CompletionScriptCommandModule extends command_module_1.CommandModule {
56
+ constructor() {
57
+ super(...arguments);
58
+ this.command = 'script';
59
+ this.describe = 'Generate a bash and zsh real-time type-ahead autocompletion script.';
60
+ this.longDescriptionPath = undefined;
61
+ }
24
62
  builder(localYargs) {
25
63
  return localYargs;
26
64
  }
@@ -28,4 +66,3 @@ class CompletionCommandModule extends command_module_1.CommandModule {
28
66
  yargs_1.default.showCompletionScript();
29
67
  }
30
68
  }
31
- exports.CompletionCommandModule = CompletionCommandModule;
@@ -1 +1,73 @@
1
- To enable bash and zsh real-time type-ahead autocompletion, copy and paste the generated script to your `.bashrc`, `.bash_profile`, `.zshrc` or `.zsh_profile`.
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.
4
+
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
17
+ typing Angular CLI commands will show possible options and autocomplete arguments. (Enabling
18
+ autocompletion will modify configuration files in your home directory.) Yes
19
+ Appended `source <(ng completion script)` to `/home/my-username/.bashrc`. Restart your terminal or
20
+ run:
21
+
22
+ source <(ng completion script)
23
+
24
+ to autocomplete `ng` commands.
25
+
26
+ # Serve output...
27
+ ```
28
+
29
+ If you already refused the prompt, it won't ask again. But you can run `ng completion` to
30
+ do the same thing automatically.
31
+
32
+ This modifies your terminal environment to load Angular CLI autocompletion, but can't update your
33
+ current terminal session. Either restart it or run `source <(ng completion script)` directly to
34
+ enable autocompletion in your current session.
35
+
36
+ Test it out by typing `ng ser<TAB>` and it should autocomplete to `ng serve`. Ambiguous arguments
37
+ will show all possible options and their documentation, such as `ng generate <TAB>`.
38
+
39
+ ## Manual setup
40
+
41
+ Some users may have highly customized terminal setups, possibly with configuration files checked
42
+ into source control with an opinionated structure. `ng completion` only ever appends Angular's setup
43
+ to an existing configuration file for your current shell, or creates one if none exists. If you want
44
+ more control over exactly where this configuration lives, you can manually set it up by having your
45
+ shell run at startup:
46
+
47
+ ```bash
48
+ source <(ng completion script)
49
+ ```
50
+
51
+ This is equivalent to what `ng completion` will automatically set up, and gives power users more
52
+ flexibility in their environments when desired.
53
+
54
+ ## Platform support
55
+
56
+ Angular CLI supports autocompletion for the Bash and Zsh shells on MacOS and Linux operating
57
+ systems.
58
+
59
+ Windows does not support autocompletion in native shells, such as Cmd and Powershell. However,
60
+ the Angular CLI supports Git Bash and
61
+ [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) using Bash or Zsh.
62
+
63
+ ## Global install
64
+
65
+ Autocompletion works by configuring your terminal to invoke the Angular CLI on startup to load the
66
+ setup script. This means the terminal must be able to find and execute the Angular CLI, typically
67
+ through a global install that places the binary on the user's `$PATH`. If you get
68
+ `command not found: ng`, make sure the CLI is installed globally which you can do with the `-g`
69
+ flag:
70
+
71
+ ```bash
72
+ npm install -g @angular/cli
73
+ ```
@@ -83,6 +83,7 @@ class ConfigCommandModule extends command_module_1.CommandModule {
83
83
  'cli.analytics',
84
84
  'cli.analyticsSharing.tracking',
85
85
  'cli.analyticsSharing.uuid',
86
+ 'cli.completion.prompted',
86
87
  ]);
87
88
  if (options.global &&
88
89
  !options.jsonPath.startsWith('schematics.') &&
@@ -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
  })
@@ -584,8 +581,9 @@ class UpdateCommandModule extends command_module_1.CommandModule {
584
581
  }
585
582
  }
586
583
  const result = await this.executeMigrations(workflow, migration.package, migrations, migration.from, migration.to, options.createCommits);
587
- if (!result) {
588
- return 0;
584
+ // A non-zero value is a failure for the package's migrations
585
+ if (result !== 0) {
586
+ return result;
589
587
  }
590
588
  }
591
589
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ import { logging } from '@angular-devkit/core';
9
+ /**
10
+ * Checks if it is appropriate to prompt the user to setup autocompletion. If not, does nothing. If
11
+ * so prompts and sets up autocompletion for the user. Returns an exit code if the program should
12
+ * terminate, otherwise returns `undefined`.
13
+ * @returns an exit code if the program should terminate, undefined otherwise.
14
+ */
15
+ export declare function considerSettingUpAutocompletion(command: string, logger: logging.Logger): Promise<number | undefined>;
16
+ /**
17
+ * Sets up autocompletion for the user's terminal. This attempts to find the configuration file for
18
+ * the current shell (`.bashrc`, `.zshrc`, etc.) and append a command which enables autocompletion
19
+ * for the Angular CLI. Supports only Bash and Zsh. Returns whether or not it was successful.
20
+ * @return The full path of the configuration file modified.
21
+ */
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>;
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.io/license
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || function (mod) {
26
+ if (mod && mod.__esModule) return mod;
27
+ var result = {};
28
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29
+ __setModuleDefault(result, mod);
30
+ return result;
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.hasGlobalCliInstall = exports.initializeAutocomplete = exports.considerSettingUpAutocompletion = void 0;
34
+ const core_1 = require("@angular-devkit/core");
35
+ const child_process_1 = require("child_process");
36
+ const fs_1 = require("fs");
37
+ const path = __importStar(require("path"));
38
+ const process_1 = require("process");
39
+ const color_1 = require("../utilities/color");
40
+ const config_1 = require("../utilities/config");
41
+ const environment_options_1 = require("../utilities/environment-options");
42
+ const tty_1 = require("../utilities/tty");
43
+ /**
44
+ * Checks if it is appropriate to prompt the user to setup autocompletion. If not, does nothing. If
45
+ * so prompts and sets up autocompletion for the user. Returns an exit code if the program should
46
+ * terminate, otherwise returns `undefined`.
47
+ * @returns an exit code if the program should terminate, undefined otherwise.
48
+ */
49
+ async function considerSettingUpAutocompletion(command, logger) {
50
+ // Check if we should prompt the user to setup autocompletion.
51
+ const completionConfig = await getCompletionConfig();
52
+ if (!(await shouldPromptForAutocompletionSetup(command, completionConfig))) {
53
+ return undefined; // Already set up or prompted previously, nothing to do.
54
+ }
55
+ // Prompt the user and record their response.
56
+ const shouldSetupAutocompletion = await promptForAutocompletion();
57
+ if (!shouldSetupAutocompletion) {
58
+ // User rejected the prompt and doesn't want autocompletion.
59
+ logger.info(`
60
+ Ok, you won't be prompted again. Should you change your mind, the following command will set up autocompletion for you:
61
+
62
+ ${color_1.colors.yellow(`ng completion`)}
63
+ `.trim());
64
+ // Save configuration to remember that the user was prompted and avoid prompting again.
65
+ await setCompletionConfig({ ...completionConfig, prompted: true });
66
+ return undefined;
67
+ }
68
+ // User accepted the prompt, set up autocompletion.
69
+ let rcFile;
70
+ try {
71
+ rcFile = await initializeAutocomplete();
72
+ }
73
+ catch (err) {
74
+ // Failed to set up autocompeletion, log the error and abort.
75
+ logger.error(err.message);
76
+ return 1;
77
+ }
78
+ // Notify the user autocompletion was set up successfully.
79
+ logger.info(`
80
+ Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your terminal or run the following to autocomplete \`ng\` commands:
81
+
82
+ ${color_1.colors.yellow(`source <(ng completion script)`)}
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
+ }
91
+ // Save configuration to remember that the user was prompted.
92
+ await setCompletionConfig({ ...completionConfig, prompted: true });
93
+ return undefined;
94
+ }
95
+ exports.considerSettingUpAutocompletion = considerSettingUpAutocompletion;
96
+ async function getCompletionConfig() {
97
+ var _a;
98
+ const wksp = await (0, config_1.getWorkspace)('global');
99
+ return (_a = wksp === null || wksp === void 0 ? void 0 : wksp.getCli()) === null || _a === void 0 ? void 0 : _a['completion'];
100
+ }
101
+ async function setCompletionConfig(config) {
102
+ var _a;
103
+ var _b;
104
+ const wksp = await (0, config_1.getWorkspace)('global');
105
+ if (!wksp) {
106
+ throw new Error(`Could not find global workspace`);
107
+ }
108
+ (_a = (_b = wksp.extensions)['cli']) !== null && _a !== void 0 ? _a : (_b['cli'] = {});
109
+ const cli = wksp.extensions['cli'];
110
+ if (!core_1.json.isJsonObject(cli)) {
111
+ throw new Error(`Invalid config found at ${wksp.filePath}. \`extensions.cli\` should be an object.`);
112
+ }
113
+ cli.completion = config;
114
+ await wksp.save();
115
+ }
116
+ async function shouldPromptForAutocompletionSetup(command, config) {
117
+ // Force whether or not to prompt for autocomplete to give an easy path for e2e testing to skip.
118
+ if (environment_options_1.forceAutocomplete !== undefined) {
119
+ return environment_options_1.forceAutocomplete;
120
+ }
121
+ // Don't prompt on `ng update` or `ng completion`.
122
+ if (command === 'update' || command === 'completion') {
123
+ return false;
124
+ }
125
+ // Non-interactive and continuous integration systems don't care about autocompletion.
126
+ if (!(0, tty_1.isTTY)()) {
127
+ return false;
128
+ }
129
+ // Skip prompt if the user has already been prompted.
130
+ if (config === null || config === void 0 ? void 0 : config.prompted) {
131
+ return false;
132
+ }
133
+ // `$HOME` variable is necessary to find RC files to modify.
134
+ const home = process_1.env['HOME'];
135
+ if (!home) {
136
+ return false;
137
+ }
138
+ // Get possible RC files for the current shell.
139
+ const shell = process_1.env['SHELL'];
140
+ if (!shell) {
141
+ return false;
142
+ }
143
+ const rcFiles = getShellRunCommandCandidates(shell, home);
144
+ if (!rcFiles) {
145
+ return false; // Unknown shell.
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
+ }
152
+ // Check each RC file if they already use `ng completion script` in any capacity and don't prompt.
153
+ for (const rcFile of rcFiles) {
154
+ const contents = await fs_1.promises.readFile(rcFile, 'utf-8').catch(() => undefined);
155
+ if (contents === null || contents === void 0 ? void 0 : contents.includes('ng completion script')) {
156
+ return false;
157
+ }
158
+ }
159
+ return true;
160
+ }
161
+ async function promptForAutocompletion() {
162
+ // Dynamically load `inquirer` so users don't have to pay the cost of parsing and executing it for
163
+ // the 99% of builds that *don't* prompt for autocompletion.
164
+ const { prompt } = await Promise.resolve().then(() => __importStar(require('inquirer')));
165
+ const { autocomplete } = await prompt([
166
+ {
167
+ name: 'autocomplete',
168
+ type: 'confirm',
169
+ message: `
170
+ Would you like to enable autocompletion? This will set up your terminal so pressing TAB while typing
171
+ Angular CLI commands will show possible options and autocomplete arguments. (Enabling autocompletion
172
+ will modify configuration files in your home directory.)
173
+ `
174
+ .split('\n')
175
+ .join(' ')
176
+ .trim(),
177
+ default: true,
178
+ },
179
+ ]);
180
+ return autocomplete;
181
+ }
182
+ /**
183
+ * Sets up autocompletion for the user's terminal. This attempts to find the configuration file for
184
+ * the current shell (`.bashrc`, `.zshrc`, etc.) and append a command which enables autocompletion
185
+ * for the Angular CLI. Supports only Bash and Zsh. Returns whether or not it was successful.
186
+ * @return The full path of the configuration file modified.
187
+ */
188
+ async function initializeAutocomplete() {
189
+ var _a, _b;
190
+ // Get the currently active `$SHELL` and `$HOME` environment variables.
191
+ const shell = process_1.env['SHELL'];
192
+ if (!shell) {
193
+ throw new Error('`$SHELL` environment variable not set. Angular CLI autocompletion only supports Bash or' +
194
+ " Zsh. If you're on Windows, Cmd and Powershell don't support command autocompletion," +
195
+ ' but Git Bash or Windows Subsystem for Linux should work, so please try again in one of' +
196
+ ' those environments.');
197
+ }
198
+ const home = process_1.env['HOME'];
199
+ if (!home) {
200
+ throw new Error('`$HOME` environment variable not set. Setting up autocompletion modifies configuration files' +
201
+ ' in the home directory and must be set.');
202
+ }
203
+ // Get all the files we can add `ng completion` to which apply to the user's `$SHELL`.
204
+ const runCommandCandidates = getShellRunCommandCandidates(shell, home);
205
+ if (!runCommandCandidates) {
206
+ throw new Error(`Unknown \`$SHELL\` environment variable value (${shell}). Angular CLI autocompletion only supports Bash or Zsh.`);
207
+ }
208
+ // Get the first file that already exists or fallback to a new file of the first candidate.
209
+ const candidates = await Promise.allSettled(runCommandCandidates.map((rcFile) => fs_1.promises.access(rcFile).then(() => rcFile)));
210
+ const rcFile = (_b = (_a = candidates.find((result) => result.status === 'fulfilled')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : runCommandCandidates[0];
211
+ // Append Angular autocompletion setup to RC file.
212
+ try {
213
+ await fs_1.promises.appendFile(rcFile, '\n\n# Load Angular CLI autocompletion.\nsource <(ng completion script)\n');
214
+ }
215
+ catch (err) {
216
+ throw new Error(`Failed to append autocompletion setup to \`${rcFile}\`:\n${err.message}`);
217
+ }
218
+ return rcFile;
219
+ }
220
+ exports.initializeAutocomplete = initializeAutocomplete;
221
+ /** Returns an ordered list of possible candidates of RC files used by the given shell. */
222
+ function getShellRunCommandCandidates(shell, home) {
223
+ if (shell.toLowerCase().includes('bash')) {
224
+ return ['.bashrc', '.bash_profile', '.profile'].map((file) => path.join(home, file));
225
+ }
226
+ else if (shell.toLowerCase().includes('zsh')) {
227
+ return ['.zshrc', '.zsh_profile', '.profile'].map((file) => path.join(home, file));
228
+ }
229
+ else {
230
+ return undefined;
231
+ }
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;
@@ -10,3 +10,4 @@ export declare const analyticsShareDisabled: boolean;
10
10
  export declare const isCI: boolean;
11
11
  export declare const disableVersionCheck: boolean;
12
12
  export declare const ngDebug: boolean;
13
+ export declare const forceAutocomplete: boolean | undefined;
@@ -7,7 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.ngDebug = exports.disableVersionCheck = exports.isCI = exports.analyticsShareDisabled = exports.analyticsDisabled = void 0;
10
+ exports.forceAutocomplete = exports.ngDebug = exports.disableVersionCheck = exports.isCI = exports.analyticsShareDisabled = exports.analyticsDisabled = void 0;
11
11
  function isPresent(variable) {
12
12
  return typeof variable === 'string' && variable !== '';
13
13
  }
@@ -17,8 +17,15 @@ function isDisabled(variable) {
17
17
  function isEnabled(variable) {
18
18
  return isPresent(variable) && (variable === '1' || variable.toLowerCase() === 'true');
19
19
  }
20
+ function optional(variable) {
21
+ if (!isPresent(variable)) {
22
+ return undefined;
23
+ }
24
+ return isEnabled(variable);
25
+ }
20
26
  exports.analyticsDisabled = isDisabled(process.env['NG_CLI_ANALYTICS']);
21
27
  exports.analyticsShareDisabled = isDisabled(process.env['NG_CLI_ANALYTICS_SHARE']);
22
28
  exports.isCI = isEnabled(process.env['CI']);
23
29
  exports.disableVersionCheck = isEnabled(process.env['NG_DISABLE_VERSION_CHECK']);
24
30
  exports.ngDebug = isEnabled(process.env['NG_DEBUG']);
31
+ exports.forceAutocomplete = optional(process.env['NG_FORCE_AUTOCOMPLETE']);
@@ -59,6 +59,7 @@ class JSONFile {
59
59
  }
60
60
  const edits = (0, jsonc_parser_1.modify)(this.content, jsonPath, value, {
61
61
  getInsertionIndex,
62
+ // TODO: use indentation from original file.
62
63
  formattingOptions: {
63
64
  insertSpaces: true,
64
65
  tabSize: 2,