@theia/cli 1.34.3 → 1.34.4

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/src/theia.ts CHANGED
@@ -1,616 +1,616 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2017 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import * as fs from 'fs';
18
- import * as path from 'path';
19
- import * as temp from 'temp';
20
- import * as yargs from 'yargs';
21
- import yargsFactory = require('yargs/yargs');
22
- import { ApplicationPackageManager, rebuild } from '@theia/application-manager';
23
- import { ApplicationProps, DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package';
24
- import checkDependencies from './check-dependencies';
25
- import downloadPlugins from './download-plugins';
26
- import runTest from './run-test';
27
- import { LocalizationManager, extract } from '@theia/localization-manager';
28
-
29
- process.on('unhandledRejection', (reason, promise) => {
30
- throw reason;
31
- });
32
- process.on('uncaughtException', error => {
33
- if (error) {
34
- console.error('Uncaught Exception: ', error.toString());
35
- if (error.stack) {
36
- console.error(error.stack);
37
- }
38
- }
39
- process.exit(1);
40
- });
41
- theiaCli();
42
-
43
- function toStringArray(argv: (string | number)[]): string[];
44
- function toStringArray(argv?: (string | number)[]): string[] | undefined;
45
- function toStringArray(argv?: (string | number)[]): string[] | undefined {
46
- return argv === undefined
47
- ? undefined
48
- : argv.map(arg => String(arg));
49
- }
50
-
51
- function rebuildCommand(command: string, target: ApplicationProps.Target): yargs.CommandModule<unknown, {
52
- modules: string[]
53
- cacheRoot?: string
54
- forceAbi?: number,
55
- }> {
56
- return {
57
- command,
58
- describe: `Rebuild/revert native node modules for "${target}"`,
59
- builder: {
60
- 'cacheRoot': {
61
- type: 'string',
62
- describe: 'Root folder where to store the .browser_modules cache'
63
- },
64
- 'modules': {
65
- alias: 'm',
66
- type: 'array', // === `--modules/-m` can be specified multiple times
67
- describe: 'List of modules to rebuild/revert'
68
- },
69
- 'forceAbi': {
70
- type: 'number',
71
- describe: 'The Node ABI version to rebuild for'
72
- }
73
- },
74
- handler: ({ cacheRoot, modules, forceAbi }) => {
75
- // Note: `modules` is actually `string[] | undefined`.
76
- if (modules) {
77
- // It is ergonomic to pass arguments as --modules="a,b,c,..."
78
- // but yargs doesn't parse it this way by default.
79
- const flattened: string[] = [];
80
- for (const value of modules) {
81
- if (value.includes(',')) {
82
- flattened.push(...value.split(',').map(mod => mod.trim()));
83
- } else {
84
- flattened.push(value);
85
- }
86
- }
87
- modules = flattened;
88
- }
89
- rebuild(target, { cacheRoot, modules, forceAbi });
90
- }
91
- };
92
- }
93
-
94
- function defineCommonOptions<T>(cli: yargs.Argv<T>): yargs.Argv<T & {
95
- appTarget?: 'browser' | 'electron'
96
- }> {
97
- return cli
98
- .option('app-target', {
99
- description: 'The target application type. Overrides `theia.target` in the application\'s package.json',
100
- choices: ['browser', 'electron'] as const,
101
- });
102
- }
103
-
104
- async function theiaCli(): Promise<void> {
105
- const { version } = await fs.promises.readFile(path.join(__dirname, '../package.json'), 'utf8').then(JSON.parse);
106
- yargs.scriptName('theia').version(version);
107
- const projectPath = process.cwd();
108
- // Create a sub `yargs` parser to read `app-target` without
109
- // affecting the global `yargs` instance used by the CLI.
110
- const { appTarget } = defineCommonOptions(yargsFactory()).help(false).parse();
111
- const manager = new ApplicationPackageManager({ projectPath, appTarget });
112
- const localizationManager = new LocalizationManager();
113
- const { target } = manager.pck;
114
- defineCommonOptions(yargs)
115
- .command<{
116
- theiaArgs?: (string | number)[]
117
- }>({
118
- command: 'start [theia-args...]',
119
- describe: `Start the ${target} backend`,
120
- // Disable this command's `--help` option so that it is forwarded to Theia's CLI
121
- builder: cli => cli.help(false) as yargs.Argv,
122
- handler: async ({ theiaArgs }) => {
123
- manager.start(toStringArray(theiaArgs));
124
- }
125
- })
126
- .command({
127
- command: 'clean',
128
- describe: `Clean for the ${target} target`,
129
- handler: async () => {
130
- await manager.clean();
131
- }
132
- })
133
- .command({
134
- command: 'copy',
135
- describe: 'Copy various files from `src-gen` to `lib`',
136
- handler: async () => {
137
- await manager.copy();
138
- }
139
- })
140
- .command<{
141
- mode: 'development' | 'production',
142
- splitFrontend?: boolean
143
- }>({
144
- command: 'generate',
145
- describe: `Generate various files for the ${target} target`,
146
- builder: cli => ApplicationPackageManager.defineGeneratorOptions(cli),
147
- handler: async ({ mode, splitFrontend }) => {
148
- await manager.generate({ mode, splitFrontend });
149
- }
150
- })
151
- .command<{
152
- mode: 'development' | 'production',
153
- webpackHelp: boolean
154
- splitFrontend?: boolean
155
- webpackArgs?: (string | number)[]
156
- }>({
157
- command: 'build [webpack-args...]',
158
- describe: `Generate and bundle the ${target} frontend using webpack`,
159
- builder: cli => ApplicationPackageManager.defineGeneratorOptions(cli)
160
- .option('webpack-help' as 'webpackHelp', {
161
- boolean: true,
162
- description: 'Display Webpack\'s help',
163
- default: false
164
- }),
165
- handler: async ({ mode, splitFrontend, webpackHelp, webpackArgs = [] }) => {
166
- await manager.build(
167
- webpackHelp
168
- ? ['--help']
169
- : [
170
- // Forward the `mode` argument to Webpack too:
171
- '--mode', mode,
172
- ...toStringArray(webpackArgs)
173
- ],
174
- { mode, splitFrontend }
175
- );
176
- }
177
- })
178
- .command(rebuildCommand('rebuild', target))
179
- .command(rebuildCommand('rebuild:browser', 'browser'))
180
- .command(rebuildCommand('rebuild:electron', 'electron'))
181
- .command<{
182
- suppress: boolean
183
- }>({
184
- command: 'check:hoisted',
185
- describe: 'Check that all dependencies are hoisted',
186
- builder: {
187
- 'suppress': {
188
- alias: 's',
189
- describe: 'Suppress exiting with failure code',
190
- boolean: true,
191
- default: false
192
- }
193
- },
194
- handler: ({ suppress }) => {
195
- checkDependencies({
196
- workspaces: ['packages/*'],
197
- include: ['**'],
198
- exclude: ['.bin/**', '.cache/**'],
199
- skipHoisted: false,
200
- skipUniqueness: true,
201
- skipSingleTheiaVersion: true,
202
- suppress
203
- });
204
- }
205
- })
206
- .command<{
207
- suppress: boolean
208
- }>({
209
- command: 'check:theia-version',
210
- describe: 'Check that all dependencies have been resolved to the same Theia version',
211
- builder: {
212
- 'suppress': {
213
- alias: 's',
214
- describe: 'Suppress exiting with failure code',
215
- boolean: true,
216
- default: false
217
- }
218
- },
219
- handler: ({ suppress }) => {
220
- checkDependencies({
221
- workspaces: undefined,
222
- include: ['@theia/**'],
223
- exclude: [],
224
- skipHoisted: true,
225
- skipUniqueness: false,
226
- skipSingleTheiaVersion: false,
227
- suppress
228
- });
229
- }
230
- })
231
- .command<{
232
- workspaces: string[] | undefined,
233
- include: string[],
234
- exclude: string[],
235
- skipHoisted: boolean,
236
- skipUniqueness: boolean,
237
- skipSingleTheiaVersion: boolean,
238
- suppress: boolean
239
- }>({
240
- command: 'check:dependencies',
241
- describe: 'Check uniqueness of dependency versions or whether they are hoisted',
242
- builder: {
243
- 'workspaces': {
244
- alias: 'w',
245
- describe: 'Glob patterns of workspaces to analyze, relative to `cwd`',
246
- array: true,
247
- defaultDescription: 'All glob patterns listed in the package.json\'s workspaces',
248
- demandOption: false
249
- },
250
- 'include': {
251
- alias: 'i',
252
- describe: 'Glob pattern of dependencies\' package names to be included, e.g. -i "@theia/**"',
253
- array: true,
254
- default: ['**']
255
- },
256
- 'exclude': {
257
- alias: 'e',
258
- describe: 'Glob pattern of dependencies\' package names to be excluded',
259
- array: true,
260
- defaultDescription: 'None',
261
- default: []
262
- },
263
- 'skip-hoisted': {
264
- alias: 'h',
265
- describe: 'Skip checking whether dependencies are hoisted',
266
- boolean: true,
267
- default: false
268
- },
269
- 'skip-uniqueness': {
270
- alias: 'u',
271
- describe: 'Skip checking whether all dependencies are resolved to a unique version',
272
- boolean: true,
273
- default: false
274
- },
275
- 'skip-single-theia-version': {
276
- alias: 't',
277
- describe: 'Skip checking whether all @theia/* dependencies are resolved to a single version',
278
- boolean: true,
279
- default: false
280
- },
281
- 'suppress': {
282
- alias: 's',
283
- describe: 'Suppress exiting with failure code',
284
- boolean: true,
285
- default: false
286
- }
287
- },
288
- handler: ({
289
- workspaces,
290
- include,
291
- exclude,
292
- skipHoisted,
293
- skipUniqueness,
294
- skipSingleTheiaVersion,
295
- suppress
296
- }) => {
297
- checkDependencies({
298
- workspaces,
299
- include,
300
- exclude,
301
- skipHoisted,
302
- skipUniqueness,
303
- skipSingleTheiaVersion,
304
- suppress
305
- });
306
- }
307
- })
308
- .command<{
309
- packed: boolean
310
- ignoreErrors: boolean
311
- apiVersion: string
312
- apiUrl: string
313
- parallel: boolean
314
- proxyUrl?: string
315
- proxyAuthentification?: string
316
- strictSsl: boolean
317
- }>({
318
- command: 'download:plugins',
319
- describe: 'Download defined external plugins',
320
- builder: {
321
- 'packed': {
322
- alias: 'p',
323
- describe: 'Controls whether to pack or unpack plugins',
324
- boolean: true,
325
- default: false,
326
- },
327
- 'ignore-errors': {
328
- alias: 'i',
329
- describe: 'Ignore errors while downloading plugins',
330
- boolean: true,
331
- default: false,
332
- },
333
- 'api-version': {
334
- alias: 'v',
335
- describe: 'Supported API version for plugins',
336
- default: DEFAULT_SUPPORTED_API_VERSION
337
- },
338
- 'api-url': {
339
- alias: 'u',
340
- describe: 'Open-VSX Registry API URL',
341
- default: 'https://open-vsx.org/api'
342
- },
343
- 'parallel': {
344
- describe: 'Download in parallel',
345
- boolean: true,
346
- default: true
347
- },
348
- 'rate-limit': {
349
- describe: 'Amount of maximum open-vsx requests per second',
350
- number: true,
351
- default: 15
352
- },
353
- 'proxy-url': {
354
- describe: 'Proxy URL'
355
- },
356
- 'proxy-authentification': {
357
- describe: 'Proxy authentification information'
358
- },
359
- 'strict-ssl': {
360
- describe: 'Whether to enable strict SSL mode',
361
- boolean: true,
362
- default: false
363
- }
364
- },
365
- handler: async args => {
366
- await downloadPlugins(args);
367
- },
368
- })
369
- .command<{
370
- freeApi?: boolean,
371
- deeplKey: string,
372
- file: string,
373
- languages: string[],
374
- sourceLanguage?: string
375
- }>({
376
- command: 'nls-localize [languages...]',
377
- describe: 'Localize json files using the DeepL API',
378
- builder: {
379
- 'file': {
380
- alias: 'f',
381
- describe: 'The source file which should be translated',
382
- demandOption: true
383
- },
384
- 'deepl-key': {
385
- alias: 'k',
386
- describe: 'DeepL key used for API access. See https://www.deepl.com/docs-api for more information',
387
- demandOption: true
388
- },
389
- 'free-api': {
390
- describe: 'Indicates whether the specified DeepL API key belongs to the free API',
391
- boolean: true,
392
- default: false,
393
- },
394
- 'source-language': {
395
- alias: 's',
396
- describe: 'The source language of the translation file'
397
- }
398
- },
399
- handler: async ({ freeApi, deeplKey, file, sourceLanguage, languages = [] }) => {
400
- await localizationManager.localize({
401
- sourceFile: file,
402
- freeApi: freeApi ?? true,
403
- authKey: deeplKey,
404
- targetLanguages: languages,
405
- sourceLanguage
406
- });
407
- }
408
- })
409
- .command<{
410
- root: string,
411
- output: string,
412
- merge: boolean,
413
- exclude?: string,
414
- logs?: string,
415
- files?: string[],
416
- quiet: boolean
417
- }>({
418
- command: 'nls-extract',
419
- describe: 'Extract translation key/value pairs from source code',
420
- builder: {
421
- 'output': {
422
- alias: 'o',
423
- describe: 'Output file for the extracted translations',
424
- demandOption: true
425
- },
426
- 'root': {
427
- alias: 'r',
428
- describe: 'The directory which contains the source code',
429
- default: '.'
430
- },
431
- 'merge': {
432
- alias: 'm',
433
- describe: 'Whether to merge new with existing translation values',
434
- boolean: true,
435
- default: false
436
- },
437
- 'exclude': {
438
- alias: 'e',
439
- describe: 'Allows to exclude translation keys starting with this value'
440
- },
441
- 'files': {
442
- alias: 'f',
443
- describe: 'Glob pattern matching the files to extract from (starting from --root).',
444
- array: true
445
- },
446
- 'logs': {
447
- alias: 'l',
448
- describe: 'File path to a log file'
449
- },
450
- 'quiet': {
451
- alias: 'q',
452
- describe: 'Prevents errors from being logged to console',
453
- boolean: true,
454
- default: false
455
- }
456
- },
457
- handler: async options => {
458
- await extract(options);
459
- }
460
- })
461
- .command<{
462
- testInspect: boolean,
463
- testExtension: string[],
464
- testFile: string[],
465
- testIgnore: string[],
466
- testRecursive: boolean,
467
- testSort: boolean,
468
- testSpec: string[],
469
- testCoverage: boolean
470
- theiaArgs?: (string | number)[]
471
- }>({
472
- command: 'test [theia-args...]',
473
- builder: {
474
- 'test-inspect': {
475
- describe: 'Whether to auto-open a DevTools panel for test page.',
476
- boolean: true,
477
- default: false
478
- },
479
- 'test-extension': {
480
- describe: 'Test file extension(s) to load',
481
- array: true,
482
- default: ['js']
483
- },
484
- 'test-file': {
485
- describe: 'Specify test file(s) to be loaded prior to root suite execution',
486
- array: true,
487
- default: []
488
- },
489
- 'test-ignore': {
490
- describe: 'Ignore test file(s) or glob pattern(s)',
491
- array: true,
492
- default: []
493
- },
494
- 'test-recursive': {
495
- describe: 'Look for tests in subdirectories',
496
- boolean: true,
497
- default: false
498
- },
499
- 'test-sort': {
500
- describe: 'Sort test files',
501
- boolean: true,
502
- default: false
503
- },
504
- 'test-spec': {
505
- describe: 'One or more test files, directories, or globs to test',
506
- array: true,
507
- default: ['test']
508
- },
509
- 'test-coverage': {
510
- describe: 'Report test coverage consumable by istanbul',
511
- boolean: true,
512
- default: false
513
- }
514
- },
515
- handler: async ({ testInspect, testExtension, testFile, testIgnore, testRecursive, testSort, testSpec, testCoverage, theiaArgs }) => {
516
- if (!process.env.THEIA_CONFIG_DIR) {
517
- process.env.THEIA_CONFIG_DIR = temp.track().mkdirSync('theia-test-config-dir');
518
- }
519
- await runTest({
520
- start: () => new Promise((resolve, reject) => {
521
- const serverProcess = manager.start(toStringArray(theiaArgs));
522
- serverProcess.on('message', resolve);
523
- serverProcess.on('error', reject);
524
- serverProcess.on('close', (code, signal) => reject(`Server process exited unexpectedly: ${code ?? signal}`));
525
- }),
526
- launch: {
527
- args: ['--no-sandbox'],
528
- devtools: testInspect
529
- },
530
- files: {
531
- extension: testExtension,
532
- file: testFile,
533
- ignore: testIgnore,
534
- recursive: testRecursive,
535
- sort: testSort,
536
- spec: testSpec
537
- },
538
- coverage: testCoverage
539
- });
540
- }
541
- })
542
- .command<{
543
- electronVersion?: string
544
- electronDist?: string
545
- ffmpegPath?: string
546
- platform?: NodeJS.Platform
547
- }>({
548
- command: 'ffmpeg:replace [ffmpeg-path]',
549
- describe: '',
550
- builder: {
551
- 'electronDist': {
552
- description: 'Electron distribution location.',
553
- },
554
- 'electronVersion': {
555
- description: 'Electron version for which to pull the "clean" ffmpeg library.',
556
- },
557
- 'ffmpegPath': {
558
- description: 'Absolute path to the ffmpeg shared library.',
559
- },
560
- 'platform': {
561
- description: 'Dictates where the library is located within the Electron distribution.',
562
- choices: ['darwin', 'linux', 'win32'] as NodeJS.Platform[],
563
- },
564
- },
565
- handler: async options => {
566
- const ffmpeg = await import('@theia/ffmpeg');
567
- await ffmpeg.replaceFfmpeg(options);
568
- },
569
- })
570
- .command<{
571
- electronDist?: string
572
- ffmpegPath?: string
573
- json?: boolean
574
- platform?: NodeJS.Platform
575
- }>({
576
- command: 'ffmpeg:check [ffmpeg-path]',
577
- describe: '(electron-only) Check that ffmpeg doesn\'t contain proprietary codecs',
578
- builder: {
579
- 'electronDist': {
580
- description: 'Electron distribution location',
581
- },
582
- 'ffmpegPath': {
583
- describe: 'Absolute path to the ffmpeg shared library',
584
- },
585
- 'json': {
586
- description: 'Output the found codecs as JSON on stdout',
587
- boolean: true,
588
- },
589
- 'platform': {
590
- description: 'Dictates where the library is located within the Electron distribution',
591
- choices: ['darwin', 'linux', 'win32'] as NodeJS.Platform[],
592
- },
593
- },
594
- handler: async options => {
595
- const ffmpeg = await import('@theia/ffmpeg');
596
- await ffmpeg.checkFfmpeg(options);
597
- },
598
- })
599
- .parserConfiguration({
600
- 'unknown-options-as-args': true,
601
- })
602
- .strictCommands()
603
- .demandCommand(1, 'Please run a command')
604
- .fail((msg, err, cli) => {
605
- process.exitCode = 1;
606
- if (err) {
607
- // One of the handlers threw an error:
608
- console.error(err);
609
- } else {
610
- // Yargs detected a problem with commands and/or arguments while parsing:
611
- cli.showHelp();
612
- console.error(msg);
613
- }
614
- })
615
- .parse();
616
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2017 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import * as temp from 'temp';
20
+ import * as yargs from 'yargs';
21
+ import yargsFactory = require('yargs/yargs');
22
+ import { ApplicationPackageManager, rebuild } from '@theia/application-manager';
23
+ import { ApplicationProps, DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package';
24
+ import checkDependencies from './check-dependencies';
25
+ import downloadPlugins from './download-plugins';
26
+ import runTest from './run-test';
27
+ import { LocalizationManager, extract } from '@theia/localization-manager';
28
+
29
+ process.on('unhandledRejection', (reason, promise) => {
30
+ throw reason;
31
+ });
32
+ process.on('uncaughtException', error => {
33
+ if (error) {
34
+ console.error('Uncaught Exception: ', error.toString());
35
+ if (error.stack) {
36
+ console.error(error.stack);
37
+ }
38
+ }
39
+ process.exit(1);
40
+ });
41
+ theiaCli();
42
+
43
+ function toStringArray(argv: (string | number)[]): string[];
44
+ function toStringArray(argv?: (string | number)[]): string[] | undefined;
45
+ function toStringArray(argv?: (string | number)[]): string[] | undefined {
46
+ return argv === undefined
47
+ ? undefined
48
+ : argv.map(arg => String(arg));
49
+ }
50
+
51
+ function rebuildCommand(command: string, target: ApplicationProps.Target): yargs.CommandModule<unknown, {
52
+ modules: string[]
53
+ cacheRoot?: string
54
+ forceAbi?: number,
55
+ }> {
56
+ return {
57
+ command,
58
+ describe: `Rebuild/revert native node modules for "${target}"`,
59
+ builder: {
60
+ 'cacheRoot': {
61
+ type: 'string',
62
+ describe: 'Root folder where to store the .browser_modules cache'
63
+ },
64
+ 'modules': {
65
+ alias: 'm',
66
+ type: 'array', // === `--modules/-m` can be specified multiple times
67
+ describe: 'List of modules to rebuild/revert'
68
+ },
69
+ 'forceAbi': {
70
+ type: 'number',
71
+ describe: 'The Node ABI version to rebuild for'
72
+ }
73
+ },
74
+ handler: ({ cacheRoot, modules, forceAbi }) => {
75
+ // Note: `modules` is actually `string[] | undefined`.
76
+ if (modules) {
77
+ // It is ergonomic to pass arguments as --modules="a,b,c,..."
78
+ // but yargs doesn't parse it this way by default.
79
+ const flattened: string[] = [];
80
+ for (const value of modules) {
81
+ if (value.includes(',')) {
82
+ flattened.push(...value.split(',').map(mod => mod.trim()));
83
+ } else {
84
+ flattened.push(value);
85
+ }
86
+ }
87
+ modules = flattened;
88
+ }
89
+ rebuild(target, { cacheRoot, modules, forceAbi });
90
+ }
91
+ };
92
+ }
93
+
94
+ function defineCommonOptions<T>(cli: yargs.Argv<T>): yargs.Argv<T & {
95
+ appTarget?: 'browser' | 'electron'
96
+ }> {
97
+ return cli
98
+ .option('app-target', {
99
+ description: 'The target application type. Overrides `theia.target` in the application\'s package.json',
100
+ choices: ['browser', 'electron'] as const,
101
+ });
102
+ }
103
+
104
+ async function theiaCli(): Promise<void> {
105
+ const { version } = await fs.promises.readFile(path.join(__dirname, '../package.json'), 'utf8').then(JSON.parse);
106
+ yargs.scriptName('theia').version(version);
107
+ const projectPath = process.cwd();
108
+ // Create a sub `yargs` parser to read `app-target` without
109
+ // affecting the global `yargs` instance used by the CLI.
110
+ const { appTarget } = defineCommonOptions(yargsFactory()).help(false).parse();
111
+ const manager = new ApplicationPackageManager({ projectPath, appTarget });
112
+ const localizationManager = new LocalizationManager();
113
+ const { target } = manager.pck;
114
+ defineCommonOptions(yargs)
115
+ .command<{
116
+ theiaArgs?: (string | number)[]
117
+ }>({
118
+ command: 'start [theia-args...]',
119
+ describe: `Start the ${target} backend`,
120
+ // Disable this command's `--help` option so that it is forwarded to Theia's CLI
121
+ builder: cli => cli.help(false) as yargs.Argv,
122
+ handler: async ({ theiaArgs }) => {
123
+ manager.start(toStringArray(theiaArgs));
124
+ }
125
+ })
126
+ .command({
127
+ command: 'clean',
128
+ describe: `Clean for the ${target} target`,
129
+ handler: async () => {
130
+ await manager.clean();
131
+ }
132
+ })
133
+ .command({
134
+ command: 'copy',
135
+ describe: 'Copy various files from `src-gen` to `lib`',
136
+ handler: async () => {
137
+ await manager.copy();
138
+ }
139
+ })
140
+ .command<{
141
+ mode: 'development' | 'production',
142
+ splitFrontend?: boolean
143
+ }>({
144
+ command: 'generate',
145
+ describe: `Generate various files for the ${target} target`,
146
+ builder: cli => ApplicationPackageManager.defineGeneratorOptions(cli),
147
+ handler: async ({ mode, splitFrontend }) => {
148
+ await manager.generate({ mode, splitFrontend });
149
+ }
150
+ })
151
+ .command<{
152
+ mode: 'development' | 'production',
153
+ webpackHelp: boolean
154
+ splitFrontend?: boolean
155
+ webpackArgs?: (string | number)[]
156
+ }>({
157
+ command: 'build [webpack-args...]',
158
+ describe: `Generate and bundle the ${target} frontend using webpack`,
159
+ builder: cli => ApplicationPackageManager.defineGeneratorOptions(cli)
160
+ .option('webpack-help' as 'webpackHelp', {
161
+ boolean: true,
162
+ description: 'Display Webpack\'s help',
163
+ default: false
164
+ }),
165
+ handler: async ({ mode, splitFrontend, webpackHelp, webpackArgs = [] }) => {
166
+ await manager.build(
167
+ webpackHelp
168
+ ? ['--help']
169
+ : [
170
+ // Forward the `mode` argument to Webpack too:
171
+ '--mode', mode,
172
+ ...toStringArray(webpackArgs)
173
+ ],
174
+ { mode, splitFrontend }
175
+ );
176
+ }
177
+ })
178
+ .command(rebuildCommand('rebuild', target))
179
+ .command(rebuildCommand('rebuild:browser', 'browser'))
180
+ .command(rebuildCommand('rebuild:electron', 'electron'))
181
+ .command<{
182
+ suppress: boolean
183
+ }>({
184
+ command: 'check:hoisted',
185
+ describe: 'Check that all dependencies are hoisted',
186
+ builder: {
187
+ 'suppress': {
188
+ alias: 's',
189
+ describe: 'Suppress exiting with failure code',
190
+ boolean: true,
191
+ default: false
192
+ }
193
+ },
194
+ handler: ({ suppress }) => {
195
+ checkDependencies({
196
+ workspaces: ['packages/*'],
197
+ include: ['**'],
198
+ exclude: ['.bin/**', '.cache/**'],
199
+ skipHoisted: false,
200
+ skipUniqueness: true,
201
+ skipSingleTheiaVersion: true,
202
+ suppress
203
+ });
204
+ }
205
+ })
206
+ .command<{
207
+ suppress: boolean
208
+ }>({
209
+ command: 'check:theia-version',
210
+ describe: 'Check that all dependencies have been resolved to the same Theia version',
211
+ builder: {
212
+ 'suppress': {
213
+ alias: 's',
214
+ describe: 'Suppress exiting with failure code',
215
+ boolean: true,
216
+ default: false
217
+ }
218
+ },
219
+ handler: ({ suppress }) => {
220
+ checkDependencies({
221
+ workspaces: undefined,
222
+ include: ['@theia/**'],
223
+ exclude: [],
224
+ skipHoisted: true,
225
+ skipUniqueness: false,
226
+ skipSingleTheiaVersion: false,
227
+ suppress
228
+ });
229
+ }
230
+ })
231
+ .command<{
232
+ workspaces: string[] | undefined,
233
+ include: string[],
234
+ exclude: string[],
235
+ skipHoisted: boolean,
236
+ skipUniqueness: boolean,
237
+ skipSingleTheiaVersion: boolean,
238
+ suppress: boolean
239
+ }>({
240
+ command: 'check:dependencies',
241
+ describe: 'Check uniqueness of dependency versions or whether they are hoisted',
242
+ builder: {
243
+ 'workspaces': {
244
+ alias: 'w',
245
+ describe: 'Glob patterns of workspaces to analyze, relative to `cwd`',
246
+ array: true,
247
+ defaultDescription: 'All glob patterns listed in the package.json\'s workspaces',
248
+ demandOption: false
249
+ },
250
+ 'include': {
251
+ alias: 'i',
252
+ describe: 'Glob pattern of dependencies\' package names to be included, e.g. -i "@theia/**"',
253
+ array: true,
254
+ default: ['**']
255
+ },
256
+ 'exclude': {
257
+ alias: 'e',
258
+ describe: 'Glob pattern of dependencies\' package names to be excluded',
259
+ array: true,
260
+ defaultDescription: 'None',
261
+ default: []
262
+ },
263
+ 'skip-hoisted': {
264
+ alias: 'h',
265
+ describe: 'Skip checking whether dependencies are hoisted',
266
+ boolean: true,
267
+ default: false
268
+ },
269
+ 'skip-uniqueness': {
270
+ alias: 'u',
271
+ describe: 'Skip checking whether all dependencies are resolved to a unique version',
272
+ boolean: true,
273
+ default: false
274
+ },
275
+ 'skip-single-theia-version': {
276
+ alias: 't',
277
+ describe: 'Skip checking whether all @theia/* dependencies are resolved to a single version',
278
+ boolean: true,
279
+ default: false
280
+ },
281
+ 'suppress': {
282
+ alias: 's',
283
+ describe: 'Suppress exiting with failure code',
284
+ boolean: true,
285
+ default: false
286
+ }
287
+ },
288
+ handler: ({
289
+ workspaces,
290
+ include,
291
+ exclude,
292
+ skipHoisted,
293
+ skipUniqueness,
294
+ skipSingleTheiaVersion,
295
+ suppress
296
+ }) => {
297
+ checkDependencies({
298
+ workspaces,
299
+ include,
300
+ exclude,
301
+ skipHoisted,
302
+ skipUniqueness,
303
+ skipSingleTheiaVersion,
304
+ suppress
305
+ });
306
+ }
307
+ })
308
+ .command<{
309
+ packed: boolean
310
+ ignoreErrors: boolean
311
+ apiVersion: string
312
+ apiUrl: string
313
+ parallel: boolean
314
+ proxyUrl?: string
315
+ proxyAuthentification?: string
316
+ strictSsl: boolean
317
+ }>({
318
+ command: 'download:plugins',
319
+ describe: 'Download defined external plugins',
320
+ builder: {
321
+ 'packed': {
322
+ alias: 'p',
323
+ describe: 'Controls whether to pack or unpack plugins',
324
+ boolean: true,
325
+ default: false,
326
+ },
327
+ 'ignore-errors': {
328
+ alias: 'i',
329
+ describe: 'Ignore errors while downloading plugins',
330
+ boolean: true,
331
+ default: false,
332
+ },
333
+ 'api-version': {
334
+ alias: 'v',
335
+ describe: 'Supported API version for plugins',
336
+ default: DEFAULT_SUPPORTED_API_VERSION
337
+ },
338
+ 'api-url': {
339
+ alias: 'u',
340
+ describe: 'Open-VSX Registry API URL',
341
+ default: 'https://open-vsx.org/api'
342
+ },
343
+ 'parallel': {
344
+ describe: 'Download in parallel',
345
+ boolean: true,
346
+ default: true
347
+ },
348
+ 'rate-limit': {
349
+ describe: 'Amount of maximum open-vsx requests per second',
350
+ number: true,
351
+ default: 15
352
+ },
353
+ 'proxy-url': {
354
+ describe: 'Proxy URL'
355
+ },
356
+ 'proxy-authentification': {
357
+ describe: 'Proxy authentification information'
358
+ },
359
+ 'strict-ssl': {
360
+ describe: 'Whether to enable strict SSL mode',
361
+ boolean: true,
362
+ default: false
363
+ }
364
+ },
365
+ handler: async args => {
366
+ await downloadPlugins(args);
367
+ },
368
+ })
369
+ .command<{
370
+ freeApi?: boolean,
371
+ deeplKey: string,
372
+ file: string,
373
+ languages: string[],
374
+ sourceLanguage?: string
375
+ }>({
376
+ command: 'nls-localize [languages...]',
377
+ describe: 'Localize json files using the DeepL API',
378
+ builder: {
379
+ 'file': {
380
+ alias: 'f',
381
+ describe: 'The source file which should be translated',
382
+ demandOption: true
383
+ },
384
+ 'deepl-key': {
385
+ alias: 'k',
386
+ describe: 'DeepL key used for API access. See https://www.deepl.com/docs-api for more information',
387
+ demandOption: true
388
+ },
389
+ 'free-api': {
390
+ describe: 'Indicates whether the specified DeepL API key belongs to the free API',
391
+ boolean: true,
392
+ default: false,
393
+ },
394
+ 'source-language': {
395
+ alias: 's',
396
+ describe: 'The source language of the translation file'
397
+ }
398
+ },
399
+ handler: async ({ freeApi, deeplKey, file, sourceLanguage, languages = [] }) => {
400
+ await localizationManager.localize({
401
+ sourceFile: file,
402
+ freeApi: freeApi ?? true,
403
+ authKey: deeplKey,
404
+ targetLanguages: languages,
405
+ sourceLanguage
406
+ });
407
+ }
408
+ })
409
+ .command<{
410
+ root: string,
411
+ output: string,
412
+ merge: boolean,
413
+ exclude?: string,
414
+ logs?: string,
415
+ files?: string[],
416
+ quiet: boolean
417
+ }>({
418
+ command: 'nls-extract',
419
+ describe: 'Extract translation key/value pairs from source code',
420
+ builder: {
421
+ 'output': {
422
+ alias: 'o',
423
+ describe: 'Output file for the extracted translations',
424
+ demandOption: true
425
+ },
426
+ 'root': {
427
+ alias: 'r',
428
+ describe: 'The directory which contains the source code',
429
+ default: '.'
430
+ },
431
+ 'merge': {
432
+ alias: 'm',
433
+ describe: 'Whether to merge new with existing translation values',
434
+ boolean: true,
435
+ default: false
436
+ },
437
+ 'exclude': {
438
+ alias: 'e',
439
+ describe: 'Allows to exclude translation keys starting with this value'
440
+ },
441
+ 'files': {
442
+ alias: 'f',
443
+ describe: 'Glob pattern matching the files to extract from (starting from --root).',
444
+ array: true
445
+ },
446
+ 'logs': {
447
+ alias: 'l',
448
+ describe: 'File path to a log file'
449
+ },
450
+ 'quiet': {
451
+ alias: 'q',
452
+ describe: 'Prevents errors from being logged to console',
453
+ boolean: true,
454
+ default: false
455
+ }
456
+ },
457
+ handler: async options => {
458
+ await extract(options);
459
+ }
460
+ })
461
+ .command<{
462
+ testInspect: boolean,
463
+ testExtension: string[],
464
+ testFile: string[],
465
+ testIgnore: string[],
466
+ testRecursive: boolean,
467
+ testSort: boolean,
468
+ testSpec: string[],
469
+ testCoverage: boolean
470
+ theiaArgs?: (string | number)[]
471
+ }>({
472
+ command: 'test [theia-args...]',
473
+ builder: {
474
+ 'test-inspect': {
475
+ describe: 'Whether to auto-open a DevTools panel for test page.',
476
+ boolean: true,
477
+ default: false
478
+ },
479
+ 'test-extension': {
480
+ describe: 'Test file extension(s) to load',
481
+ array: true,
482
+ default: ['js']
483
+ },
484
+ 'test-file': {
485
+ describe: 'Specify test file(s) to be loaded prior to root suite execution',
486
+ array: true,
487
+ default: []
488
+ },
489
+ 'test-ignore': {
490
+ describe: 'Ignore test file(s) or glob pattern(s)',
491
+ array: true,
492
+ default: []
493
+ },
494
+ 'test-recursive': {
495
+ describe: 'Look for tests in subdirectories',
496
+ boolean: true,
497
+ default: false
498
+ },
499
+ 'test-sort': {
500
+ describe: 'Sort test files',
501
+ boolean: true,
502
+ default: false
503
+ },
504
+ 'test-spec': {
505
+ describe: 'One or more test files, directories, or globs to test',
506
+ array: true,
507
+ default: ['test']
508
+ },
509
+ 'test-coverage': {
510
+ describe: 'Report test coverage consumable by istanbul',
511
+ boolean: true,
512
+ default: false
513
+ }
514
+ },
515
+ handler: async ({ testInspect, testExtension, testFile, testIgnore, testRecursive, testSort, testSpec, testCoverage, theiaArgs }) => {
516
+ if (!process.env.THEIA_CONFIG_DIR) {
517
+ process.env.THEIA_CONFIG_DIR = temp.track().mkdirSync('theia-test-config-dir');
518
+ }
519
+ await runTest({
520
+ start: () => new Promise((resolve, reject) => {
521
+ const serverProcess = manager.start(toStringArray(theiaArgs));
522
+ serverProcess.on('message', resolve);
523
+ serverProcess.on('error', reject);
524
+ serverProcess.on('close', (code, signal) => reject(`Server process exited unexpectedly: ${code ?? signal}`));
525
+ }),
526
+ launch: {
527
+ args: ['--no-sandbox'],
528
+ devtools: testInspect
529
+ },
530
+ files: {
531
+ extension: testExtension,
532
+ file: testFile,
533
+ ignore: testIgnore,
534
+ recursive: testRecursive,
535
+ sort: testSort,
536
+ spec: testSpec
537
+ },
538
+ coverage: testCoverage
539
+ });
540
+ }
541
+ })
542
+ .command<{
543
+ electronVersion?: string
544
+ electronDist?: string
545
+ ffmpegPath?: string
546
+ platform?: NodeJS.Platform
547
+ }>({
548
+ command: 'ffmpeg:replace [ffmpeg-path]',
549
+ describe: '',
550
+ builder: {
551
+ 'electronDist': {
552
+ description: 'Electron distribution location.',
553
+ },
554
+ 'electronVersion': {
555
+ description: 'Electron version for which to pull the "clean" ffmpeg library.',
556
+ },
557
+ 'ffmpegPath': {
558
+ description: 'Absolute path to the ffmpeg shared library.',
559
+ },
560
+ 'platform': {
561
+ description: 'Dictates where the library is located within the Electron distribution.',
562
+ choices: ['darwin', 'linux', 'win32'] as NodeJS.Platform[],
563
+ },
564
+ },
565
+ handler: async options => {
566
+ const ffmpeg = await import('@theia/ffmpeg');
567
+ await ffmpeg.replaceFfmpeg(options);
568
+ },
569
+ })
570
+ .command<{
571
+ electronDist?: string
572
+ ffmpegPath?: string
573
+ json?: boolean
574
+ platform?: NodeJS.Platform
575
+ }>({
576
+ command: 'ffmpeg:check [ffmpeg-path]',
577
+ describe: '(electron-only) Check that ffmpeg doesn\'t contain proprietary codecs',
578
+ builder: {
579
+ 'electronDist': {
580
+ description: 'Electron distribution location',
581
+ },
582
+ 'ffmpegPath': {
583
+ describe: 'Absolute path to the ffmpeg shared library',
584
+ },
585
+ 'json': {
586
+ description: 'Output the found codecs as JSON on stdout',
587
+ boolean: true,
588
+ },
589
+ 'platform': {
590
+ description: 'Dictates where the library is located within the Electron distribution',
591
+ choices: ['darwin', 'linux', 'win32'] as NodeJS.Platform[],
592
+ },
593
+ },
594
+ handler: async options => {
595
+ const ffmpeg = await import('@theia/ffmpeg');
596
+ await ffmpeg.checkFfmpeg(options);
597
+ },
598
+ })
599
+ .parserConfiguration({
600
+ 'unknown-options-as-args': true,
601
+ })
602
+ .strictCommands()
603
+ .demandCommand(1, 'Please run a command')
604
+ .fail((msg, err, cli) => {
605
+ process.exitCode = 1;
606
+ if (err) {
607
+ // One of the handlers threw an error:
608
+ console.error(err);
609
+ } else {
610
+ // Yargs detected a problem with commands and/or arguments while parsing:
611
+ cli.showHelp();
612
+ console.error(msg);
613
+ }
614
+ })
615
+ .parse();
616
+ }