|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import { json, logging } from '@angular-devkit/core'; |
| 9 | +import { json, logging, tags } from '@angular-devkit/core'; |
| 10 | +import { execFile } from 'child_process'; |
10 | 11 | import { promises as fs } from 'fs';
|
11 | 12 | import * as path from 'path';
|
12 | 13 | import { env } from 'process';
|
@@ -72,12 +73,29 @@ Ok, you won't be prompted again. Should you change your mind, the following comm
|
72 | 73 | // Notify the user autocompletion was set up successfully.
|
73 | 74 | logger.info(
|
74 | 75 | `
|
75 |
| -Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your terminal or run the following to autocomplete \`ng\` commands: |
| 76 | +${tags.oneLine` |
| 77 | + Appended \`source <(ng completion script)\` to \`${rcFile}\`. Restart your terminal or run the |
| 78 | + following to autocomplete \`ng\` commands: |
| 79 | +`} |
76 | 80 |
|
77 | 81 | ${colors.yellow(`source <(ng completion script)`)}
|
78 | 82 | `.trim(),
|
79 | 83 | );
|
80 | 84 |
|
| 85 | + if ((await hasGlobalCliInstall()) === false) { |
| 86 | + logger.warn( |
| 87 | + ` |
| 88 | +${tags.oneLine` |
| 89 | + Setup completed successfully, but there does not seem to be a global install of the Angular CLI. |
| 90 | + For autocompletion to work, the CLI will need to be on your \`$PATH\`, which is typically done |
| 91 | + with the \`-g\` flag in \`npm install -g @angular/cli\`. |
| 92 | +`} |
| 93 | +
|
| 94 | +See https://angular.io/cli/completion#global-install for more info. |
| 95 | + `.trim(), |
| 96 | + ); |
| 97 | + } |
| 98 | + |
81 | 99 | // Save configuration to remember that the user was prompted.
|
82 | 100 | await setCompletionConfig({ ...completionConfig, prompted: true });
|
83 | 101 |
|
@@ -147,6 +165,12 @@ async function shouldPromptForAutocompletionSetup(
|
147 | 165 | return false; // Unknown shell.
|
148 | 166 | }
|
149 | 167 |
|
| 168 | + // Don't prompt if the user is missing a global CLI install. Autocompletion won't work after setup |
| 169 | + // anyway and could be annoying for users running one-off commands via `npx` or using `npm start`. |
| 170 | + if (!(await hasGlobalCliInstall())) { |
| 171 | + return false; |
| 172 | + } |
| 173 | + |
150 | 174 | // Check each RC file if they already use `ng completion script` in any capacity and don't prompt.
|
151 | 175 | for (const rcFile of rcFiles) {
|
152 | 176 | const contents = await fs.readFile(rcFile, 'utf-8').catch(() => undefined);
|
@@ -246,3 +270,38 @@ function getShellRunCommandCandidates(shell: string, home: string): string[] | u
|
246 | 270 | return undefined;
|
247 | 271 | }
|
248 | 272 | }
|
| 273 | + |
| 274 | +/** |
| 275 | + * Returns whether the user has a global CLI install or `undefined` if this can't be determined. |
| 276 | + * Execution from `npx` is *not* considered a global CLI install. |
| 277 | + * |
| 278 | + * This does *not* mean the current execution is from a global CLI install, only that a global |
| 279 | + * install exists on the system. |
| 280 | + */ |
| 281 | +export async function hasGlobalCliInstall(): Promise<boolean | undefined> { |
| 282 | + // List all binaries with the `ng` name on the user's `$PATH`. |
| 283 | + const proc = execFile('which', ['-a', 'ng']); |
| 284 | + let stdout = ''; |
| 285 | + proc.stdout?.addListener('data', (content) => { |
| 286 | + stdout += content; |
| 287 | + }); |
| 288 | + const exitCode = await new Promise((resolve) => { |
| 289 | + proc.addListener('exit', (exitCode) => { |
| 290 | + resolve(exitCode); |
| 291 | + }); |
| 292 | + }); |
| 293 | + |
| 294 | + if (exitCode !== 0) { |
| 295 | + // `which -a` might not be supported some some systems, can't tell whether CLI is globally |
| 296 | + // installed. |
| 297 | + return undefined; |
| 298 | + } |
| 299 | + |
| 300 | + // Look for at least one line which is a global install. We can't easily identify global installs, |
| 301 | + // but local installs are typically placed in `node_modules/.bin` by NPM / Yarn. `npx` also |
| 302 | + // currently caches files at `~/.npm/_npx/*/node_modules/.bin/`, so the same logic applies. |
| 303 | + const lines = stdout.split('\n').filter((line) => line !== ''); |
| 304 | + const globalInstalls = lines.filter((line) => !line.endsWith('node_modules/.bin')); |
| 305 | + |
| 306 | + return globalInstalls.length >= 1; |
| 307 | +} |
0 commit comments