@hydranium/core 1.0.0-next.51 → 1.0.0-next.55
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/lib/langium/config/configuration-provider.d.ts +61 -0
- package/lib/langium/config/configuration-provider.d.ts.map +1 -0
- package/lib/langium/config/configuration-provider.js +89 -0
- package/lib/langium/config/configuration-provider.js.map +1 -0
- package/lib/langium/config/index.d.ts +1 -0
- package/lib/langium/config/index.d.ts.map +1 -1
- package/lib/langium/config/index.js +1 -0
- package/lib/langium/config/index.js.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
- package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
- package/lib/langium/diagnostics/lsp-logger.js +40 -6
- package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
- package/lib/langium/module.d.ts.map +1 -1
- package/lib/langium/module.js +5 -0
- package/lib/langium/module.js.map +1 -1
- package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
- package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
- package/lib/testing/playwright/flaky-network-proxy.js +164 -0
- package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
- package/lib/testing/playwright/index.d.ts +1 -0
- package/lib/testing/playwright/index.d.ts.map +1 -1
- package/lib/testing/playwright/index.js +1 -0
- package/lib/testing/playwright/index.js.map +1 -1
- package/package.json +5 -5
- package/src/langium/config/configuration-provider.ts +96 -0
- package/src/langium/config/index.ts +1 -0
- package/src/langium/diagnostics/lsp-logger.ts +41 -6
- package/src/langium/module.ts +5 -0
- package/src/testing/playwright/flaky-network-proxy.ts +240 -0
- package/src/testing/playwright/index.ts +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
import { type ConfigurationInitializedParams, DefaultConfigurationProvider } from '@hydranium/langium';
|
|
10
|
+
import type { ConfigurationItem, DidChangeConfigurationRegistrationOptions } from 'vscode-languageserver';
|
|
11
|
+
/**
|
|
12
|
+
* Configuration provider that asks the client about a section the first time
|
|
13
|
+
* something reads it.
|
|
14
|
+
*
|
|
15
|
+
* Langium's base requests one section per registered language, named after the
|
|
16
|
+
* language id, and never asks again. A read of any other section — including
|
|
17
|
+
* whatever `lsp.configurationRoot` is bound to — then resolves `undefined` with
|
|
18
|
+
* no request on the wire, which is indistinguishable from a setting the user has
|
|
19
|
+
* not set.
|
|
20
|
+
*
|
|
21
|
+
* Asking on first read rather than from a list collected up front: a reader
|
|
22
|
+
* names its section during DI, after `initialized` has taken its one-shot pass,
|
|
23
|
+
* so a list would have to be complete before the server initialized.
|
|
24
|
+
*/
|
|
25
|
+
export declare class HydraniumConfigurationProvider extends DefaultConfigurationProvider {
|
|
26
|
+
/**
|
|
27
|
+
* The client-facing hooks, captured because they arrive once as arguments to
|
|
28
|
+
* {@link initialized} and are needed on every later read. Absent in a
|
|
29
|
+
* headless composition, which has no connection to ask.
|
|
30
|
+
*/
|
|
31
|
+
protected registerForSection?: (params: DidChangeConfigurationRegistrationOptions) => void;
|
|
32
|
+
protected fetchSections?: (items: ConfigurationItem[]) => Promise<unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Sections already asked about, holding the in-flight request so concurrent
|
|
35
|
+
* readers share one round trip. Entries stay after they settle: they record
|
|
36
|
+
* that the question was ASKED, and without that an unset section is
|
|
37
|
+
* re-requested on every read, "unset" and "not fetched" being the same
|
|
38
|
+
* absence in the store.
|
|
39
|
+
*/
|
|
40
|
+
protected readonly requestedSections: Map<string, Promise<void>>;
|
|
41
|
+
initialized(params: ConfigurationInitializedParams): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* The `ready` await precedes the emptiness check: the base resolves it at the
|
|
44
|
+
* end of its own initial fetch, so testing the store first calls every
|
|
45
|
+
* language section missing and re-requests what is already on its way.
|
|
46
|
+
*/
|
|
47
|
+
getConfiguration(language: string, configuration: string): Promise<unknown>;
|
|
48
|
+
/** Ask about `section` once, sharing the request with any concurrent reader. */
|
|
49
|
+
protected requestSection(section: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Registering before fetching, so an edit landing between the two arrives as
|
|
52
|
+
* a notification instead of being lost; the reverse order drops it.
|
|
53
|
+
*
|
|
54
|
+
* A `null` result is left out of the store rather than written, so the
|
|
55
|
+
* section reads as unset, and no `onConfigurationSectionUpdate` fires — the
|
|
56
|
+
* value goes to the caller that asked for it, and an event would announce a
|
|
57
|
+
* change that is only this provider catching up.
|
|
58
|
+
*/
|
|
59
|
+
protected doRequestSection(section: string): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=configuration-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration-provider.d.ts","sourceRoot":"","sources":["../../../src/langium/config/configuration-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,8BAA8B,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,KAAK,EAAE,iBAAiB,EAAE,yCAAyC,EAAE,MAAM,uBAAuB,CAAC;AAE1G;;;;;;;;;;;;;GAaG;AACH,qBAAa,8BAA+B,SAAQ,4BAA4B;IAC7E;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,yCAAyC,KAAK,IAAI,CAAC;IAC3F,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3E;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,CAAC,iBAAiB,6BAAoC;IAEzD,WAAW,CAAC,MAAM,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjF;;;;OAIG;IACY,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1F,gFAAgF;IAChF,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxD;;;;;;;;OAQG;cACa,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAWlE"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
import { DefaultConfigurationProvider } from '@hydranium/langium';
|
|
10
|
+
/**
|
|
11
|
+
* Configuration provider that asks the client about a section the first time
|
|
12
|
+
* something reads it.
|
|
13
|
+
*
|
|
14
|
+
* Langium's base requests one section per registered language, named after the
|
|
15
|
+
* language id, and never asks again. A read of any other section — including
|
|
16
|
+
* whatever `lsp.configurationRoot` is bound to — then resolves `undefined` with
|
|
17
|
+
* no request on the wire, which is indistinguishable from a setting the user has
|
|
18
|
+
* not set.
|
|
19
|
+
*
|
|
20
|
+
* Asking on first read rather than from a list collected up front: a reader
|
|
21
|
+
* names its section during DI, after `initialized` has taken its one-shot pass,
|
|
22
|
+
* so a list would have to be complete before the server initialized.
|
|
23
|
+
*/
|
|
24
|
+
export class HydraniumConfigurationProvider extends DefaultConfigurationProvider {
|
|
25
|
+
/**
|
|
26
|
+
* The client-facing hooks, captured because they arrive once as arguments to
|
|
27
|
+
* {@link initialized} and are needed on every later read. Absent in a
|
|
28
|
+
* headless composition, which has no connection to ask.
|
|
29
|
+
*/
|
|
30
|
+
registerForSection;
|
|
31
|
+
fetchSections;
|
|
32
|
+
/**
|
|
33
|
+
* Sections already asked about, holding the in-flight request so concurrent
|
|
34
|
+
* readers share one round trip. Entries stay after they settle: they record
|
|
35
|
+
* that the question was ASKED, and without that an unset section is
|
|
36
|
+
* re-requested on every read, "unset" and "not fetched" being the same
|
|
37
|
+
* absence in the store.
|
|
38
|
+
*/
|
|
39
|
+
requestedSections = new Map();
|
|
40
|
+
async initialized(params) {
|
|
41
|
+
this.registerForSection = params.register;
|
|
42
|
+
this.fetchSections = params.fetchConfiguration;
|
|
43
|
+
await super.initialized(params);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The `ready` await precedes the emptiness check: the base resolves it at the
|
|
47
|
+
* end of its own initial fetch, so testing the store first calls every
|
|
48
|
+
* language section missing and re-requests what is already on its way.
|
|
49
|
+
*/
|
|
50
|
+
async getConfiguration(language, configuration) {
|
|
51
|
+
await this.ready;
|
|
52
|
+
const section = this.toSectionName(language);
|
|
53
|
+
if (this.settings[section] === undefined) {
|
|
54
|
+
await this.requestSection(section);
|
|
55
|
+
}
|
|
56
|
+
return super.getConfiguration(language, configuration);
|
|
57
|
+
}
|
|
58
|
+
/** Ask about `section` once, sharing the request with any concurrent reader. */
|
|
59
|
+
requestSection(section) {
|
|
60
|
+
const existing = this.requestedSections.get(section);
|
|
61
|
+
if (existing) {
|
|
62
|
+
return existing;
|
|
63
|
+
}
|
|
64
|
+
const request = this.doRequestSection(section);
|
|
65
|
+
this.requestedSections.set(section, request);
|
|
66
|
+
return request;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Registering before fetching, so an edit landing between the two arrives as
|
|
70
|
+
* a notification instead of being lost; the reverse order drops it.
|
|
71
|
+
*
|
|
72
|
+
* A `null` result is left out of the store rather than written, so the
|
|
73
|
+
* section reads as unset, and no `onConfigurationSectionUpdate` fires — the
|
|
74
|
+
* value goes to the caller that asked for it, and an event would announce a
|
|
75
|
+
* change that is only this provider catching up.
|
|
76
|
+
*/
|
|
77
|
+
async doRequestSection(section) {
|
|
78
|
+
this.registerForSection?.({ section });
|
|
79
|
+
if (!this.fetchSections) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const fetched = await this.fetchSections([{ section }]);
|
|
83
|
+
const value = Array.isArray(fetched) ? fetched[0] : fetched;
|
|
84
|
+
if (value !== null && value !== undefined) {
|
|
85
|
+
this.updateSectionConfiguration(section, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=configuration-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration-provider.js","sourceRoot":"","sources":["../../../src/langium/config/configuration-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAuC,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAGvG;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,8BAA+B,SAAQ,4BAA4B;IAC7E;;;;OAIG;IACO,kBAAkB,CAA+D;IACjF,aAAa,CAAoD;IAE3E;;;;;;OAMG;IACgB,iBAAiB,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE/D,KAAK,CAAC,WAAW,CAAC,MAAsC;QAC9D,IAAI,CAAC,kBAAkB,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC1C,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,kBAAkB,CAAC;QAC/C,MAAM,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACM,KAAK,CAAC,gBAAgB,CAAC,QAAgB,EAAE,aAAqB;QACpE,MAAM,IAAI,CAAC,KAAK,CAAC;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;YACxC,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED,gFAAgF;IACtE,cAAc,CAAC,OAAe;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,QAAQ,EAAE,CAAC;YACZ,OAAO,QAAQ,CAAC;QACnB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC;IAClB,CAAC;IAED;;;;;;;;OAQG;IACO,KAAK,CAAC,gBAAgB,CAAC,OAAe;QAC7C,IAAI,CAAC,kBAAkB,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,OAAO;QACV,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5D,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,0BAA0B,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACJ,CAAC;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/langium/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/langium/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/langium/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/langium/config/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC"}
|
|
@@ -51,6 +51,23 @@ export interface LspLoggerOptions {
|
|
|
51
51
|
export declare class LspLogger extends AbstractLogger implements Logger {
|
|
52
52
|
protected readonly services: ServerSharedServices;
|
|
53
53
|
constructor(services: ServerSharedServices, options?: LspLoggerOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Set the process-wide threshold and announce the transition under whichever
|
|
56
|
+
* of the two thresholds can carry the notice, so a reader sees every change
|
|
57
|
+
* and its absence means the threshold never moved.
|
|
58
|
+
*
|
|
59
|
+
* `error` and `off` admit no notice of their own — `off` nothing at all, and
|
|
60
|
+
* an error-severity line is read as a fault, which a client that marks its
|
|
61
|
+
* log as having seen an error keeps for the session. Entering either is
|
|
62
|
+
* therefore announced BEFORE the switch, under the outgoing threshold; every
|
|
63
|
+
* other change is announced after it, at the level being entered. Leaving
|
|
64
|
+
* `error` or `off` for one of those two says nothing, both thresholds being
|
|
65
|
+
* unable to carry it.
|
|
66
|
+
*
|
|
67
|
+
* Only on a real transition: the bound setting re-fires for configuration
|
|
68
|
+
* changes that leave the value alone.
|
|
69
|
+
*/
|
|
70
|
+
protected applyLevel(next: LogThreshold, source: string): void;
|
|
54
71
|
/**
|
|
55
72
|
* Route logger timing and timestamps through the injected framework
|
|
56
73
|
* {@link Clock} (the dedicated `shared.Clock` slot) so server-side log time
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lsp-logger.d.ts","sourceRoot":"","sources":["../../../src/langium/diagnostics/lsp-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,cAAc,EACd,KAAK,KAAK,EAGV,MAAM,EACN,KAAK,QAAQ,EACb,KAAK,YAAY,EAEjB,KAAK,oBAAoB,EAE3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAYzD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;CACzE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,SAAQ,cAAe,YAAW,MAAM;IAEzD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB;gBAA9B,QAAQ,EAAE,oBAAoB,EACjD,OAAO,GAAE,gBAAqB;
|
|
1
|
+
{"version":3,"file":"lsp-logger.d.ts","sourceRoot":"","sources":["../../../src/langium/diagnostics/lsp-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,cAAc,EACd,KAAK,KAAK,EAGV,MAAM,EACN,KAAK,QAAQ,EACb,KAAK,YAAY,EAEjB,KAAK,oBAAoB,EAE3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAYzD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;CACzE;AAED;;;;;;;;;;GAUG;AACH,qBAAa,SAAU,SAAQ,cAAe,YAAW,MAAM;IAEzD,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB;gBAA9B,QAAQ,EAAE,oBAAoB,EACjD,OAAO,GAAE,gBAAqB;IA8CjC;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAgB9D;;;;;;;OAOG;cACgB,KAAK,IAAI,KAAK;IAIjC;;;;;;;;OAQG;IACM,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAIzC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAuC/F,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAM3C"}
|
|
@@ -45,14 +45,17 @@ export class LspLogger extends AbstractLogger {
|
|
|
45
45
|
// a Node concern; skip it cleanly there.
|
|
46
46
|
const env = processEnv();
|
|
47
47
|
if (env) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
48
|
+
// The file-tee target FIRST: the other order writes the level
|
|
49
|
+
// announcement before the sink exists, losing the one line that says
|
|
50
|
+
// which threshold the rest of the file was written at.
|
|
52
51
|
const envLogFile = env[DEFAULT_LOG_FILE_ENV];
|
|
53
52
|
if (envLogFile) {
|
|
54
53
|
setLogFilePath(envLogFile);
|
|
55
54
|
}
|
|
55
|
+
const envLevel = parseLogLevel(env[DEFAULT_LOG_LEVEL_ENV]);
|
|
56
|
+
if (envLevel) {
|
|
57
|
+
this.applyLevel(envLevel, DEFAULT_LOG_LEVEL_ENV);
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
// Apply the configured threshold from the root logger only — `derive`
|
|
@@ -62,15 +65,46 @@ export class LspLogger extends AbstractLogger {
|
|
|
62
65
|
if (options.logThreshold !== undefined) {
|
|
63
66
|
const level = ObservableValue.from(options.logThreshold);
|
|
64
67
|
if (level.value) {
|
|
65
|
-
|
|
68
|
+
this.applyLevel(level.value, 'setting');
|
|
66
69
|
}
|
|
67
70
|
level.onChange(next => {
|
|
68
71
|
if (next) {
|
|
69
|
-
|
|
72
|
+
this.applyLevel(next, 'setting');
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Set the process-wide threshold and announce the transition under whichever
|
|
79
|
+
* of the two thresholds can carry the notice, so a reader sees every change
|
|
80
|
+
* and its absence means the threshold never moved.
|
|
81
|
+
*
|
|
82
|
+
* `error` and `off` admit no notice of their own — `off` nothing at all, and
|
|
83
|
+
* an error-severity line is read as a fault, which a client that marks its
|
|
84
|
+
* log as having seen an error keeps for the session. Entering either is
|
|
85
|
+
* therefore announced BEFORE the switch, under the outgoing threshold; every
|
|
86
|
+
* other change is announced after it, at the level being entered. Leaving
|
|
87
|
+
* `error` or `off` for one of those two says nothing, both thresholds being
|
|
88
|
+
* unable to carry it.
|
|
89
|
+
*
|
|
90
|
+
* Only on a real transition: the bound setting re-fires for configuration
|
|
91
|
+
* changes that leave the value alone.
|
|
92
|
+
*/
|
|
93
|
+
applyLevel(next, source) {
|
|
94
|
+
const previous = Logger.getLevel();
|
|
95
|
+
if (previous === next) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const message = `Log level ${previous} → ${next} (${source})`;
|
|
99
|
+
const announceFirst = next === 'error' || next === 'off';
|
|
100
|
+
if (announceFirst && previous !== 'error') {
|
|
101
|
+
this.logAt(previous, message);
|
|
102
|
+
}
|
|
103
|
+
Logger.setLevel(next);
|
|
104
|
+
if (!announceFirst) {
|
|
105
|
+
this.logAt(next, message);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
74
108
|
/**
|
|
75
109
|
* Route logger timing and timestamps through the injected framework
|
|
76
110
|
* {@link Clock} (the dedicated `shared.Clock` slot) so server-side log time
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lsp-logger.js","sourceRoot":"","sources":["../../../src/langium/diagnostics/lsp-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,cAAc,EAEd,oBAAoB,EACpB,qBAAqB,EACrB,MAAM,EAGN,eAAe,EAEf,aAAa,EACf,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;;GAKG;AACH,IAAI,kBAAkB,GAAG,KAAK,CAAC;AA+B/B;;;;;;;;;;GAUG;AACH,MAAM,OAAO,SAAU,SAAQ,cAAc;IAEpB;IADtB,YACsB,QAA8B,EACjD,UAA4B,EAAE;QAE9B,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAHN,aAAQ,GAAR,QAAQ,CAAsB;QAIjD,+DAA+D;QAC/D,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,yEAAyE;QACzE,oEAAoE;QACpE,8CAA8C;QAC9C,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACvB,kBAAkB,GAAG,IAAI,CAAC;YAC1B,qEAAqE;YACrE,yCAAyC;YACzC,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;YACzB,IAAI,GAAG,EAAE,CAAC;gBACP,
|
|
1
|
+
{"version":3,"file":"lsp-logger.js","sourceRoot":"","sources":["../../../src/langium/diagnostics/lsp-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/langium/module.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAA2B,MAAM,qBAAqB,CAAC;AAClH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAGvF,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,KAAK,0BAA0B,EAAE,KAAK,qBAAqB,EAAE,KAAK,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AACxI,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAC1F,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAC9F,OAAO,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AAClF,OAAO,EAAmC,KAAK,wBAAwB,EAAE,MAAM,kDAAkD,CAAC;AAClI,OAAO,EAAE,KAAK,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AACzF,OAAO,EAAE,KAAK,qBAAqB,EAAgC,MAAM,gDAAgD,CAAC;AAC1H,OAAO,EAA8B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/langium/module.ts"],"names":[],"mappings":"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"}
|
package/lib/langium/module.js
CHANGED
|
@@ -18,6 +18,7 @@ import { HydraniumDocumentBuilder } from './document-builder/document-builder.js
|
|
|
18
18
|
import { DefaultBuildPipelineIntegration } from './document-builder/build-pipeline-integration.js';
|
|
19
19
|
import { DefaultBuildPhasePassService } from './build-phase-pass/build-phase-pass-service.js';
|
|
20
20
|
import { DefaultCstResidencyService } from './residency/index.js';
|
|
21
|
+
import { HydraniumConfigurationProvider } from './config/configuration-provider.js';
|
|
21
22
|
import { HydraniumIndexManager } from './workspace/index-manager.js';
|
|
22
23
|
import { HydraniumWorkspaceManager } from './workspace/hydranium-workspace-manager.js';
|
|
23
24
|
import { HydraniumWorkspaceLock } from './workspace/hydranium-workspace-lock.js';
|
|
@@ -111,6 +112,10 @@ export function createServerSharedModule(context) {
|
|
|
111
112
|
TextDocuments: services => new HydraniumTextDocuments(services),
|
|
112
113
|
WorkspaceManager: services => new HydraniumWorkspaceManager(services),
|
|
113
114
|
IndexManager: services => new HydraniumIndexManager(services),
|
|
115
|
+
// Langium's default asks only about sections named after registered
|
|
116
|
+
// language ids, so a section `lsp.configurationRoot` is bound to reads
|
|
117
|
+
// `undefined` forever with no request on the wire.
|
|
118
|
+
ConfigurationProvider: services => new HydraniumConfigurationProvider(services),
|
|
114
119
|
// Override Langium's factory so a `fromModel` (code-built) virtual
|
|
115
120
|
// document retains serialized text via the per-language Serializer,
|
|
116
121
|
// making it re-read-safe through the virtual-aware FileSystemProvider.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/langium/module.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/langium/module.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
/**
|
|
10
|
+
* A TCP proxy that can half-close a connection, so an e2e suite can reproduce a
|
|
11
|
+
* flaky network against a real browser session.
|
|
12
|
+
*
|
|
13
|
+
* Toggling the browser offline is not enough. On localhost the server sees the
|
|
14
|
+
* close immediately and reaps the socket before the browser reconnects, so the
|
|
15
|
+
* window where both sides disagree about the session — the one where reconnect
|
|
16
|
+
* defects live — never opens. Reaching it needs the browser's socket to die
|
|
17
|
+
* while the server still believes it is alive, and nothing inside the app or in
|
|
18
|
+
* Playwright's offline emulation can produce that asymmetry: both close cleanly
|
|
19
|
+
* at both ends.
|
|
20
|
+
*
|
|
21
|
+
* The proxy holds both halves of every connection and lets a test kill them
|
|
22
|
+
* independently:
|
|
23
|
+
*
|
|
24
|
+
* `break` destroy the BROWSER-side sockets, keep the server-side ones open.
|
|
25
|
+
* The browser reconnects through a fresh pair while the server
|
|
26
|
+
* still holds the old one.
|
|
27
|
+
* `release` destroy the orphaned server-side sockets `break` left. The
|
|
28
|
+
* server's disconnect handler fires at once, rather than whenever
|
|
29
|
+
* its next heartbeat write would have failed.
|
|
30
|
+
*/
|
|
31
|
+
/** Where the proxy listens, and the application it fronts. */
|
|
32
|
+
export interface FlakyNetworkProxyOptions {
|
|
33
|
+
/** Port the browser connects to. */
|
|
34
|
+
readonly listenPort: number;
|
|
35
|
+
/** Port of the application being proxied. */
|
|
36
|
+
readonly targetPort: number;
|
|
37
|
+
/** Host of the application being proxied. Defaults to loopback. */
|
|
38
|
+
readonly targetHost?: string;
|
|
39
|
+
/** Port the HTTP control channel listens on. Defaults to `listenPort + 1000`. */
|
|
40
|
+
readonly controlPort?: number;
|
|
41
|
+
}
|
|
42
|
+
/** The commands the control channel accepts, and the proxy's own vocabulary. */
|
|
43
|
+
export type FlakyNetworkCommand = 'break' | 'release' | 'status';
|
|
44
|
+
/** Drives a running proxy. Implemented both in-process and over HTTP. */
|
|
45
|
+
export interface FlakyNetworkControl {
|
|
46
|
+
/** Kill the browser side of every live connection; leave the server side up. */
|
|
47
|
+
break(): Promise<string>;
|
|
48
|
+
/** Kill the server side of every connection `break` orphaned. */
|
|
49
|
+
release(): Promise<string>;
|
|
50
|
+
/** How many connections are live and how many are orphaned. */
|
|
51
|
+
status(): Promise<string>;
|
|
52
|
+
}
|
|
53
|
+
export interface FlakyNetworkProxy extends FlakyNetworkControl {
|
|
54
|
+
/** Base URL a browser should be pointed at, i.e. Playwright's `baseURL`. */
|
|
55
|
+
readonly url: string;
|
|
56
|
+
/** Control channel, for workers that cannot reach this object directly. */
|
|
57
|
+
readonly controlUrl: string;
|
|
58
|
+
close(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Starts the proxy in the calling process.
|
|
62
|
+
*
|
|
63
|
+
* Call it from a Playwright config, which runs in the main process before any
|
|
64
|
+
* worker forks. Workers are separate processes and cannot reach the returned
|
|
65
|
+
* object, which is what {@link controlUrl} and {@link flakyNetworkControlClient}
|
|
66
|
+
* are for.
|
|
67
|
+
*/
|
|
68
|
+
export declare function startFlakyNetworkProxy(options: FlakyNetworkProxyOptions): Promise<FlakyNetworkProxy>;
|
|
69
|
+
/**
|
|
70
|
+
* Drives a proxy running in another process, over its control channel.
|
|
71
|
+
*
|
|
72
|
+
* This is what a spec uses: Playwright workers are separate processes from the
|
|
73
|
+
* config that started the proxy.
|
|
74
|
+
*/
|
|
75
|
+
export declare function flakyNetworkControlClient(controlUrl: string): FlakyNetworkControl;
|
|
76
|
+
//# sourceMappingURL=flaky-network-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flaky-network-proxy.d.ts","sourceRoot":"","sources":["../../../src/testing/playwright/flaky-network-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAKlF;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,wBAAwB;IACtC,oCAAoC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjE,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IACjC,gFAAgF;IAChF,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,iEAAiE;IACjE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,+DAA+D;IAC/D,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAkB,SAAQ,mBAAmB;IAC3D,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AASD;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAyH1G;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAajF"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
import { createServer as createHttpServer } from 'node:http';
|
|
10
|
+
import { connect, createServer as createTcpServer } from 'node:net';
|
|
11
|
+
/**
|
|
12
|
+
* Starts the proxy in the calling process.
|
|
13
|
+
*
|
|
14
|
+
* Call it from a Playwright config, which runs in the main process before any
|
|
15
|
+
* worker forks. Workers are separate processes and cannot reach the returned
|
|
16
|
+
* object, which is what {@link controlUrl} and {@link flakyNetworkControlClient}
|
|
17
|
+
* are for.
|
|
18
|
+
*/
|
|
19
|
+
export async function startFlakyNetworkProxy(options) {
|
|
20
|
+
const targetHost = options.targetHost ?? '127.0.0.1';
|
|
21
|
+
const controlPort = options.controlPort ?? options.listenPort + 1000;
|
|
22
|
+
const pairs = new Set();
|
|
23
|
+
let sequence = 0;
|
|
24
|
+
let transcript = [];
|
|
25
|
+
const log = (message) => {
|
|
26
|
+
transcript.push(message);
|
|
27
|
+
console.log(`[flaky-network] ${message}`);
|
|
28
|
+
};
|
|
29
|
+
const proxyServer = createTcpServer(browserSocket => {
|
|
30
|
+
const id = ++sequence;
|
|
31
|
+
const targetSocket = connect(options.targetPort, targetHost);
|
|
32
|
+
const pair = { id, browserSocket, targetSocket, orphaned: false };
|
|
33
|
+
pairs.add(pair);
|
|
34
|
+
// Forwarded by hand rather than with `pipe()`, so killing one side does not
|
|
35
|
+
// tear the other down — which is the entire point of the proxy.
|
|
36
|
+
browserSocket.on('data', chunk => {
|
|
37
|
+
if (!targetSocket.destroyed) {
|
|
38
|
+
targetSocket.write(chunk);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
targetSocket.on('data', chunk => {
|
|
42
|
+
if (!pair.orphaned && !browserSocket.destroyed) {
|
|
43
|
+
browserSocket.write(chunk);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const teardown = (origin) => {
|
|
47
|
+
if (pair.orphaned && origin === 'browser') {
|
|
48
|
+
// Expected: `break` killed this side deliberately. The server side
|
|
49
|
+
// stays up until `release`.
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
pairs.delete(pair);
|
|
53
|
+
browserSocket.destroy();
|
|
54
|
+
targetSocket.destroy();
|
|
55
|
+
};
|
|
56
|
+
browserSocket.on('close', () => teardown('browser'));
|
|
57
|
+
targetSocket.on('close', () => teardown('target'));
|
|
58
|
+
// A half-killed pair reliably raises ECONNRESET on the surviving side; an
|
|
59
|
+
// unhandled 'error' would take the whole runner down with it.
|
|
60
|
+
browserSocket.on('error', () => undefined);
|
|
61
|
+
targetSocket.on('error', () => undefined);
|
|
62
|
+
});
|
|
63
|
+
const commands = {
|
|
64
|
+
break: () => {
|
|
65
|
+
const live = [...pairs].filter(pair => !pair.orphaned);
|
|
66
|
+
if (live.length === 0) {
|
|
67
|
+
log('nothing to break');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const pair of live) {
|
|
71
|
+
pair.orphaned = true;
|
|
72
|
+
pair.browserSocket.destroy();
|
|
73
|
+
}
|
|
74
|
+
log(`${live.length} connection(s) broken; the browser should reconnect shortly`);
|
|
75
|
+
},
|
|
76
|
+
release: () => {
|
|
77
|
+
const orphans = [...pairs].filter(pair => pair.orphaned);
|
|
78
|
+
if (orphans.length === 0) {
|
|
79
|
+
log('no orphaned connections; run `break` first');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const pair of orphans) {
|
|
83
|
+
pairs.delete(pair);
|
|
84
|
+
pair.targetSocket.destroy();
|
|
85
|
+
}
|
|
86
|
+
log(`${orphans.length} orphaned connection(s) destroyed; their late disconnect fires now`);
|
|
87
|
+
},
|
|
88
|
+
status: () => {
|
|
89
|
+
const live = [...pairs].filter(pair => !pair.orphaned).length;
|
|
90
|
+
const orphaned = pairs.size - live;
|
|
91
|
+
log(`${live} live, ${orphaned} orphaned`);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const run = (name) => {
|
|
95
|
+
transcript = [];
|
|
96
|
+
const command = commands[name];
|
|
97
|
+
if (command) {
|
|
98
|
+
command();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
log(`unknown command '${name}'; use: ${Object.keys(commands).join(' | ')}`);
|
|
102
|
+
}
|
|
103
|
+
return transcript.join('\n');
|
|
104
|
+
};
|
|
105
|
+
const controlServer = createHttpServer((request, response) => {
|
|
106
|
+
const output = run((request.url ?? '').replace(/^\//, '').split('?')[0]);
|
|
107
|
+
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
108
|
+
response.end(`${output}\n`);
|
|
109
|
+
});
|
|
110
|
+
await Promise.all([listen(proxyServer, options.listenPort), listen(controlServer, controlPort)]);
|
|
111
|
+
// Neither server keeps the process alive, so a runner that starts one from a
|
|
112
|
+
// config does not need a teardown hook to exit — and a caller that wants a
|
|
113
|
+
// deterministic shutdown still has `close()`.
|
|
114
|
+
proxyServer.unref();
|
|
115
|
+
controlServer.unref();
|
|
116
|
+
return {
|
|
117
|
+
url: `http://localhost:${options.listenPort}`,
|
|
118
|
+
controlUrl: `http://localhost:${controlPort}`,
|
|
119
|
+
break: async () => run('break'),
|
|
120
|
+
release: async () => run('release'),
|
|
121
|
+
status: async () => run('status'),
|
|
122
|
+
close: async () => {
|
|
123
|
+
for (const pair of pairs) {
|
|
124
|
+
pair.browserSocket.destroy();
|
|
125
|
+
pair.targetSocket.destroy();
|
|
126
|
+
}
|
|
127
|
+
pairs.clear();
|
|
128
|
+
await Promise.all([close(proxyServer), close(controlServer)]);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Drives a proxy running in another process, over its control channel.
|
|
134
|
+
*
|
|
135
|
+
* This is what a spec uses: Playwright workers are separate processes from the
|
|
136
|
+
* config that started the proxy.
|
|
137
|
+
*/
|
|
138
|
+
export function flakyNetworkControlClient(controlUrl) {
|
|
139
|
+
const send = async (command) => {
|
|
140
|
+
const response = await fetch(`${controlUrl}/${command}`);
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
throw new Error(`flaky-network proxy rejected '${command}': ${response.status}`);
|
|
143
|
+
}
|
|
144
|
+
return (await response.text()).trim();
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
break: () => send('break'),
|
|
148
|
+
release: () => send('release'),
|
|
149
|
+
status: () => send('status')
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function listen(server, port) {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
server.once('error', reject);
|
|
155
|
+
server.listen(port, () => {
|
|
156
|
+
server.off('error', reject);
|
|
157
|
+
resolve();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function close(server) {
|
|
162
|
+
return new Promise(resolve => server.close(() => resolve()));
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=flaky-network-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flaky-network-proxy.js","sourceRoot":"","sources":["../../../src/testing/playwright/flaky-network-proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
export * from './e2e-profiling.js';
|
|
10
|
+
export * from './flaky-network-proxy.js';
|
|
10
11
|
export * from './browser-capture-bridge.js';
|
|
11
12
|
export * from './server-log-capture.js';
|
|
12
13
|
export * from './server-log-fixture.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/playwright/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAKlF,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/playwright/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAKlF,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// `@hydranium/core/testing` barrel) so importing the framework's unit-test
|
|
11
11
|
// helpers never pulls in `@playwright/test` — which is an OPTIONAL peer here.
|
|
12
12
|
export * from './e2e-profiling.js';
|
|
13
|
+
export * from './flaky-network-proxy.js';
|
|
13
14
|
export * from './browser-capture-bridge.js';
|
|
14
15
|
export * from './server-log-capture.js';
|
|
15
16
|
export * from './server-log-fixture.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/playwright/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/playwright/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/core",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.55",
|
|
4
4
|
"description": "Foundational runtime of the hydranium framework: AST coordination layer (services, integrity, AST extension, serialization, multi-client documents) plus the LSP textual head at the /lsp subpath. Consumed by protocol-head packages (@hydranium/data-server, @hydranium/glsp-server).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"diff": "^5.2.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@hydranium/langium": "1.0.0-next.
|
|
108
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
107
|
+
"@hydranium/langium": "1.0.0-next.55",
|
|
108
|
+
"@hydranium/protocol": "1.0.0-next.55",
|
|
109
109
|
"@playwright/test": "^1.40.0",
|
|
110
110
|
"@types/diff": "^5.2.0",
|
|
111
111
|
"rimraf": "^5.0.0",
|
|
@@ -117,8 +117,8 @@
|
|
|
117
117
|
"vscode-languageserver-types": "^3.17.5"
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
|
-
"@hydranium/langium": "1.0.0-next.
|
|
121
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
120
|
+
"@hydranium/langium": "1.0.0-next.55",
|
|
121
|
+
"@hydranium/protocol": "1.0.0-next.55",
|
|
122
122
|
"@playwright/test": "^1.40.0",
|
|
123
123
|
"vscode-jsonrpc": "9.0.1",
|
|
124
124
|
"vscode-languageserver": "~10.0.1",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { type ConfigurationInitializedParams, DefaultConfigurationProvider } from '@hydranium/langium';
|
|
11
|
+
import type { ConfigurationItem, DidChangeConfigurationRegistrationOptions } from 'vscode-languageserver';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration provider that asks the client about a section the first time
|
|
15
|
+
* something reads it.
|
|
16
|
+
*
|
|
17
|
+
* Langium's base requests one section per registered language, named after the
|
|
18
|
+
* language id, and never asks again. A read of any other section — including
|
|
19
|
+
* whatever `lsp.configurationRoot` is bound to — then resolves `undefined` with
|
|
20
|
+
* no request on the wire, which is indistinguishable from a setting the user has
|
|
21
|
+
* not set.
|
|
22
|
+
*
|
|
23
|
+
* Asking on first read rather than from a list collected up front: a reader
|
|
24
|
+
* names its section during DI, after `initialized` has taken its one-shot pass,
|
|
25
|
+
* so a list would have to be complete before the server initialized.
|
|
26
|
+
*/
|
|
27
|
+
export class HydraniumConfigurationProvider extends DefaultConfigurationProvider {
|
|
28
|
+
/**
|
|
29
|
+
* The client-facing hooks, captured because they arrive once as arguments to
|
|
30
|
+
* {@link initialized} and are needed on every later read. Absent in a
|
|
31
|
+
* headless composition, which has no connection to ask.
|
|
32
|
+
*/
|
|
33
|
+
protected registerForSection?: (params: DidChangeConfigurationRegistrationOptions) => void;
|
|
34
|
+
protected fetchSections?: (items: ConfigurationItem[]) => Promise<unknown>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sections already asked about, holding the in-flight request so concurrent
|
|
38
|
+
* readers share one round trip. Entries stay after they settle: they record
|
|
39
|
+
* that the question was ASKED, and without that an unset section is
|
|
40
|
+
* re-requested on every read, "unset" and "not fetched" being the same
|
|
41
|
+
* absence in the store.
|
|
42
|
+
*/
|
|
43
|
+
protected readonly requestedSections = new Map<string, Promise<void>>();
|
|
44
|
+
|
|
45
|
+
override async initialized(params: ConfigurationInitializedParams): Promise<void> {
|
|
46
|
+
this.registerForSection = params.register;
|
|
47
|
+
this.fetchSections = params.fetchConfiguration;
|
|
48
|
+
await super.initialized(params);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The `ready` await precedes the emptiness check: the base resolves it at the
|
|
53
|
+
* end of its own initial fetch, so testing the store first calls every
|
|
54
|
+
* language section missing and re-requests what is already on its way.
|
|
55
|
+
*/
|
|
56
|
+
override async getConfiguration(language: string, configuration: string): Promise<unknown> {
|
|
57
|
+
await this.ready;
|
|
58
|
+
const section = this.toSectionName(language);
|
|
59
|
+
if (this.settings[section] === undefined) {
|
|
60
|
+
await this.requestSection(section);
|
|
61
|
+
}
|
|
62
|
+
return super.getConfiguration(language, configuration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Ask about `section` once, sharing the request with any concurrent reader. */
|
|
66
|
+
protected requestSection(section: string): Promise<void> {
|
|
67
|
+
const existing = this.requestedSections.get(section);
|
|
68
|
+
if (existing) {
|
|
69
|
+
return existing;
|
|
70
|
+
}
|
|
71
|
+
const request = this.doRequestSection(section);
|
|
72
|
+
this.requestedSections.set(section, request);
|
|
73
|
+
return request;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Registering before fetching, so an edit landing between the two arrives as
|
|
78
|
+
* a notification instead of being lost; the reverse order drops it.
|
|
79
|
+
*
|
|
80
|
+
* A `null` result is left out of the store rather than written, so the
|
|
81
|
+
* section reads as unset, and no `onConfigurationSectionUpdate` fires — the
|
|
82
|
+
* value goes to the caller that asked for it, and an event would announce a
|
|
83
|
+
* change that is only this provider catching up.
|
|
84
|
+
*/
|
|
85
|
+
protected async doRequestSection(section: string): Promise<void> {
|
|
86
|
+
this.registerForSection?.({ section });
|
|
87
|
+
if (!this.fetchSections) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const fetched = await this.fetchSections([{ section }]);
|
|
91
|
+
const value = Array.isArray(fetched) ? fetched[0] : fetched;
|
|
92
|
+
if (value !== null && value !== undefined) {
|
|
93
|
+
this.updateSectionConfiguration(section, value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -92,14 +92,17 @@ export class LspLogger extends AbstractLogger implements Logger {
|
|
|
92
92
|
// a Node concern; skip it cleanly there.
|
|
93
93
|
const env = processEnv();
|
|
94
94
|
if (env) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
95
|
+
// The file-tee target FIRST: the other order writes the level
|
|
96
|
+
// announcement before the sink exists, losing the one line that says
|
|
97
|
+
// which threshold the rest of the file was written at.
|
|
99
98
|
const envLogFile = env[DEFAULT_LOG_FILE_ENV];
|
|
100
99
|
if (envLogFile) {
|
|
101
100
|
setLogFilePath(envLogFile);
|
|
102
101
|
}
|
|
102
|
+
const envLevel = parseLogLevel(env[DEFAULT_LOG_LEVEL_ENV]);
|
|
103
|
+
if (envLevel) {
|
|
104
|
+
this.applyLevel(envLevel, DEFAULT_LOG_LEVEL_ENV);
|
|
105
|
+
}
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
// Apply the configured threshold from the root logger only — `derive`
|
|
@@ -109,16 +112,48 @@ export class LspLogger extends AbstractLogger implements Logger {
|
|
|
109
112
|
if (options.logThreshold !== undefined) {
|
|
110
113
|
const level = ObservableValue.from(options.logThreshold);
|
|
111
114
|
if (level.value) {
|
|
112
|
-
|
|
115
|
+
this.applyLevel(level.value, 'setting');
|
|
113
116
|
}
|
|
114
117
|
level.onChange(next => {
|
|
115
118
|
if (next) {
|
|
116
|
-
|
|
119
|
+
this.applyLevel(next, 'setting');
|
|
117
120
|
}
|
|
118
121
|
});
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Set the process-wide threshold and announce the transition under whichever
|
|
127
|
+
* of the two thresholds can carry the notice, so a reader sees every change
|
|
128
|
+
* and its absence means the threshold never moved.
|
|
129
|
+
*
|
|
130
|
+
* `error` and `off` admit no notice of their own — `off` nothing at all, and
|
|
131
|
+
* an error-severity line is read as a fault, which a client that marks its
|
|
132
|
+
* log as having seen an error keeps for the session. Entering either is
|
|
133
|
+
* therefore announced BEFORE the switch, under the outgoing threshold; every
|
|
134
|
+
* other change is announced after it, at the level being entered. Leaving
|
|
135
|
+
* `error` or `off` for one of those two says nothing, both thresholds being
|
|
136
|
+
* unable to carry it.
|
|
137
|
+
*
|
|
138
|
+
* Only on a real transition: the bound setting re-fires for configuration
|
|
139
|
+
* changes that leave the value alone.
|
|
140
|
+
*/
|
|
141
|
+
protected applyLevel(next: LogThreshold, source: string): void {
|
|
142
|
+
const previous = Logger.getLevel();
|
|
143
|
+
if (previous === next) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const message = `Log level ${previous} → ${next} (${source})`;
|
|
147
|
+
const announceFirst = next === 'error' || next === 'off';
|
|
148
|
+
if (announceFirst && previous !== 'error') {
|
|
149
|
+
this.logAt(previous, message);
|
|
150
|
+
}
|
|
151
|
+
Logger.setLevel(next);
|
|
152
|
+
if (!announceFirst) {
|
|
153
|
+
this.logAt(next, message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
/**
|
|
123
158
|
* Route logger timing and timestamps through the injected framework
|
|
124
159
|
* {@link Clock} (the dedicated `shared.Clock` slot) so server-side log time
|
package/src/langium/module.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { DefaultBuildPipelineIntegration, type BuildPipelineIntegration } from '
|
|
|
24
24
|
import { type BuildPhasePassContribution } from './build-phase-pass/build-phase-pass.js';
|
|
25
25
|
import { type BuildPhasePassService, DefaultBuildPhasePassService } from './build-phase-pass/build-phase-pass-service.js';
|
|
26
26
|
import { DefaultCstResidencyService, type CstResidencyService } from './residency/index.js';
|
|
27
|
+
import { HydraniumConfigurationProvider } from './config/configuration-provider.js';
|
|
27
28
|
import { HydraniumIndexManager } from './workspace/index-manager.js';
|
|
28
29
|
import { HydraniumWorkspaceManager } from './workspace/hydranium-workspace-manager.js';
|
|
29
30
|
import { HydraniumWorkspaceLock } from './workspace/hydranium-workspace-lock.js';
|
|
@@ -464,6 +465,10 @@ export function createServerSharedModule(
|
|
|
464
465
|
TextDocuments: services => new HydraniumTextDocuments(services),
|
|
465
466
|
WorkspaceManager: services => new HydraniumWorkspaceManager(services),
|
|
466
467
|
IndexManager: services => new HydraniumIndexManager(services),
|
|
468
|
+
// Langium's default asks only about sections named after registered
|
|
469
|
+
// language ids, so a section `lsp.configurationRoot` is bound to reads
|
|
470
|
+
// `undefined` forever with no request on the wire.
|
|
471
|
+
ConfigurationProvider: services => new HydraniumConfigurationProvider(services),
|
|
467
472
|
// Override Langium's factory so a `fromModel` (code-built) virtual
|
|
468
473
|
// document retains serialized text via the per-language Serializer,
|
|
469
474
|
// making it re-read-safe through the virtual-aware FileSystemProvider.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { createServer as createHttpServer, type Server as HttpServer } from 'node:http';
|
|
11
|
+
import { connect, createServer as createTcpServer, type Server as TcpServer, type Socket } from 'node:net';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A TCP proxy that can half-close a connection, so an e2e suite can reproduce a
|
|
15
|
+
* flaky network against a real browser session.
|
|
16
|
+
*
|
|
17
|
+
* Toggling the browser offline is not enough. On localhost the server sees the
|
|
18
|
+
* close immediately and reaps the socket before the browser reconnects, so the
|
|
19
|
+
* window where both sides disagree about the session — the one where reconnect
|
|
20
|
+
* defects live — never opens. Reaching it needs the browser's socket to die
|
|
21
|
+
* while the server still believes it is alive, and nothing inside the app or in
|
|
22
|
+
* Playwright's offline emulation can produce that asymmetry: both close cleanly
|
|
23
|
+
* at both ends.
|
|
24
|
+
*
|
|
25
|
+
* The proxy holds both halves of every connection and lets a test kill them
|
|
26
|
+
* independently:
|
|
27
|
+
*
|
|
28
|
+
* `break` destroy the BROWSER-side sockets, keep the server-side ones open.
|
|
29
|
+
* The browser reconnects through a fresh pair while the server
|
|
30
|
+
* still holds the old one.
|
|
31
|
+
* `release` destroy the orphaned server-side sockets `break` left. The
|
|
32
|
+
* server's disconnect handler fires at once, rather than whenever
|
|
33
|
+
* its next heartbeat write would have failed.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** Where the proxy listens, and the application it fronts. */
|
|
37
|
+
export interface FlakyNetworkProxyOptions {
|
|
38
|
+
/** Port the browser connects to. */
|
|
39
|
+
readonly listenPort: number;
|
|
40
|
+
/** Port of the application being proxied. */
|
|
41
|
+
readonly targetPort: number;
|
|
42
|
+
/** Host of the application being proxied. Defaults to loopback. */
|
|
43
|
+
readonly targetHost?: string;
|
|
44
|
+
/** Port the HTTP control channel listens on. Defaults to `listenPort + 1000`. */
|
|
45
|
+
readonly controlPort?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The commands the control channel accepts, and the proxy's own vocabulary. */
|
|
49
|
+
export type FlakyNetworkCommand = 'break' | 'release' | 'status';
|
|
50
|
+
|
|
51
|
+
/** Drives a running proxy. Implemented both in-process and over HTTP. */
|
|
52
|
+
export interface FlakyNetworkControl {
|
|
53
|
+
/** Kill the browser side of every live connection; leave the server side up. */
|
|
54
|
+
break(): Promise<string>;
|
|
55
|
+
/** Kill the server side of every connection `break` orphaned. */
|
|
56
|
+
release(): Promise<string>;
|
|
57
|
+
/** How many connections are live and how many are orphaned. */
|
|
58
|
+
status(): Promise<string>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface FlakyNetworkProxy extends FlakyNetworkControl {
|
|
62
|
+
/** Base URL a browser should be pointed at, i.e. Playwright's `baseURL`. */
|
|
63
|
+
readonly url: string;
|
|
64
|
+
/** Control channel, for workers that cannot reach this object directly. */
|
|
65
|
+
readonly controlUrl: string;
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface ConnectionPair {
|
|
70
|
+
readonly id: number;
|
|
71
|
+
readonly browserSocket: Socket;
|
|
72
|
+
readonly targetSocket: Socket;
|
|
73
|
+
orphaned: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Starts the proxy in the calling process.
|
|
78
|
+
*
|
|
79
|
+
* Call it from a Playwright config, which runs in the main process before any
|
|
80
|
+
* worker forks. Workers are separate processes and cannot reach the returned
|
|
81
|
+
* object, which is what {@link controlUrl} and {@link flakyNetworkControlClient}
|
|
82
|
+
* are for.
|
|
83
|
+
*/
|
|
84
|
+
export async function startFlakyNetworkProxy(options: FlakyNetworkProxyOptions): Promise<FlakyNetworkProxy> {
|
|
85
|
+
const targetHost = options.targetHost ?? '127.0.0.1';
|
|
86
|
+
const controlPort = options.controlPort ?? options.listenPort + 1000;
|
|
87
|
+
const pairs = new Set<ConnectionPair>();
|
|
88
|
+
let sequence = 0;
|
|
89
|
+
let transcript: string[] = [];
|
|
90
|
+
|
|
91
|
+
const log = (message: string): void => {
|
|
92
|
+
transcript.push(message);
|
|
93
|
+
console.log(`[flaky-network] ${message}`);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const proxyServer: TcpServer = createTcpServer(browserSocket => {
|
|
97
|
+
const id = ++sequence;
|
|
98
|
+
const targetSocket = connect(options.targetPort, targetHost);
|
|
99
|
+
const pair: ConnectionPair = { id, browserSocket, targetSocket, orphaned: false };
|
|
100
|
+
pairs.add(pair);
|
|
101
|
+
|
|
102
|
+
// Forwarded by hand rather than with `pipe()`, so killing one side does not
|
|
103
|
+
// tear the other down — which is the entire point of the proxy.
|
|
104
|
+
browserSocket.on('data', chunk => {
|
|
105
|
+
if (!targetSocket.destroyed) {
|
|
106
|
+
targetSocket.write(chunk);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
targetSocket.on('data', chunk => {
|
|
110
|
+
if (!pair.orphaned && !browserSocket.destroyed) {
|
|
111
|
+
browserSocket.write(chunk);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const teardown = (origin: 'browser' | 'target'): void => {
|
|
116
|
+
if (pair.orphaned && origin === 'browser') {
|
|
117
|
+
// Expected: `break` killed this side deliberately. The server side
|
|
118
|
+
// stays up until `release`.
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
pairs.delete(pair);
|
|
122
|
+
browserSocket.destroy();
|
|
123
|
+
targetSocket.destroy();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
browserSocket.on('close', () => teardown('browser'));
|
|
127
|
+
targetSocket.on('close', () => teardown('target'));
|
|
128
|
+
// A half-killed pair reliably raises ECONNRESET on the surviving side; an
|
|
129
|
+
// unhandled 'error' would take the whole runner down with it.
|
|
130
|
+
browserSocket.on('error', () => undefined);
|
|
131
|
+
targetSocket.on('error', () => undefined);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const commands: Record<FlakyNetworkCommand, () => void> = {
|
|
135
|
+
break: () => {
|
|
136
|
+
const live = [...pairs].filter(pair => !pair.orphaned);
|
|
137
|
+
if (live.length === 0) {
|
|
138
|
+
log('nothing to break');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const pair of live) {
|
|
142
|
+
pair.orphaned = true;
|
|
143
|
+
pair.browserSocket.destroy();
|
|
144
|
+
}
|
|
145
|
+
log(`${live.length} connection(s) broken; the browser should reconnect shortly`);
|
|
146
|
+
},
|
|
147
|
+
release: () => {
|
|
148
|
+
const orphans = [...pairs].filter(pair => pair.orphaned);
|
|
149
|
+
if (orphans.length === 0) {
|
|
150
|
+
log('no orphaned connections; run `break` first');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const pair of orphans) {
|
|
154
|
+
pairs.delete(pair);
|
|
155
|
+
pair.targetSocket.destroy();
|
|
156
|
+
}
|
|
157
|
+
log(`${orphans.length} orphaned connection(s) destroyed; their late disconnect fires now`);
|
|
158
|
+
},
|
|
159
|
+
status: () => {
|
|
160
|
+
const live = [...pairs].filter(pair => !pair.orphaned).length;
|
|
161
|
+
const orphaned = pairs.size - live;
|
|
162
|
+
log(`${live} live, ${orphaned} orphaned`);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const run = (name: string): string => {
|
|
167
|
+
transcript = [];
|
|
168
|
+
const command = commands[name as FlakyNetworkCommand];
|
|
169
|
+
if (command) {
|
|
170
|
+
command();
|
|
171
|
+
} else {
|
|
172
|
+
log(`unknown command '${name}'; use: ${Object.keys(commands).join(' | ')}`);
|
|
173
|
+
}
|
|
174
|
+
return transcript.join('\n');
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const controlServer: HttpServer = createHttpServer((request, response) => {
|
|
178
|
+
const output = run((request.url ?? '').replace(/^\//, '').split('?')[0]);
|
|
179
|
+
response.writeHead(200, { 'content-type': 'text/plain' });
|
|
180
|
+
response.end(`${output}\n`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await Promise.all([listen(proxyServer, options.listenPort), listen(controlServer, controlPort)]);
|
|
184
|
+
// Neither server keeps the process alive, so a runner that starts one from a
|
|
185
|
+
// config does not need a teardown hook to exit — and a caller that wants a
|
|
186
|
+
// deterministic shutdown still has `close()`.
|
|
187
|
+
proxyServer.unref();
|
|
188
|
+
controlServer.unref();
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
url: `http://localhost:${options.listenPort}`,
|
|
192
|
+
controlUrl: `http://localhost:${controlPort}`,
|
|
193
|
+
break: async () => run('break'),
|
|
194
|
+
release: async () => run('release'),
|
|
195
|
+
status: async () => run('status'),
|
|
196
|
+
close: async () => {
|
|
197
|
+
for (const pair of pairs) {
|
|
198
|
+
pair.browserSocket.destroy();
|
|
199
|
+
pair.targetSocket.destroy();
|
|
200
|
+
}
|
|
201
|
+
pairs.clear();
|
|
202
|
+
await Promise.all([close(proxyServer), close(controlServer)]);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Drives a proxy running in another process, over its control channel.
|
|
209
|
+
*
|
|
210
|
+
* This is what a spec uses: Playwright workers are separate processes from the
|
|
211
|
+
* config that started the proxy.
|
|
212
|
+
*/
|
|
213
|
+
export function flakyNetworkControlClient(controlUrl: string): FlakyNetworkControl {
|
|
214
|
+
const send = async (command: FlakyNetworkCommand): Promise<string> => {
|
|
215
|
+
const response = await fetch(`${controlUrl}/${command}`);
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error(`flaky-network proxy rejected '${command}': ${response.status}`);
|
|
218
|
+
}
|
|
219
|
+
return (await response.text()).trim();
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
break: () => send('break'),
|
|
223
|
+
release: () => send('release'),
|
|
224
|
+
status: () => send('status')
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function listen(server: TcpServer | HttpServer, port: number): Promise<void> {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
server.once('error', reject);
|
|
231
|
+
server.listen(port, () => {
|
|
232
|
+
server.off('error', reject);
|
|
233
|
+
resolve();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function close(server: TcpServer | HttpServer): Promise<void> {
|
|
239
|
+
return new Promise(resolve => server.close(() => resolve()));
|
|
240
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// `@hydranium/core/testing` barrel) so importing the framework's unit-test
|
|
12
12
|
// helpers never pulls in `@playwright/test` — which is an OPTIONAL peer here.
|
|
13
13
|
export * from './e2e-profiling.js';
|
|
14
|
+
export * from './flaky-network-proxy.js';
|
|
14
15
|
export * from './browser-capture-bridge.js';
|
|
15
16
|
export * from './server-log-capture.js';
|
|
16
17
|
export * from './server-log-fixture.js';
|