Skip to content

Commit 6053771

Browse files
committed
fix(@angular/cli): only load autocompletion in interactive shells
Previously, `ng completion` appended an unconditional `source <(ng completion script)` statement to the user's shell configuration file. When non-interactive shells (such as those spawned by scripts, automation tools, or coding agents) sourced the configuration file, they still paid the overhead of starting Node.js and generating the completion script despite tab completion not being used. (cherry picked from commit 21846da)
1 parent d69966a commit 6053771

3 files changed

Lines changed: 21 additions & 52 deletions

File tree

‎packages/angular/cli/src/commands/completion/long-description.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ more control over exactly where this configuration lives, you can manually set i
4242
shell run at startup:
4343

4444
```bash
45-
source <(ng completion script)
45+
if [[ $- == *i* ]]; then
46+
source <(ng completion script)
47+
fi
4648
```
4749

4850
This is equivalent to what `ng completion` will automatically set up, and gives power users more

‎packages/angular/cli/src/utilities/completion.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export async function initializeAutocomplete(): Promise<string> {
240240
try {
241241
await fs.appendFile(
242242
rcFile,
243-
'\n\n# Load Angular CLI autocompletion.\nsource <(ng completion script)\n',
243+
'\n\n# Load Angular CLI autocompletion for interactive shells.\nif [[ $- == *i* ]]; then\n source <(ng completion script)\nfi\n',
244244
);
245245
} catch (err) {
246246
assertIsError(err);

‎tests/e2e/tests/commands/completion/completion.ts‎

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import {
1111

1212
const testRegistry = getGlobalVariable('package-registry');
1313

14+
const AUTOCOMPLETION_SNIPPET = `
15+
# Load Angular CLI autocompletion for interactive shells.
16+
if [[ $- == *i* ]]; then
17+
source <(ng completion script)
18+
fi
19+
`.trim();
20+
1421
export default async function () {
1522
// Windows Cmd and Powershell do not support autocompletion. Run a different set of tests to
1623
// confirm autocompletion fails gracefully.
@@ -33,11 +40,7 @@ export default async function () {
3340
);
3441

3542
const rcContents = await fs.readFile(path.join(home, '.bashrc'), 'utf-8');
36-
const expected = `
37-
# Load Angular CLI autocompletion.
38-
source <(ng completion script)
39-
`.trim();
40-
if (!rcContents.includes(expected)) {
43+
if (!rcContents.includes(AUTOCOMPLETION_SNIPPET)) {
4144
throw new Error(`~/.bashrc does not contain autocompletion script. Contents:\n${rcContents}`);
4245
}
4346
});
@@ -55,11 +58,7 @@ source <(ng completion script)
5558
);
5659

5760
const rcContents = await fs.readFile(path.join(home, '.zshrc'), 'utf-8');
58-
const expected = `
59-
# Load Angular CLI autocompletion.
60-
source <(ng completion script)
61-
`.trim();
62-
if (!rcContents.includes(expected)) {
61+
if (!rcContents.includes(AUTOCOMPLETION_SNIPPET)) {
6362
throw new Error(`~/.zshrc does not contain autocompletion script. Contents:\n${rcContents}`);
6463
}
6564
});
@@ -79,11 +78,7 @@ source <(ng completion script)
7978
);
8079

8180
const rcContents = await fs.readFile(bashrc, 'utf-8');
82-
const expected = `# Other commands...
83-
84-
# Load Angular CLI autocompletion.
85-
source <(ng completion script)
86-
`;
81+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
8782
if (rcContents !== expected) {
8883
throw new Error(`~/.bashrc does not match expectation. Contents:\n${rcContents}`);
8984
}
@@ -104,11 +99,7 @@ source <(ng completion script)
10499
);
105100

106101
const rcContents = await fs.readFile(bashProfile, 'utf-8');
107-
const expected = `# Other commands...
108-
109-
# Load Angular CLI autocompletion.
110-
source <(ng completion script)
111-
`;
102+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
112103
if (rcContents !== expected) {
113104
throw new Error(`~/.bash_profile does not match expectation. Contents:\n${rcContents}`);
114105
}
@@ -129,11 +120,7 @@ source <(ng completion script)
129120
);
130121

131122
const rcContents = await fs.readFile(profile, 'utf-8');
132-
const expected = `# Other commands...
133-
134-
# Load Angular CLI autocompletion.
135-
source <(ng completion script)
136-
`;
123+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
137124
if (rcContents !== expected) {
138125
throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`);
139126
}
@@ -159,11 +146,7 @@ source <(ng completion script)
159146
);
160147

161148
const bashrcContents = await fs.readFile(bashrc, 'utf-8');
162-
const bashrcExpected = `# \`.bashrc\` commands...
163-
164-
# Load Angular CLI autocompletion.
165-
source <(ng completion script)
166-
`;
149+
const bashrcExpected = `# \`.bashrc\` commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
167150
if (bashrcContents !== bashrcExpected) {
168151
throw new Error(`~/.bashrc does not match expectation. Contents:\n${bashrcContents}`);
169152
}
@@ -194,11 +177,7 @@ source <(ng completion script)
194177
);
195178

196179
const rcContents = await fs.readFile(zshrc, 'utf-8');
197-
const expected = `# Other commands...
198-
199-
# Load Angular CLI autocompletion.
200-
source <(ng completion script)
201-
`;
180+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
202181
if (rcContents !== expected) {
203182
throw new Error(`~/.zshrc does not match expectation. Contents:\n${rcContents}`);
204183
}
@@ -219,11 +198,7 @@ source <(ng completion script)
219198
);
220199

221200
const rcContents = await fs.readFile(zshProfile, 'utf-8');
222-
const expected = `# Other commands...
223-
224-
# Load Angular CLI autocompletion.
225-
source <(ng completion script)
226-
`;
201+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
227202
if (rcContents !== expected) {
228203
throw new Error(`~/.zsh_profile does not match expectation. Contents:\n${rcContents}`);
229204
}
@@ -244,11 +219,7 @@ source <(ng completion script)
244219
);
245220

246221
const rcContents = await fs.readFile(profile, 'utf-8');
247-
const expected = `# Other commands...
248-
249-
# Load Angular CLI autocompletion.
250-
source <(ng completion script)
251-
`;
222+
const expected = `# Other commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
252223
if (rcContents !== expected) {
253224
throw new Error(`~/.profile does not match expectation. Contents:\n${rcContents}`);
254225
}
@@ -274,11 +245,7 @@ source <(ng completion script)
274245
);
275246

276247
const zshrcContents = await fs.readFile(zshrc, 'utf-8');
277-
const zshrcExpected = `# \`.zshrc\` commands...
278-
279-
# Load Angular CLI autocompletion.
280-
source <(ng completion script)
281-
`;
248+
const zshrcExpected = `# \`.zshrc\` commands...\n\n${AUTOCOMPLETION_SNIPPET}\n`;
282249
if (zshrcContents !== zshrcExpected) {
283250
throw new Error(`~/.zshrc does not match expectation. Contents:\n${zshrcContents}`);
284251
}

0 commit comments

Comments
 (0)