Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/environment/node/argv.ts
5221 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import minimist from 'minimist';
7
import { isWindows } from '../../../base/common/platform.js';
8
import { localize } from '../../../nls.js';
9
import { NativeParsedArgs } from '../common/argv.js';
10
11
/**
12
* This code is also used by standalone cli's. Avoid adding any other dependencies.
13
*/
14
const helpCategories = {
15
o: localize('optionsUpperCase', "Options"),
16
e: localize('extensionsManagement', "Extensions Management"),
17
t: localize('troubleshooting', "Troubleshooting"),
18
m: localize('mcp', "Model Context Protocol")
19
};
20
21
export interface Option<OptionType> {
22
type: OptionType;
23
alias?: string;
24
deprecates?: string[]; // old deprecated ids
25
args?: string | string[];
26
description?: string;
27
deprecationMessage?: string;
28
allowEmptyValue?: boolean;
29
cat?: keyof typeof helpCategories;
30
global?: boolean;
31
}
32
33
export interface Subcommand<T> {
34
type: 'subcommand';
35
description?: string;
36
deprecationMessage?: string;
37
options: OptionDescriptions<Required<T>>;
38
}
39
40
export type OptionDescriptions<T> = {
41
[P in keyof T]:
42
T[P] extends boolean | undefined ? Option<'boolean'> :
43
T[P] extends string | undefined ? Option<'string'> :
44
T[P] extends string[] | undefined ? Option<'string[]'> :
45
Subcommand<T[P]>
46
};
47
48
export const NATIVE_CLI_COMMANDS = ['tunnel', 'serve-web'] as const;
49
50
export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
51
'chat': {
52
type: 'subcommand',
53
description: 'Pass in a prompt to run in a chat session in the current working directory.',
54
options: {
55
'_': { type: 'string[]', description: localize('prompt', "The prompt to use as chat.") },
56
'mode': { type: 'string', cat: 'o', alias: 'm', args: 'mode', description: localize('chatMode', "The mode to use for the chat session. Available options: 'ask', 'edit', 'agent', or the identifier of a custom mode. Defaults to 'agent'.") },
57
'add-file': { type: 'string[]', cat: 'o', alias: 'a', args: 'path', description: localize('addFile', "Add files as context to the chat session.") },
58
'maximize': { type: 'boolean', cat: 'o', description: localize('chatMaximize', "Maximize the chat session view.") },
59
'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindowForChat', "Force to use the last active window for the chat session.") },
60
'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindowForChat', "Force to open an empty window for the chat session.") },
61
'profile': { type: 'string', 'cat': 'o', args: 'profileName', description: localize('profileName', "Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created.") },
62
'help': { type: 'boolean', alias: 'h', description: localize('help', "Print usage.") }
63
}
64
},
65
'serve-web': {
66
type: 'subcommand',
67
description: 'Run a server that displays the editor UI in browsers.',
68
options: {
69
'cli-data-dir': { type: 'string', args: 'dir', description: localize('cliDataDir', "Directory where CLI metadata should be stored.") },
70
'disable-telemetry': { type: 'boolean' },
71
'telemetry-level': { type: 'string' },
72
}
73
},
74
'tunnel': {
75
type: 'subcommand',
76
description: 'Make the current machine accessible from vscode.dev or other machines through a secure tunnel.',
77
options: {
78
'cli-data-dir': { type: 'string', args: 'dir', description: localize('cliDataDir', "Directory where CLI metadata should be stored.") },
79
'disable-telemetry': { type: 'boolean' },
80
'telemetry-level': { type: 'string' },
81
user: {
82
type: 'subcommand',
83
options: {
84
login: {
85
type: 'subcommand',
86
options: {
87
provider: { type: 'string' },
88
'access-token': { type: 'string' }
89
}
90
}
91
}
92
}
93
}
94
},
95
'diff': { type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") },
96
'merge': { type: 'boolean', cat: 'o', alias: 'm', args: ['path1', 'path2', 'base', 'result'], description: localize('merge', "Perform a three-way merge by providing paths for two modified versions of a file, the common origin of both modified versions and the output file to save merge results.") },
97
'add': { type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") },
98
'remove': { type: 'boolean', cat: 'o', args: 'folder', description: localize('remove', "Remove folder(s) from the last active window.") },
99
'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") },
100
'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
101
'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
102
'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
103
'waitMarkerFilePath': { type: 'string' },
104
'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
105
'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
106
'profile': { type: 'string', 'cat': 'o', args: 'profileName', description: localize('profileName', "Opens the provided folder or workspace with the given profile and associates the profile with the workspace. If the profile does not exist, a new empty one is created.") },
107
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
108
109
'extensions-dir': { type: 'string', deprecates: ['extensionHomePath'], cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
110
'extensions-download-dir': { type: 'string' },
111
'builtin-extensions-dir': { type: 'string' },
112
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
113
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") },
114
'category': { type: 'string', allowEmptyValue: true, cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' },
115
'install-extension': { type: 'string[]', cat: 'e', args: 'ext-id | path', description: localize('installExtension', "Installs or updates an extension. The argument is either an extension id or a path to a VSIX. The identifier of an extension is '${publisher}.${name}'. Use '--force' argument to update to latest version. To install a specific version provide '@${version}'. For example: '[email protected]'.") },
116
'pre-release': { type: 'boolean', cat: 'e', description: localize('install prerelease', "Installs the pre-release version of the extension, when using --install-extension") },
117
'uninstall-extension': { type: 'string[]', cat: 'e', args: 'ext-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
118
'update-extensions': { type: 'boolean', cat: 'e', description: localize('updateExtensions', "Update the installed extensions.") },
119
'enable-proposed-api': { type: 'string[]', allowEmptyValue: true, cat: 'e', args: 'ext-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
120
121
'add-mcp': { type: 'string[]', cat: 'm', args: 'json', description: localize('addMcp', "Adds a Model Context Protocol server definition to the user profile. Accepts JSON input in the form '{\"name\":\"server-name\",\"command\":...}'") },
122
123
'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") },
124
'verbose': { type: 'boolean', cat: 't', global: true, description: localize('verbose', "Print verbose output (implies --wait).") },
125
'log': { type: 'string[]', cat: 't', args: 'level', global: true, description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'. You can also configure the log level of an extension by passing extension id and log level in the following format: '${publisher}.${name}:${logLevel}'. For example: 'vscode.csharp:trace'. Can receive one or more such entries.") },
126
'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
127
'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup.") },
128
'prof-append-timers': { type: 'string' },
129
'prof-duration-markers': { type: 'string[]' },
130
'prof-duration-markers-file': { type: 'string' },
131
'no-cached-data': { type: 'boolean' },
132
'prof-startup-prefix': { type: 'string' },
133
'prof-v8-extensions': { type: 'boolean' },
134
'disable-extensions': { type: 'boolean', deprecates: ['disableExtensions'], cat: 't', description: localize('disableExtensions', "Disable all installed extensions. This option is not persisted and is effective only when the command opens a new window.") },
135
'disable-extension': { type: 'string[]', cat: 't', args: 'ext-id', description: localize('disableExtension', "Disable the provided extension. This option is not persisted and is effective only when the command opens a new window.") },
136
'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on | off'] },
137
138
'inspect-extensions': { type: 'string', allowEmptyValue: true, deprecates: ['debugPluginHost'], args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") },
139
'inspect-brk-extensions': { type: 'string', allowEmptyValue: true, deprecates: ['debugBrkPluginHost'], args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") },
140
'disable-lcd-text': { type: 'boolean', cat: 't', description: localize('disableLCDText', "Disable LCD font rendering.") },
141
'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") },
142
'disable-chromium-sandbox': { type: 'boolean', cat: 't', description: localize('disableChromiumSandbox', "Use this option only when there is requirement to launch the application as sudo user on Linux or when running as an elevated user in an applocker environment on Windows.") },
143
'sandbox': { type: 'boolean' },
144
'locate-shell-integration-path': { type: 'string', cat: 't', args: ['shell'], description: localize('locateShellIntegrationPath', "Print the path to a terminal shell integration script. Allowed values are 'bash', 'pwsh', 'zsh' or 'fish'.") },
145
'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
146
147
'remote': { type: 'string', allowEmptyValue: true },
148
'folder-uri': { type: 'string[]', cat: 'o', args: 'uri' },
149
'file-uri': { type: 'string[]', cat: 'o', args: 'uri' },
150
151
'locate-extension': { type: 'string[]' },
152
'extensionDevelopmentPath': { type: 'string[]' },
153
'extensionDevelopmentKind': { type: 'string[]' },
154
'extensionTestsPath': { type: 'string' },
155
'extensionEnvironment': { type: 'string' },
156
'debugId': { type: 'string' },
157
'debugRenderer': { type: 'boolean' },
158
'inspect-ptyhost': { type: 'string', allowEmptyValue: true },
159
'inspect-brk-ptyhost': { type: 'string', allowEmptyValue: true },
160
'inspect-search': { type: 'string', deprecates: ['debugSearch'], allowEmptyValue: true },
161
'inspect-brk-search': { type: 'string', deprecates: ['debugBrkSearch'], allowEmptyValue: true },
162
'inspect-sharedprocess': { type: 'string', allowEmptyValue: true },
163
'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true },
164
'export-default-configuration': { type: 'string' },
165
'export-policy-data': { type: 'string', allowEmptyValue: true },
166
'install-source': { type: 'string' },
167
'enable-smoke-test-driver': { type: 'boolean' },
168
'logExtensionHostCommunication': { type: 'boolean' },
169
'skip-release-notes': { type: 'boolean' },
170
'skip-welcome': { type: 'boolean' },
171
'disable-telemetry': { type: 'boolean' },
172
'disable-updates': { type: 'boolean' },
173
'transient': { type: 'boolean', cat: 't', description: localize('transient', "Run with temporary data and extension directories, as if launched for the first time.") },
174
'use-inmemory-secretstorage': { type: 'boolean', deprecates: ['disable-keytar'] },
175
'password-store': { type: 'string' },
176
'disable-workspace-trust': { type: 'boolean' },
177
'disable-crash-reporter': { type: 'boolean' },
178
'crash-reporter-directory': { type: 'string' },
179
'crash-reporter-id': { type: 'string' },
180
'skip-add-to-recently-opened': { type: 'boolean' },
181
'open-url': { type: 'boolean' },
182
'file-write': { type: 'boolean' },
183
'file-chmod': { type: 'boolean' },
184
'install-builtin-extension': { type: 'string[]' },
185
'force': { type: 'boolean' },
186
'do-not-sync': { type: 'boolean' },
187
'do-not-include-pack-dependencies': { type: 'boolean' },
188
'trace': { type: 'boolean' },
189
'trace-memory-infra': { type: 'boolean' },
190
'trace-category-filter': { type: 'string' },
191
'trace-options': { type: 'string' },
192
'preserve-env': { type: 'boolean' },
193
'force-user-env': { type: 'boolean' },
194
'force-disable-user-env': { type: 'boolean' },
195
'open-devtools': { type: 'boolean' },
196
'disable-gpu-sandbox': { type: 'boolean' },
197
'logsPath': { type: 'string' },
198
'__enable-file-policy': { type: 'boolean' },
199
'editSessionId': { type: 'string' },
200
'continueOn': { type: 'string' },
201
'enable-coi': { type: 'boolean' },
202
'unresponsive-sample-interval': { type: 'string' },
203
'unresponsive-sample-period': { type: 'string' },
204
'enable-rdp-display-tracking': { type: 'boolean' },
205
'disable-layout-restore': { type: 'boolean' },
206
'disable-experiments': { type: 'boolean' },
207
208
// chromium flags
209
'no-proxy-server': { type: 'boolean' },
210
// Minimist incorrectly parses keys that start with `--no`
211
// https://github.com/substack/minimist/blob/aeb3e27dae0412de5c0494e9563a5f10c82cc7a9/index.js#L118-L121
212
// If --no-sandbox is passed via cli wrapper it will be treated as --sandbox which is incorrect, we use
213
// the alias here to make sure --no-sandbox is always respected.
214
// For https://github.com/microsoft/vscode/issues/128279
215
'no-sandbox': { type: 'boolean', alias: 'sandbox' },
216
'proxy-server': { type: 'string' },
217
'proxy-bypass-list': { type: 'string' },
218
'proxy-pac-url': { type: 'string' },
219
'js-flags': { type: 'string' }, // chrome js flags
220
'inspect': { type: 'string', allowEmptyValue: true },
221
'inspect-brk': { type: 'string', allowEmptyValue: true },
222
'nolazy': { type: 'boolean' }, // node inspect
223
'force-device-scale-factor': { type: 'string' },
224
'force-renderer-accessibility': { type: 'boolean' },
225
'ignore-certificate-errors': { type: 'boolean' },
226
'allow-insecure-localhost': { type: 'boolean' },
227
'log-net-log': { type: 'string' },
228
'vmodule': { type: 'string' },
229
'_urls': { type: 'string[]' },
230
'disable-dev-shm-usage': { type: 'boolean' },
231
'profile-temp': { type: 'boolean' },
232
'ozone-platform': { type: 'string' },
233
'enable-tracing': { type: 'string' },
234
'trace-startup-format': { type: 'string' },
235
'trace-startup-file': { type: 'string' },
236
'trace-startup-duration': { type: 'string' },
237
'xdg-portal-required-version': { type: 'string' },
238
239
_: { type: 'string[]' } // main arguments
240
};
241
242
export interface ErrorReporter {
243
onUnknownOption(id: string): void;
244
onMultipleValues(id: string, usedValue: string): void;
245
onEmptyValue(id: string): void;
246
onDeprecatedOption(deprecatedId: string, message: string): void;
247
248
getSubcommandReporter?(command: string): ErrorReporter;
249
}
250
251
const ignoringReporter = {
252
onUnknownOption: () => { },
253
onMultipleValues: () => { },
254
onEmptyValue: () => { },
255
onDeprecatedOption: () => { }
256
};
257
258
export function parseArgs<T>(args: string[], options: OptionDescriptions<T>, errorReporter: ErrorReporter = ignoringReporter): T {
259
// Find the first non-option arg, which also isn't the value for a previous `--flag`
260
const firstPossibleCommand = args.find((a, i) => a.length > 0 && a[0] !== '-' && options.hasOwnProperty(a) && options[a as T].type === 'subcommand');
261
262
const alias: { [key: string]: string } = {};
263
const stringOptions: string[] = ['_'];
264
const booleanOptions: string[] = [];
265
const globalOptions: Record<string, Option<'boolean'> | Option<'string'> | Option<'string[]'>> = {};
266
let command: Subcommand<Record<string, unknown>> | undefined = undefined;
267
for (const optionId in options) {
268
const o = options[optionId];
269
if (o.type === 'subcommand') {
270
if (optionId === firstPossibleCommand) {
271
command = o;
272
}
273
} else {
274
if (o.alias) {
275
alias[optionId] = o.alias;
276
}
277
278
if (o.type === 'string' || o.type === 'string[]') {
279
stringOptions.push(optionId);
280
if (o.deprecates) {
281
stringOptions.push(...o.deprecates);
282
}
283
} else if (o.type === 'boolean') {
284
booleanOptions.push(optionId);
285
if (o.deprecates) {
286
booleanOptions.push(...o.deprecates);
287
}
288
}
289
if (o.global) {
290
globalOptions[optionId] = o;
291
}
292
}
293
}
294
if (command && firstPossibleCommand) {
295
const options: Record<string, Option<'boolean'> | Option<'string'> | Option<'string[]'> | Subcommand<Record<string, unknown>>> = globalOptions;
296
for (const optionId in command.options) {
297
options[optionId] = command.options[optionId];
298
}
299
const newArgs = args.filter(a => a !== firstPossibleCommand);
300
const reporter = errorReporter.getSubcommandReporter ? errorReporter.getSubcommandReporter(firstPossibleCommand) : undefined;
301
const subcommandOptions = parseArgs(newArgs, options as OptionDescriptions<Record<string, unknown>>, reporter);
302
// eslint-disable-next-line local/code-no-dangerous-type-assertions
303
return <T>{
304
[firstPossibleCommand]: subcommandOptions,
305
_: []
306
};
307
}
308
309
310
// remove aliases to avoid confusion
311
const parsedArgs = minimist(args, { string: stringOptions, boolean: booleanOptions, alias });
312
313
const cleanedArgs: Record<string, unknown> = {};
314
const remainingArgs: Record<string, unknown> = parsedArgs;
315
316
// https://github.com/microsoft/vscode/issues/58177, https://github.com/microsoft/vscode/issues/106617
317
cleanedArgs._ = parsedArgs._.map(arg => String(arg)).filter(arg => arg.length > 0);
318
319
delete remainingArgs._;
320
321
for (const optionId in options) {
322
const o = options[optionId];
323
if (o.type === 'subcommand') {
324
continue;
325
}
326
if (o.alias) {
327
delete remainingArgs[o.alias];
328
}
329
330
let val = remainingArgs[optionId];
331
if (o.deprecates) {
332
for (const deprecatedId of o.deprecates) {
333
if (remainingArgs.hasOwnProperty(deprecatedId)) {
334
if (!val) {
335
val = remainingArgs[deprecatedId];
336
if (val) {
337
errorReporter.onDeprecatedOption(deprecatedId, o.deprecationMessage || localize('deprecated.useInstead', 'Use {0} instead.', optionId));
338
}
339
}
340
delete remainingArgs[deprecatedId];
341
}
342
}
343
}
344
345
if (typeof val !== 'undefined') {
346
if (o.type === 'string[]') {
347
if (!Array.isArray(val)) {
348
val = [val];
349
}
350
if (!o.allowEmptyValue) {
351
const sanitized = (val as string[]).filter((v: string) => v.length > 0);
352
if (sanitized.length !== (val as string[]).length) {
353
errorReporter.onEmptyValue(optionId);
354
val = sanitized.length > 0 ? sanitized : undefined;
355
}
356
}
357
} else if (o.type === 'string') {
358
if (Array.isArray(val)) {
359
val = val.pop(); // take the last
360
errorReporter.onMultipleValues(optionId, val as string);
361
} else if (!val && !o.allowEmptyValue) {
362
errorReporter.onEmptyValue(optionId);
363
val = undefined;
364
}
365
}
366
cleanedArgs[optionId] = val;
367
368
if (o.deprecationMessage) {
369
errorReporter.onDeprecatedOption(optionId, o.deprecationMessage);
370
}
371
}
372
delete remainingArgs[optionId];
373
}
374
375
for (const key in remainingArgs) {
376
errorReporter.onUnknownOption(key);
377
}
378
379
return cleanedArgs as T;
380
}
381
382
function formatUsage(optionId: string, option: Option<'boolean'> | Option<'string'> | Option<'string[]'>) {
383
let args = '';
384
if (option.args) {
385
if (Array.isArray(option.args)) {
386
args = ` <${option.args.join('> <')}>`;
387
} else {
388
args = ` <${option.args}>`;
389
}
390
}
391
if (option.alias) {
392
return `-${option.alias} --${optionId}${args}`;
393
}
394
return `--${optionId}${args}`;
395
}
396
397
// exported only for testing
398
export function formatOptions(options: OptionDescriptions<unknown> | Record<string, Option<'boolean'> | Option<'string'> | Option<'string[]'>>, columns: number): string[] {
399
const usageTexts: [string, string][] = [];
400
for (const optionId in options) {
401
const o = options[optionId as keyof typeof options] as Option<'boolean'> | Option<'string'> | Option<'string[]'>;
402
const usageText = formatUsage(optionId, o);
403
usageTexts.push([usageText, o.description!]);
404
}
405
return formatUsageTexts(usageTexts, columns);
406
}
407
408
function formatUsageTexts(usageTexts: [string, string][], columns: number) {
409
const maxLength = usageTexts.reduce((previous, e) => Math.max(previous, e[0].length), 12);
410
const argLength = maxLength + 2/*left padding*/ + 1/*right padding*/;
411
if (columns - argLength < 25) {
412
// Use a condensed version on narrow terminals
413
return usageTexts.reduce<string[]>((r, ut) => r.concat([` ${ut[0]}`, ` ${ut[1]}`]), []);
414
}
415
const descriptionColumns = columns - argLength - 1;
416
const result: string[] = [];
417
for (const ut of usageTexts) {
418
const usage = ut[0];
419
const wrappedDescription = wrapText(ut[1], descriptionColumns);
420
const keyPadding = indent(argLength - usage.length - 2/*left padding*/);
421
result.push(' ' + usage + keyPadding + wrappedDescription[0]);
422
for (let i = 1; i < wrappedDescription.length; i++) {
423
result.push(indent(argLength) + wrappedDescription[i]);
424
}
425
}
426
return result;
427
}
428
429
function indent(count: number): string {
430
return ' '.repeat(count);
431
}
432
433
function wrapText(text: string, columns: number): string[] {
434
const lines: string[] = [];
435
while (text.length) {
436
let index = text.length < columns ? text.length : text.lastIndexOf(' ', columns);
437
if (index === 0) {
438
index = columns;
439
}
440
const line = text.slice(0, index).trim();
441
text = text.slice(index).trimStart();
442
lines.push(line);
443
}
444
return lines;
445
}
446
447
export function buildHelpMessage(productName: string, executableName: string, version: string, options: OptionDescriptions<unknown> | Record<string, Option<'boolean'> | Option<'string'> | Option<'string[]'> | Subcommand<Record<string, unknown>>>, capabilities?: { noPipe?: boolean; noInputFiles?: boolean; isChat?: boolean }): string {
448
const columns = (process.stdout).isTTY && (process.stdout).columns || 80;
449
const inputFiles = capabilities?.noInputFiles ? '' : capabilities?.isChat ? ` [${localize('cliPrompt', 'prompt')}]` : ` [${localize('paths', 'paths')}...]`;
450
const subcommand = capabilities?.isChat ? ' chat' : '';
451
452
const help = [`${productName} ${version}`];
453
help.push('');
454
help.push(`${localize('usage', "Usage")}: ${executableName}${subcommand} [${localize('options', "options")}]${inputFiles}`);
455
help.push('');
456
if (capabilities?.noPipe !== true) {
457
help.push(buildStdinMessage(executableName, capabilities?.isChat));
458
help.push('');
459
}
460
const optionsByCategory: { [P in keyof typeof helpCategories]?: Record<string, Option<'boolean'> | Option<'string'> | Option<'string[]'>> } = {};
461
const subcommands: { command: string; description: string }[] = [];
462
for (const optionId in options) {
463
const o = options[optionId as keyof typeof options] as Option<'boolean'> | Option<'string'> | Option<'string[]'> | Subcommand<Record<string, unknown>>;
464
if (o.type === 'subcommand') {
465
if (o.description) {
466
subcommands.push({ command: optionId, description: o.description });
467
}
468
} else if (o.description && o.cat) {
469
const cat = o.cat;
470
let optionsByCat = optionsByCategory[cat];
471
if (!optionsByCat) {
472
optionsByCategory[cat] = optionsByCat = {};
473
}
474
optionsByCat[optionId] = o;
475
}
476
}
477
478
for (const helpCategoryKey in optionsByCategory) {
479
const key = <keyof typeof helpCategories>helpCategoryKey;
480
481
const categoryOptions = optionsByCategory[key];
482
if (categoryOptions) {
483
help.push(helpCategories[key]);
484
help.push(...formatOptions(categoryOptions, columns));
485
help.push('');
486
}
487
}
488
489
if (subcommands.length) {
490
help.push(localize('subcommands', "Subcommands"));
491
help.push(...formatUsageTexts(subcommands.map(s => [s.command, s.description]), columns));
492
help.push('');
493
}
494
495
return help.join('\n');
496
}
497
498
export function buildStdinMessage(executableName: string, isChat?: boolean): string {
499
let example: string;
500
if (isWindows) {
501
if (isChat) {
502
example = `echo Hello World | ${executableName} chat <prompt> -`;
503
} else {
504
example = `echo Hello World | ${executableName} -`;
505
}
506
} else {
507
if (isChat) {
508
example = `ps aux | grep code | ${executableName} chat <prompt> -`;
509
} else {
510
example = `ps aux | grep code | ${executableName} -`;
511
}
512
}
513
514
return localize('stdinUsage', "To read from stdin, append '-' (e.g. '{0}')", example);
515
}
516
517
export function buildVersionMessage(version: string | undefined, commit: string | undefined): string {
518
return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`;
519
}
520
521