@theia/core 1.72.0-next.5 → 1.72.0-next.50
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/README.md +3 -3
- package/lib/browser/catalog.json +474 -30
- package/lib/browser/frontend-application-module.d.ts +3 -0
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +5 -2
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/hover-service.d.ts +4 -4
- package/lib/browser/hover-service.d.ts.map +1 -1
- package/lib/browser/hover-service.js +10 -7
- package/lib/browser/hover-service.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts +15 -0
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts.map +1 -0
- package/lib/browser/markdown-rendering/markdown-link-handler.js +60 -0
- package/lib/browser/markdown-rendering/markdown-link-handler.js.map +1 -0
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts +5 -0
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.js +6 -1
- package/lib/browser/markdown-rendering/markdown-renderer.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/common/markdown-rendering/markdown-string.d.ts +7 -0
- package/lib/common/markdown-rendering/markdown-string.d.ts.map +1 -1
- package/lib/common/markdown-rendering/markdown-string.js +34 -2
- package/lib/common/markdown-rendering/markdown-string.js.map +1 -1
- package/lib/common/markdown-rendering/markdown-string.spec.d.ts +2 -0
- package/lib/common/markdown-rendering/markdown-string.spec.d.ts.map +1 -0
- package/lib/common/markdown-rendering/markdown-string.spec.js +47 -0
- package/lib/common/markdown-rendering/markdown-string.spec.js.map +1 -0
- package/lib/node/backend-application-module.d.ts.map +1 -1
- package/lib/node/backend-application-module.js +7 -0
- package/lib/node/backend-application-module.js.map +1 -1
- package/lib/node/backend-application.d.ts +43 -3
- package/lib/node/backend-application.d.ts.map +1 -1
- package/lib/node/backend-application.js +82 -15
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/backend-application.spec.d.ts +2 -0
- package/lib/node/backend-application.spec.d.ts.map +1 -0
- package/lib/node/backend-application.spec.js +252 -0
- package/lib/node/backend-application.spec.js.map +1 -0
- package/lib/node/logger-cli-contribution.spec.js +9 -9
- package/lib/node/logger-cli-contribution.spec.js.map +1 -1
- package/package.json +16 -16
- package/src/browser/frontend-application-module.ts +6 -3
- package/src/browser/hover-service.ts +9 -7
- package/src/browser/index.ts +1 -0
- package/src/browser/markdown-rendering/markdown-link-handler.ts +61 -0
- package/src/browser/markdown-rendering/markdown-renderer.ts +6 -0
- package/src/browser/style/index.css +31 -32
- package/src/browser/style/scrollbars.css +0 -2
- package/src/browser/style/select-component.css +6 -3
- package/src/browser/widgets/select-component.tsx +1 -1
- package/src/common/markdown-rendering/markdown-string.spec.ts +50 -0
- package/src/common/markdown-rendering/markdown-string.ts +34 -2
- package/src/node/backend-application-module.ts +11 -1
- package/src/node/backend-application.spec.ts +310 -0
- package/src/node/backend-application.ts +104 -19
- package/src/node/logger-cli-contribution.spec.ts +9 -9
|
@@ -21,10 +21,10 @@ import * as https from 'https';
|
|
|
21
21
|
import * as express from 'express';
|
|
22
22
|
import * as yargs from 'yargs';
|
|
23
23
|
import * as fs from 'fs-extra';
|
|
24
|
-
import { inject, named, injectable, postConstruct } from 'inversify';
|
|
24
|
+
import { inject, named, injectable, type interfaces, postConstruct } from 'inversify';
|
|
25
25
|
import { ContributionProvider, LogLevel, MaybePromise, MeasurementContext, Stopwatch } from '../common';
|
|
26
26
|
import { CliContribution } from './cli';
|
|
27
|
-
import { Deferred } from '../common/promise-util';
|
|
27
|
+
import { Deferred, timeoutReject } from '../common/promise-util';
|
|
28
28
|
import { environment } from '../common/index';
|
|
29
29
|
import { AddressInfo } from 'net';
|
|
30
30
|
import { ProcessUtils } from './process-utils';
|
|
@@ -35,12 +35,19 @@ import { ProcessUtils } from './process-utils';
|
|
|
35
35
|
*/
|
|
36
36
|
export const BackendApplicationPath = process.env.THEIA_APP_PROJECT_PATH || process.cwd();
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Private injection token for the backend's root Inversify {@link Container}.
|
|
40
|
+
*/
|
|
41
|
+
export const RootContainer = Symbol('RootContainer');
|
|
42
|
+
|
|
38
43
|
export type DnsResultOrder = 'ipv4first' | 'verbatim' | 'nodeDefault';
|
|
39
44
|
|
|
40
45
|
const APP_PROJECT_PATH = 'app-project-path';
|
|
41
46
|
|
|
42
47
|
const TIMER_WARNING_THRESHOLD = 50;
|
|
43
48
|
|
|
49
|
+
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
50
|
+
|
|
44
51
|
const DEFAULT_PORT = environment.electron.is() ? 0 : 3000;
|
|
45
52
|
const DEFAULT_HOST = 'localhost';
|
|
46
53
|
const DEFAULT_SSL = false;
|
|
@@ -103,12 +110,23 @@ export interface BackendApplicationContribution {
|
|
|
103
110
|
onStart?(server: http.Server | https.Server): MaybePromise<void>;
|
|
104
111
|
|
|
105
112
|
/**
|
|
106
|
-
* Called when the backend application shuts down.
|
|
107
|
-
*
|
|
113
|
+
* Called when the backend application shuts down.
|
|
114
|
+
*
|
|
115
|
+
* When shutdown is initiated via `SIGINT`/`SIGTERM`, contributions are dispatched
|
|
116
|
+
* in parallel and any returned promise is awaited up to `SHUTDOWN_TIMEOUT_MS`
|
|
117
|
+
* milliseconds while injected services from the root container are still
|
|
118
|
+
* resolvable.
|
|
119
|
+
*
|
|
120
|
+
* On synchronous-exit fallback paths (uncaught exceptions, server bind failures,
|
|
121
|
+
* or normal process exit), the hook is invoked synchronously and any returned
|
|
122
|
+
* promise is discarded. Implementations should be resilient to either path.
|
|
123
|
+
*
|
|
124
|
+
* Contributions must be independent of one another during stop because they are
|
|
125
|
+
* dispatched in parallel.
|
|
108
126
|
*
|
|
109
127
|
* @param app the express application.
|
|
110
128
|
*/
|
|
111
|
-
onStop?(app?: express.Application): void
|
|
129
|
+
onStop?(app?: express.Application): MaybePromise<void>;
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
@injectable()
|
|
@@ -162,8 +180,14 @@ export class BackendApplication {
|
|
|
162
180
|
@inject(Stopwatch)
|
|
163
181
|
protected readonly stopwatch: Stopwatch;
|
|
164
182
|
|
|
183
|
+
@inject(RootContainer)
|
|
184
|
+
protected readonly rootContainer: interfaces.Container;
|
|
185
|
+
|
|
165
186
|
private _configured: Promise<void>;
|
|
166
187
|
|
|
188
|
+
private stoppedContributions = false;
|
|
189
|
+
private shuttingDown = false;
|
|
190
|
+
|
|
167
191
|
private settlementContext?: MeasurementContext<BackendApplicationContribution>;
|
|
168
192
|
|
|
169
193
|
constructor(
|
|
@@ -179,18 +203,15 @@ export class BackendApplication {
|
|
|
179
203
|
process.on('SIGPIPE', () => {
|
|
180
204
|
console.error(new Error('Unexpected SIGPIPE'));
|
|
181
205
|
});
|
|
182
|
-
|
|
183
|
-
* Kill the current process tree on exit.
|
|
184
|
-
*/
|
|
185
|
-
function signalHandler(signal: NodeJS.Signals): never {
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
206
|
+
|
|
188
207
|
// Handles normal process termination.
|
|
189
208
|
process.on('exit', () => this.onStop());
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
|
|
209
|
+
|
|
210
|
+
// Handles `Ctrl+C` and `kill pid`. Delegates to gracefulShutdown so that
|
|
211
|
+
// root-scoped singletons get their @preDestroy hooks invoked before exit.
|
|
212
|
+
const onSignal = () => { this.gracefulShutdown().catch(err => console.error(err)); };
|
|
213
|
+
process.on('SIGINT', onSignal);
|
|
214
|
+
process.on('SIGTERM', onSignal);
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
protected async initialize(): Promise<void> {
|
|
@@ -332,18 +353,82 @@ export class BackendApplication {
|
|
|
332
353
|
: `${scheme}://${address}:${port}`;
|
|
333
354
|
}
|
|
334
355
|
|
|
335
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Performs an asynchronous shutdown of the backend in two phases:
|
|
358
|
+
*
|
|
359
|
+
* 1. Contributions' {@link BackendApplicationContribution.onStop onStop} hooks are
|
|
360
|
+
* dispatched in parallel and awaited so that they can still resolve services
|
|
361
|
+
* from the root Inversify container while it is bound.
|
|
362
|
+
* 2. All services in the root container are unbound, running their `@preDestroy`
|
|
363
|
+
* hooks.
|
|
364
|
+
*
|
|
365
|
+
* Each phase has its own {@link SHUTDOWN_TIMEOUT_MS} budget to avoid hanging on a
|
|
366
|
+
* misbehaving hook. Late-resolving promises from a timed-out phase may still
|
|
367
|
+
* settle in the background and could log noisily or interact with a partly
|
|
368
|
+
* unbound container; this is accepted because the process is exiting.
|
|
369
|
+
*
|
|
370
|
+
* Idempotent: a second invocation is a no-op. Exits the process with code 1 so
|
|
371
|
+
* that the `process.on('exit')` handler runs for fallback cleanup such as
|
|
372
|
+
* {@link ProcessUtils.terminateProcessTree}; the exit handler does not re-invoke
|
|
373
|
+
* contribution `onStop()` hooks that this method already dispatched.
|
|
374
|
+
*/
|
|
375
|
+
protected async gracefulShutdown(): Promise<void> {
|
|
376
|
+
if (this.shuttingDown) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.shuttingDown = true;
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
await Promise.race([
|
|
383
|
+
this.stopContributions(),
|
|
384
|
+
timeoutReject<void>(SHUTDOWN_TIMEOUT_MS, `Stopping backend contributions timed out after ${SHUTDOWN_TIMEOUT_MS}ms`)
|
|
385
|
+
]);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
388
|
+
console.warn(`Backend contributions cleanup failed: ${message}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
await Promise.race([
|
|
393
|
+
this.rootContainer.unbindAllAsync(),
|
|
394
|
+
timeoutReject<void>(SHUTDOWN_TIMEOUT_MS, `Container unbind timed out after ${SHUTDOWN_TIMEOUT_MS}ms`)
|
|
395
|
+
]);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
398
|
+
console.warn(`Backend root container cleanup failed: ${message}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
protected async stopContributions(): Promise<void> {
|
|
405
|
+
if (this.stoppedContributions) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.stoppedContributions = true;
|
|
336
409
|
console.info('>>> Stopping backend contributions...');
|
|
337
|
-
|
|
410
|
+
// The `async` wrapper converts a synchronous throw inside a non-async
|
|
411
|
+
// contribution's `onStop` into a rejected promise so the per-contribution
|
|
412
|
+
// try/catch can handle it; otherwise `Promise.all` would abort.
|
|
413
|
+
await Promise.all(this.contributionsProvider.getContributions().map(async contrib => {
|
|
338
414
|
if (contrib.onStop) {
|
|
339
415
|
try {
|
|
340
|
-
contrib.onStop(this.app);
|
|
416
|
+
await contrib.onStop(this.app);
|
|
341
417
|
} catch (error) {
|
|
342
418
|
console.error('Could not stop contribution', error);
|
|
343
419
|
}
|
|
344
420
|
}
|
|
345
|
-
}
|
|
421
|
+
}));
|
|
346
422
|
console.info('<<< All backend contributions have been stopped.');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
protected onStop(): void {
|
|
426
|
+
// Deliberate fire-and-forget of an async `stopContributions`()` call.
|
|
427
|
+
// It invokes each contribution's `onStop` synchronously up to its
|
|
428
|
+
// first `await`, so any synchronous cleanup runs before
|
|
429
|
+
// `terminateProcessTree`. Any returned promises are abandoned because
|
|
430
|
+
// the `'exit'` event does not yield back to the event loop.
|
|
431
|
+
this.stopContributions();
|
|
347
432
|
this.processUtils.terminateProcessTree(process.pid);
|
|
348
433
|
}
|
|
349
434
|
|
|
@@ -67,7 +67,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it('should use --log-level flag', async () => {
|
|
70
|
-
const args: yargs.Arguments = yargs.parse(['--log-level=debug']);
|
|
70
|
+
const args: yargs.Arguments = await yargs.parse(['--log-level=debug']);
|
|
71
71
|
await cli.setArguments(args);
|
|
72
72
|
|
|
73
73
|
expect(cli.defaultLogLevel).eq(LogLevel.DEBUG);
|
|
@@ -85,7 +85,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
85
85
|
fs.fsyncSync(file.fd);
|
|
86
86
|
fs.closeSync(file.fd);
|
|
87
87
|
|
|
88
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
88
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
89
89
|
await cli.setArguments(args);
|
|
90
90
|
|
|
91
91
|
expect(cli.defaultLogLevel).eq(LogLevel.INFO);
|
|
@@ -96,7 +96,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
it('should use info as default log level', async () => {
|
|
99
|
-
const args: yargs.Arguments = yargs.parse([]);
|
|
99
|
+
const args: yargs.Arguments = await yargs.parse([]);
|
|
100
100
|
await cli.setArguments(args);
|
|
101
101
|
|
|
102
102
|
expect(cli.defaultLogLevel).eq(LogLevel.INFO);
|
|
@@ -113,7 +113,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
113
113
|
}
|
|
114
114
|
}));
|
|
115
115
|
|
|
116
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
116
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
117
117
|
await cli.setArguments(args);
|
|
118
118
|
sinon.assert.calledWithMatch(consoleErrorSpy, 'Unknown default log level in');
|
|
119
119
|
});
|
|
@@ -128,13 +128,13 @@ describe('log-level-cli-contribution', () => {
|
|
|
128
128
|
}
|
|
129
129
|
}));
|
|
130
130
|
|
|
131
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
131
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
132
132
|
await cli.setArguments(args);
|
|
133
133
|
sinon.assert.calledWithMatch(consoleErrorSpy, 'Unknown log level for logger hello in');
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
it('should reject nonexistent config files', async () => {
|
|
137
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', '/tmp/cacaca']);
|
|
137
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', '/tmp/cacaca']);
|
|
138
138
|
await cli.setArguments(args);
|
|
139
139
|
sinon.assert.calledWithMatch(consoleErrorSpy, 'no such file or directory');
|
|
140
140
|
});
|
|
@@ -150,7 +150,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
150
150
|
});
|
|
151
151
|
fs.writeFileSync(file.fd, '{' + text);
|
|
152
152
|
|
|
153
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
153
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
154
154
|
await cli.setArguments(args);
|
|
155
155
|
sinon.assert.calledWithMatch(consoleErrorSpy, 'Error reading log config file');
|
|
156
156
|
});
|
|
@@ -175,7 +175,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
175
175
|
fs.fsyncSync(file.fd);
|
|
176
176
|
fs.closeSync(file.fd);
|
|
177
177
|
|
|
178
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
178
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
179
179
|
await cli.setArguments(args);
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -224,7 +224,7 @@ describe('log-level-cli-contribution', () => {
|
|
|
224
224
|
}));
|
|
225
225
|
fs.fsyncSync(file.fd);
|
|
226
226
|
|
|
227
|
-
const args: yargs.Arguments = yargs.parse(['--log-config', file.path]);
|
|
227
|
+
const args: yargs.Arguments = await yargs.parse(['--log-config', file.path]);
|
|
228
228
|
await cli.setArguments(args);
|
|
229
229
|
|
|
230
230
|
expect(cli.defaultLogLevel).eq(LogLevel.INFO);
|