rightsize 0.1.0
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +255 -0
- package/dist/backend-docker/backend.d.ts +53 -0
- package/dist/backend-docker/backend.d.ts.map +1 -0
- package/dist/backend-docker/backend.js +336 -0
- package/dist/backend-docker/backend.js.map +1 -0
- package/dist/backend-docker/client.d.ts +82 -0
- package/dist/backend-docker/client.d.ts.map +1 -0
- package/dist/backend-docker/client.js +190 -0
- package/dist/backend-docker/client.js.map +1 -0
- package/dist/backend-docker/frames.d.ts +44 -0
- package/dist/backend-docker/frames.d.ts.map +1 -0
- package/dist/backend-docker/frames.js +105 -0
- package/dist/backend-docker/frames.js.map +1 -0
- package/dist/backend-docker/index.d.ts +5 -0
- package/dist/backend-docker/index.d.ts.map +1 -0
- package/dist/backend-docker/index.js +20 -0
- package/dist/backend-docker/index.js.map +1 -0
- package/dist/backend-docker/json.d.ts +13 -0
- package/dist/backend-docker/json.d.ts.map +1 -0
- package/dist/backend-docker/json.js +47 -0
- package/dist/backend-docker/json.js.map +1 -0
- package/dist/backend-docker/labels.d.ts +11 -0
- package/dist/backend-docker/labels.d.ts.map +1 -0
- package/dist/backend-docker/labels.js +13 -0
- package/dist/backend-docker/labels.js.map +1 -0
- package/dist/backend-docker/port-conflict.d.ts +9 -0
- package/dist/backend-docker/port-conflict.d.ts.map +1 -0
- package/dist/backend-docker/port-conflict.js +12 -0
- package/dist/backend-docker/port-conflict.js.map +1 -0
- package/dist/backend-docker/provider.d.ts +22 -0
- package/dist/backend-docker/provider.d.ts.map +1 -0
- package/dist/backend-docker/provider.js +36 -0
- package/dist/backend-docker/provider.js.map +1 -0
- package/dist/backend-msb/backend.d.ts +169 -0
- package/dist/backend-msb/backend.d.ts.map +1 -0
- package/dist/backend-msb/backend.js +664 -0
- package/dist/backend-msb/backend.js.map +1 -0
- package/dist/backend-msb/commands.d.ts +28 -0
- package/dist/backend-msb/commands.d.ts.map +1 -0
- package/dist/backend-msb/commands.js +67 -0
- package/dist/backend-msb/commands.js.map +1 -0
- package/dist/backend-msb/exec-tunnel.d.ts +33 -0
- package/dist/backend-msb/exec-tunnel.d.ts.map +1 -0
- package/dist/backend-msb/exec-tunnel.js +163 -0
- package/dist/backend-msb/exec-tunnel.js.map +1 -0
- package/dist/backend-msb/follow-replay.d.ts +14 -0
- package/dist/backend-msb/follow-replay.d.ts.map +1 -0
- package/dist/backend-msb/follow-replay.js +20 -0
- package/dist/backend-msb/follow-replay.js.map +1 -0
- package/dist/backend-msb/image-cache.d.ts +25 -0
- package/dist/backend-msb/image-cache.d.ts.map +1 -0
- package/dist/backend-msb/image-cache.js +27 -0
- package/dist/backend-msb/image-cache.js.map +1 -0
- package/dist/backend-msb/index.d.ts +6 -0
- package/dist/backend-msb/index.d.ts.map +1 -0
- package/dist/backend-msb/index.js +21 -0
- package/dist/backend-msb/index.js.map +1 -0
- package/dist/backend-msb/invoke.d.ts +24 -0
- package/dist/backend-msb/invoke.d.ts.map +1 -0
- package/dist/backend-msb/invoke.js +73 -0
- package/dist/backend-msb/invoke.js.map +1 -0
- package/dist/backend-msb/ls-json.d.ts +23 -0
- package/dist/backend-msb/ls-json.d.ts.map +1 -0
- package/dist/backend-msb/ls-json.js +113 -0
- package/dist/backend-msb/ls-json.js.map +1 -0
- package/dist/backend-msb/network-links.d.ts +7 -0
- package/dist/backend-msb/network-links.d.ts.map +1 -0
- package/dist/backend-msb/network-links.js +31 -0
- package/dist/backend-msb/network-links.js.map +1 -0
- package/dist/backend-msb/platform.d.ts +29 -0
- package/dist/backend-msb/platform.d.ts.map +1 -0
- package/dist/backend-msb/platform.js +128 -0
- package/dist/backend-msb/platform.js.map +1 -0
- package/dist/backend-msb/port-conflict.d.ts +10 -0
- package/dist/backend-msb/port-conflict.d.ts.map +1 -0
- package/dist/backend-msb/port-conflict.js +16 -0
- package/dist/backend-msb/port-conflict.js.map +1 -0
- package/dist/backend-msb/provider.d.ts +10 -0
- package/dist/backend-msb/provider.d.ts.map +1 -0
- package/dist/backend-msb/provider.js +40 -0
- package/dist/backend-msb/provider.js.map +1 -0
- package/dist/backend-msb/provisioner.d.ts +19 -0
- package/dist/backend-msb/provisioner.d.ts.map +1 -0
- package/dist/backend-msb/provisioner.js +332 -0
- package/dist/backend-msb/provisioner.js.map +1 -0
- package/dist/backend-msb/reaper.d.ts +10 -0
- package/dist/backend-msb/reaper.d.ts.map +1 -0
- package/dist/backend-msb/reaper.js +73 -0
- package/dist/backend-msb/reaper.js.map +1 -0
- package/dist/core/backend.d.ts +98 -0
- package/dist/core/backend.d.ts.map +1 -0
- package/dist/core/backend.js +2 -0
- package/dist/core/backend.js.map +1 -0
- package/dist/core/backends.d.ts +37 -0
- package/dist/core/backends.d.ts.map +1 -0
- package/dist/core/backends.js +127 -0
- package/dist/core/backends.js.map +1 -0
- package/dist/core/cleanup.d.ts +37 -0
- package/dist/core/cleanup.d.ts.map +1 -0
- package/dist/core/cleanup.js +86 -0
- package/dist/core/cleanup.js.map +1 -0
- package/dist/core/errors.d.ts +51 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +66 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/free-ports.d.ts +23 -0
- package/dist/core/free-ports.d.ts.map +1 -0
- package/dist/core/free-ports.js +63 -0
- package/dist/core/free-ports.js.map +1 -0
- package/dist/core/generic-container.d.ts +145 -0
- package/dist/core/generic-container.d.ts.map +1 -0
- package/dist/core/generic-container.js +385 -0
- package/dist/core/generic-container.js.map +1 -0
- package/dist/core/model.d.ts +60 -0
- package/dist/core/model.d.ts.map +1 -0
- package/dist/core/model.js +2 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/mountable-file.d.ts +25 -0
- package/dist/core/mountable-file.d.ts.map +1 -0
- package/dist/core/mountable-file.js +64 -0
- package/dist/core/mountable-file.js.map +1 -0
- package/dist/core/network.d.ts +58 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/network.js +84 -0
- package/dist/core/network.js.map +1 -0
- package/dist/core/run-id.d.ts +18 -0
- package/dist/core/run-id.d.ts.map +1 -0
- package/dist/core/run-id.js +18 -0
- package/dist/core/run-id.js.map +1 -0
- package/dist/core/wait.d.ts +59 -0
- package/dist/core/wait.d.ts.map +1 -0
- package/dist/core/wait.js +239 -0
- package/dist/core/wait.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/arango.d.ts +14 -0
- package/dist/modules/arango.d.ts.map +1 -0
- package/dist/modules/arango.js +37 -0
- package/dist/modules/arango.js.map +1 -0
- package/dist/modules/clickhouse.d.ts +37 -0
- package/dist/modules/clickhouse.d.ts.map +1 -0
- package/dist/modules/clickhouse.js +69 -0
- package/dist/modules/clickhouse.js.map +1 -0
- package/dist/modules/flink.d.ts +65 -0
- package/dist/modules/flink.d.ts.map +1 -0
- package/dist/modules/flink.js +125 -0
- package/dist/modules/flink.js.map +1 -0
- package/dist/modules/floci.d.ts +46 -0
- package/dist/modules/floci.d.ts.map +1 -0
- package/dist/modules/floci.js +62 -0
- package/dist/modules/floci.js.map +1 -0
- package/dist/modules/index.d.ts +31 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +31 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/kafka.d.ts +11 -0
- package/dist/modules/kafka.d.ts.map +1 -0
- package/dist/modules/kafka.js +41 -0
- package/dist/modules/kafka.js.map +1 -0
- package/dist/modules/keycloak.d.ts +40 -0
- package/dist/modules/keycloak.d.ts.map +1 -0
- package/dist/modules/keycloak.js +67 -0
- package/dist/modules/keycloak.js.map +1 -0
- package/dist/modules/mariadb.d.ts +56 -0
- package/dist/modules/mariadb.d.ts.map +1 -0
- package/dist/modules/mariadb.js +89 -0
- package/dist/modules/mariadb.js.map +1 -0
- package/dist/modules/memcached.d.ts +24 -0
- package/dist/modules/memcached.d.ts.map +1 -0
- package/dist/modules/memcached.js +105 -0
- package/dist/modules/memcached.js.map +1 -0
- package/dist/modules/mongodb.d.ts +19 -0
- package/dist/modules/mongodb.d.ts.map +1 -0
- package/dist/modules/mongodb.js +69 -0
- package/dist/modules/mongodb.js.map +1 -0
- package/dist/modules/mysql.d.ts +61 -0
- package/dist/modules/mysql.d.ts.map +1 -0
- package/dist/modules/mysql.js +101 -0
- package/dist/modules/mysql.js.map +1 -0
- package/dist/modules/neo4j.d.ts +60 -0
- package/dist/modules/neo4j.d.ts.map +1 -0
- package/dist/modules/neo4j.js +83 -0
- package/dist/modules/neo4j.js.map +1 -0
- package/dist/modules/pinot.d.ts +30 -0
- package/dist/modules/pinot.d.ts.map +1 -0
- package/dist/modules/pinot.js +49 -0
- package/dist/modules/pinot.js.map +1 -0
- package/dist/modules/postgres.d.ts +29 -0
- package/dist/modules/postgres.d.ts.map +1 -0
- package/dist/modules/postgres.js +73 -0
- package/dist/modules/postgres.js.map +1 -0
- package/dist/modules/rabbitmq.d.ts +46 -0
- package/dist/modules/rabbitmq.d.ts.map +1 -0
- package/dist/modules/rabbitmq.js +68 -0
- package/dist/modules/rabbitmq.js.map +1 -0
- package/dist/modules/redis.d.ts +15 -0
- package/dist/modules/redis.d.ts.map +1 -0
- package/dist/modules/redis.js +24 -0
- package/dist/modules/redis.js.map +1 -0
- package/dist/modules/redpanda.d.ts +27 -0
- package/dist/modules/redpanda.d.ts.map +1 -0
- package/dist/modules/redpanda.js +58 -0
- package/dist/modules/redpanda.js.map +1 -0
- package/dist/modules/spring-cloud-config.d.ts +18 -0
- package/dist/modules/spring-cloud-config.d.ts.map +1 -0
- package/dist/modules/spring-cloud-config.js +28 -0
- package/dist/modules/spring-cloud-config.js.map +1 -0
- package/dist/modules/wiremock.d.ts +21 -0
- package/dist/modules/wiremock.d.ts.map +1 -0
- package/dist/modules/wiremock.js +32 -0
- package/dist/modules/wiremock.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The ServiceLoader analog: an explicit process-global registry. Each
|
|
2
|
+
// backend subpath (`rightsize/backend-msb`, `rightsize/backend-docker`)
|
|
3
|
+
// calls registerBackend at import time — the `sideEffects` entry in
|
|
4
|
+
// package.json exists so bundlers keep that call.
|
|
5
|
+
const registered = [];
|
|
6
|
+
/** Adds a provider to the process-global registry. Called once, at import time, by each backend subpath's `index.ts`. */
|
|
7
|
+
export function registerBackend(provider) {
|
|
8
|
+
registered.push(provider);
|
|
9
|
+
}
|
|
10
|
+
/** Test seam: clears the process-global registry. Never call from library code. */
|
|
11
|
+
export function _resetRegistryForTests() {
|
|
12
|
+
registered.length = 0;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Test seam: snapshot of the currently registered providers, so an impure
|
|
16
|
+
* suite that wipes the registry can put the real ones back when it finishes —
|
|
17
|
+
* under a single-process test runner, whatever it leaves behind is what every
|
|
18
|
+
* later-loaded test file sees.
|
|
19
|
+
*/
|
|
20
|
+
export function _providersSnapshotForTests() {
|
|
21
|
+
return [...registered];
|
|
22
|
+
}
|
|
23
|
+
function knownNames(providers) {
|
|
24
|
+
return providers.map((p) => p.name).join(", ");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pure resolution: no I/O, no module-global registry access, so it is fully
|
|
28
|
+
* unit-testable with fake providers. `Backends.active()` is the impure
|
|
29
|
+
* caller that supplies the real registry and `process.env`.
|
|
30
|
+
*/
|
|
31
|
+
export function resolve(providers, requested) {
|
|
32
|
+
if (providers.length === 0) {
|
|
33
|
+
throw new Error("No backend providers are registered. Import 'rightsize/backend-msb' or 'rightsize/backend-docker' to register one.");
|
|
34
|
+
}
|
|
35
|
+
if (requested !== undefined) {
|
|
36
|
+
const wanted = requested.toLowerCase();
|
|
37
|
+
const match = providers.find((p) => p.name.toLowerCase() === wanted);
|
|
38
|
+
if (match === undefined) {
|
|
39
|
+
throw new Error(`Unknown backend '${requested}'. Known backends: ${knownNames(providers)}.`);
|
|
40
|
+
}
|
|
41
|
+
if (!match.isSupported()) {
|
|
42
|
+
throw new Error(`Backend '${match.name}' was requested but is not supported here: ${match.unsupportedReason()}`);
|
|
43
|
+
}
|
|
44
|
+
return match.create();
|
|
45
|
+
}
|
|
46
|
+
const supported = providers.filter((p) => p.isSupported());
|
|
47
|
+
if (supported.length === 0) {
|
|
48
|
+
const reasons = providers.map((p) => `${p.name}: ${p.unsupportedReason()}`).join("; ");
|
|
49
|
+
throw new Error(`No supported backend found. ${reasons}`);
|
|
50
|
+
}
|
|
51
|
+
const winner = supported.reduce((best, candidate) => (candidate.priority > best.priority ? candidate : best));
|
|
52
|
+
return winner.create();
|
|
53
|
+
}
|
|
54
|
+
let activeBackend;
|
|
55
|
+
let exitHookInstalled = false;
|
|
56
|
+
/**
|
|
57
|
+
* Resolves once per process (memoized) against the real registry and
|
|
58
|
+
* `process.env.RIGHTSIZE_BACKEND`, then registers the best-effort exit-path
|
|
59
|
+
* cleanup (see `src/core/cleanup.ts`) the first time a backend is created.
|
|
60
|
+
*/
|
|
61
|
+
function active() {
|
|
62
|
+
if (activeBackend === undefined) {
|
|
63
|
+
activeBackend = resolve(registered, process.env["RIGHTSIZE_BACKEND"]);
|
|
64
|
+
if (!exitHookInstalled) {
|
|
65
|
+
exitHookInstalled = true;
|
|
66
|
+
let closeStarted = false;
|
|
67
|
+
// "beforeExit" fires while the event loop is still alive (unlike
|
|
68
|
+
// "exit", which runs synchronously and cannot await), so it's the one
|
|
69
|
+
// hook where the backend's real async close() can actually run. It is
|
|
70
|
+
// NOT a true shutdown hook, though: it fires at every full event-loop
|
|
71
|
+
// drain, not only at process exit. With the once-guard below, close()
|
|
72
|
+
// therefore runs at the FIRST complete drain — in a process that goes
|
|
73
|
+
// fully idle between containers, that can be before its last container
|
|
74
|
+
// was booted. Accepted tradeoff: a completely drained loop has no
|
|
75
|
+
// pending timers, sockets, or awaits, so nothing in the process is
|
|
76
|
+
// still waiting on those containers, and both backends stay usable
|
|
77
|
+
// after close() (stateless per-request clients) for any later boot.
|
|
78
|
+
// This is best-effort and NOT the primary safety net: a process that
|
|
79
|
+
// dies via SIGKILL, or exits through the synchronous "exit" path
|
|
80
|
+
// before beforeExit ever fires, is instead covered by the per-
|
|
81
|
+
// container cleanupSync registered in cleanup.ts and by the orphan
|
|
82
|
+
// reaper each backend runs at construction.
|
|
83
|
+
//
|
|
84
|
+
// "beforeExit" re-fires every time the event loop would otherwise go
|
|
85
|
+
// idle. close() is async — it awaits HTTP/subprocess calls — so
|
|
86
|
+
// invoking it unconditionally here schedules new event-loop work on
|
|
87
|
+
// every firing, which makes the NEXT idle point trigger this same
|
|
88
|
+
// handler again: an infinite close()-then-beforeExit cycle that never
|
|
89
|
+
// lets the process exit (observed as a `node --test` run hanging
|
|
90
|
+
// indefinitely after every assertion had already passed). The
|
|
91
|
+
// closeStarted guard makes this a once-per-process call — the single
|
|
92
|
+
// shutdown pass a process-lifetime cleanup hook is meant to be.
|
|
93
|
+
process.on("beforeExit", () => {
|
|
94
|
+
if (closeStarted) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
closeStarted = true;
|
|
98
|
+
// Best-effort: a backend whose close() rejects — or, as a defensive
|
|
99
|
+
// fake in a test can do, throws synchronously before ever producing
|
|
100
|
+
// a Promise to attach .catch() to — must not crash the process on
|
|
101
|
+
// its way out. There is no caller left to report the failure to.
|
|
102
|
+
try {
|
|
103
|
+
activeBackend?.close().catch(() => { });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Swallowed for the same reason as the .catch() above.
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return activeBackend;
|
|
112
|
+
}
|
|
113
|
+
/** Test seam: forces the next `Backends.active()` call to re-resolve. */
|
|
114
|
+
function _resetActiveForTests() {
|
|
115
|
+
activeBackend = undefined;
|
|
116
|
+
exitHookInstalled = false;
|
|
117
|
+
}
|
|
118
|
+
/** The library's single entry point for obtaining the active `SandboxBackend`. */
|
|
119
|
+
export const Backends = {
|
|
120
|
+
/** Pure resolution against an explicit provider list — see `resolve` above. */
|
|
121
|
+
resolve,
|
|
122
|
+
/** The memoized, per-process active backend — resolved from whatever's registered plus `RIGHTSIZE_BACKEND`. */
|
|
123
|
+
active,
|
|
124
|
+
/** Test seam: forces the next `active()` call to re-resolve. Never call from library code. */
|
|
125
|
+
_resetActiveForTests,
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=backends.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backends.js","sourceRoot":"","sources":["../../src/core/backends.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort exit-path cleanup — the JVM-shutdown-hook / Rust-cleanup-
|
|
3
|
+
* thread analog for a process that dies (or is asked to die) before its
|
|
4
|
+
* `await using` scopes unwind normally.
|
|
5
|
+
*
|
|
6
|
+
* Node's `process.on("exit", ...)` handler runs SYNCHRONOUSLY and cannot
|
|
7
|
+
* `await` — by the time it fires, the event loop is already being torn
|
|
8
|
+
* down, so a backend's real async `stop()`/`remove()` cannot run there.
|
|
9
|
+
* Each backend therefore registers a synchronous, blocking teardown
|
|
10
|
+
* function per live container (`child_process.spawnSync` for msb, a
|
|
11
|
+
* blocking unix-socket DELETE for docker) instead of its normal async
|
|
12
|
+
* path. This module only owns the REGISTRY and the process hooks; it has
|
|
13
|
+
* no opinion on how any one backend tears a container down.
|
|
14
|
+
*
|
|
15
|
+
* This is a last-resort backstop, not the primary cleanup path — the
|
|
16
|
+
* primary path is `GenericContainer.stop()` / `[Symbol.asyncDispose]`.
|
|
17
|
+
* SIGKILL bypasses even this (no handler runs at all); the orphan reaper
|
|
18
|
+
* each backend runs at startup (`sweepOrphans`) is the backstop for that
|
|
19
|
+
* case, sweeping up `rz-<other-runid>-*` leftovers from a crashed prior
|
|
20
|
+
* run.
|
|
21
|
+
*/
|
|
22
|
+
export type SyncCleanup = () => void;
|
|
23
|
+
/** Registers a synchronous teardown for a live container, keyed by handle id. */
|
|
24
|
+
export declare function registerSyncCleanup(handleId: string, cleanupFn: SyncCleanup): void;
|
|
25
|
+
/** Unregisters a container's teardown once it has been stopped/removed normally. */
|
|
26
|
+
export declare function unregisterSyncCleanup(handleId: string): void;
|
|
27
|
+
/** Test seam: clears registered cleanups without running them. */
|
|
28
|
+
export declare function _resetForTests(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Test seam: runs every registered cleanup exactly the way the real "exit"
|
|
31
|
+
* handler does (best-effort, swallowing failures, then clearing the
|
|
32
|
+
* registry) without actually terminating the test process. This is how a
|
|
33
|
+
* unit test proves the exit-path teardown is wired to something real,
|
|
34
|
+
* rather than asserting on `registered`'s internal size.
|
|
35
|
+
*/
|
|
36
|
+
export declare function _runAllForTests(): void;
|
|
37
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/core/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AA+CrC,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,GAAG,IAAI,CAGlF;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,kEAAkE;AAClE,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort exit-path cleanup — the JVM-shutdown-hook / Rust-cleanup-
|
|
3
|
+
* thread analog for a process that dies (or is asked to die) before its
|
|
4
|
+
* `await using` scopes unwind normally.
|
|
5
|
+
*
|
|
6
|
+
* Node's `process.on("exit", ...)` handler runs SYNCHRONOUSLY and cannot
|
|
7
|
+
* `await` — by the time it fires, the event loop is already being torn
|
|
8
|
+
* down, so a backend's real async `stop()`/`remove()` cannot run there.
|
|
9
|
+
* Each backend therefore registers a synchronous, blocking teardown
|
|
10
|
+
* function per live container (`child_process.spawnSync` for msb, a
|
|
11
|
+
* blocking unix-socket DELETE for docker) instead of its normal async
|
|
12
|
+
* path. This module only owns the REGISTRY and the process hooks; it has
|
|
13
|
+
* no opinion on how any one backend tears a container down.
|
|
14
|
+
*
|
|
15
|
+
* This is a last-resort backstop, not the primary cleanup path — the
|
|
16
|
+
* primary path is `GenericContainer.stop()` / `[Symbol.asyncDispose]`.
|
|
17
|
+
* SIGKILL bypasses even this (no handler runs at all); the orphan reaper
|
|
18
|
+
* each backend runs at startup (`sweepOrphans`) is the backstop for that
|
|
19
|
+
* case, sweeping up `rz-<other-runid>-*` leftovers from a crashed prior
|
|
20
|
+
* run.
|
|
21
|
+
*/
|
|
22
|
+
const registered = new Map();
|
|
23
|
+
let hooksInstalled = false;
|
|
24
|
+
function runAll() {
|
|
25
|
+
for (const cleanupFn of registered.values()) {
|
|
26
|
+
try {
|
|
27
|
+
cleanupFn();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Best-effort: a failure to clean up one container must not block
|
|
31
|
+
// cleanup of the others, and the process is exiting regardless.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
registered.clear();
|
|
35
|
+
}
|
|
36
|
+
function installHooksOnce() {
|
|
37
|
+
if (hooksInstalled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
hooksInstalled = true;
|
|
41
|
+
process.on("exit", runAll);
|
|
42
|
+
// SIGINT/SIGTERM: run the same synchronous cleanup, then re-raise the
|
|
43
|
+
// signal so the process still exits the way it would have without this
|
|
44
|
+
// handler (correct exit code, no swallowed Ctrl-C).
|
|
45
|
+
//
|
|
46
|
+
// On Windows this pair is best-effort, not the real safety net: Node
|
|
47
|
+
// delivers a synthetic SIGINT for Ctrl+C (and SIGBREAK for Ctrl+Break,
|
|
48
|
+
// which is not registered here) but there is no real SIGTERM delivery from
|
|
49
|
+
// another process the way POSIX has it — Windows has no signal-based IPC
|
|
50
|
+
// for that. The interactive Ctrl+C path still fires this handler as
|
|
51
|
+
// expected; a `taskkill`-style external termination does not. The `"exit"`
|
|
52
|
+
// handler above is therefore the one Windows-portable guarantee — it fires
|
|
53
|
+
// on every normal process exit regardless of platform or what triggered
|
|
54
|
+
// it — while this signal-handler loop is a POSIX nicety layered on top.
|
|
55
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
56
|
+
process.on(signal, () => {
|
|
57
|
+
runAll();
|
|
58
|
+
process.removeAllListeners(signal);
|
|
59
|
+
process.kill(process.pid, signal);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Registers a synchronous teardown for a live container, keyed by handle id. */
|
|
64
|
+
export function registerSyncCleanup(handleId, cleanupFn) {
|
|
65
|
+
installHooksOnce();
|
|
66
|
+
registered.set(handleId, cleanupFn);
|
|
67
|
+
}
|
|
68
|
+
/** Unregisters a container's teardown once it has been stopped/removed normally. */
|
|
69
|
+
export function unregisterSyncCleanup(handleId) {
|
|
70
|
+
registered.delete(handleId);
|
|
71
|
+
}
|
|
72
|
+
/** Test seam: clears registered cleanups without running them. */
|
|
73
|
+
export function _resetForTests() {
|
|
74
|
+
registered.clear();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Test seam: runs every registered cleanup exactly the way the real "exit"
|
|
78
|
+
* handler does (best-effort, swallowing failures, then clearing the
|
|
79
|
+
* registry) without actually terminating the test process. This is how a
|
|
80
|
+
* unit test proves the exit-path teardown is wired to something real,
|
|
81
|
+
* rather than asserting on `registered`'s internal size.
|
|
82
|
+
*/
|
|
83
|
+
export function _runAllForTests() {
|
|
84
|
+
runAll();
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/core/cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;AAClD,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,SAAS,MAAM;IACb,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,SAAS,EAAE,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;YAClE,gEAAgE;QAClE,CAAC;IACH,CAAC;IACD,UAAU,CAAC,KAAK,EAAE,CAAC;AACrB,CAAC;AAED,SAAS,gBAAgB;IACvB,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IACD,cAAc,GAAG,IAAI,CAAC;IAEtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3B,sEAAsE;IACtE,uEAAuE;IACvE,oDAAoD;IACpD,EAAE;IACF,qEAAqE;IACrE,uEAAuE;IACvE,2EAA2E;IAC3E,yEAAyE;IACzE,oEAAoE;IACpE,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,wEAAwE;IACxE,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;QACpD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACtB,MAAM,EAAE,CAAC;YACT,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;YACnC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,SAAsB;IAC1E,gBAAgB,EAAE,CAAC;IACnB,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACtC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,cAAc;IAC5B,UAAU,CAAC,KAAK,EAAE,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `feature` stays a noun phrase ("network links", "read-only mount
|
|
3
|
+
* enforcement") and advice lives in `remedy`; the two are composed with an
|
|
4
|
+
* em-dash so every backend renders the same sentence shape.
|
|
5
|
+
*
|
|
6
|
+
* @param feature - A noun phrase naming the unsupported capability.
|
|
7
|
+
* @param backend - The active backend's name (e.g. `"microsandbox"`).
|
|
8
|
+
* @param remedy - Optional actionable advice, appended after an em-dash.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UnsupportedByBackendError extends Error {
|
|
11
|
+
/** A noun phrase naming the unsupported capability. */
|
|
12
|
+
readonly feature: string;
|
|
13
|
+
/** The active backend's name (e.g. `"microsandbox"`). */
|
|
14
|
+
readonly backend: string;
|
|
15
|
+
/** Optional actionable advice, appended after an em-dash in the rendered message. */
|
|
16
|
+
readonly remedy?: string | undefined;
|
|
17
|
+
constructor(
|
|
18
|
+
/** A noun phrase naming the unsupported capability. */
|
|
19
|
+
feature: string,
|
|
20
|
+
/** The active backend's name (e.g. `"microsandbox"`). */
|
|
21
|
+
backend: string,
|
|
22
|
+
/** Optional actionable advice, appended after an em-dash in the rendered message. */
|
|
23
|
+
remedy?: string | undefined);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Thrown when a backend's `start()` fails because a chosen host port is
|
|
27
|
+
* already bound by something else. `GenericContainer`'s start loop classifies
|
|
28
|
+
* this (typed-first, message-substring fallback) and retries with fresh
|
|
29
|
+
* ports rather than surfacing it directly — it only escapes after every
|
|
30
|
+
* retry attempt is exhausted.
|
|
31
|
+
*/
|
|
32
|
+
export declare class PortBindConflictError extends Error {
|
|
33
|
+
/** The underlying error or daemon response this classification was derived from, if any. */
|
|
34
|
+
readonly cause?: unknown | undefined;
|
|
35
|
+
constructor(message: string,
|
|
36
|
+
/** The underlying error or daemon response this classification was derived from, if any. */
|
|
37
|
+
cause?: unknown | undefined);
|
|
38
|
+
}
|
|
39
|
+
/** Thrown when a wait strategy never observes readiness before its deadline. */
|
|
40
|
+
export declare class ContainerLaunchError extends Error {
|
|
41
|
+
constructor(message: string);
|
|
42
|
+
}
|
|
43
|
+
/** Thrown on subprocess/daemon failures; message carries full stderr/body. */
|
|
44
|
+
export declare class BackendError extends Error {
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
/** Thrown when the msb toolchain provisioner can't get a runnable `msb` binary in place (download, checksum, or lock failure). */
|
|
48
|
+
export declare class ProvisionError extends Error {
|
|
49
|
+
constructor(message: string);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAEhD,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;;IALxB,uDAAuD;IAC9C,OAAO,EAAE,MAAM;IACxB,yDAAyD;IAChD,OAAO,EAAE,MAAM;IACxB,qFAAqF;IAC5E,MAAM,CAAC,EAAE,MAAM,YAAA;CAK3B;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAG5C,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAFxB,OAAO,EAAE,MAAM;IACf,4FAA4F;IACnF,KAAK,CAAC,EAAE,OAAO,YAAA;CAK3B;AAED,gFAAgF;AAChF,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,8EAA8E;AAC9E,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,kIAAkI;AAClI,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `feature` stays a noun phrase ("network links", "read-only mount
|
|
3
|
+
* enforcement") and advice lives in `remedy`; the two are composed with an
|
|
4
|
+
* em-dash so every backend renders the same sentence shape.
|
|
5
|
+
*
|
|
6
|
+
* @param feature - A noun phrase naming the unsupported capability.
|
|
7
|
+
* @param backend - The active backend's name (e.g. `"microsandbox"`).
|
|
8
|
+
* @param remedy - Optional actionable advice, appended after an em-dash.
|
|
9
|
+
*/
|
|
10
|
+
export class UnsupportedByBackendError extends Error {
|
|
11
|
+
feature;
|
|
12
|
+
backend;
|
|
13
|
+
remedy;
|
|
14
|
+
constructor(
|
|
15
|
+
/** A noun phrase naming the unsupported capability. */
|
|
16
|
+
feature,
|
|
17
|
+
/** The active backend's name (e.g. `"microsandbox"`). */
|
|
18
|
+
backend,
|
|
19
|
+
/** Optional actionable advice, appended after an em-dash in the rendered message. */
|
|
20
|
+
remedy) {
|
|
21
|
+
super(`Feature '${feature}' is not supported by the '${backend}' backend${remedy ? ` — ${remedy}` : ""}`);
|
|
22
|
+
this.feature = feature;
|
|
23
|
+
this.backend = backend;
|
|
24
|
+
this.remedy = remedy;
|
|
25
|
+
this.name = "UnsupportedByBackendError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when a backend's `start()` fails because a chosen host port is
|
|
30
|
+
* already bound by something else. `GenericContainer`'s start loop classifies
|
|
31
|
+
* this (typed-first, message-substring fallback) and retries with fresh
|
|
32
|
+
* ports rather than surfacing it directly — it only escapes after every
|
|
33
|
+
* retry attempt is exhausted.
|
|
34
|
+
*/
|
|
35
|
+
export class PortBindConflictError extends Error {
|
|
36
|
+
cause;
|
|
37
|
+
constructor(message,
|
|
38
|
+
/** The underlying error or daemon response this classification was derived from, if any. */
|
|
39
|
+
cause) {
|
|
40
|
+
super(message, cause !== undefined ? { cause } : undefined);
|
|
41
|
+
this.cause = cause;
|
|
42
|
+
this.name = "PortBindConflictError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Thrown when a wait strategy never observes readiness before its deadline. */
|
|
46
|
+
export class ContainerLaunchError extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "ContainerLaunchError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Thrown on subprocess/daemon failures; message carries full stderr/body. */
|
|
53
|
+
export class BackendError extends Error {
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "BackendError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Thrown when the msb toolchain provisioner can't get a runnable `msb` binary in place (download, checksum, or lock failure). */
|
|
60
|
+
export class ProvisionError extends Error {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = "ProvisionError";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAGvC;IAEA;IAEA;IANX;IACE,uDAAuD;IAC9C,OAAe;IACxB,yDAAyD;IAChD,OAAe;IACxB,qFAAqF;IAC5E,MAAe;QAExB,KAAK,CAAC,YAAY,OAAO,8BAA8B,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QANjG,YAAO,GAAP,OAAO,CAAQ;QAEf,YAAO,GAAP,OAAO,CAAQ;QAEf,WAAM,GAAN,MAAM,CAAS;QAGxB,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAInC;IAHX,YACE,OAAe;IACf,4FAA4F;IACnF,KAAe;QAExB,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAFnD,UAAK,GAAL,KAAK,CAAU;QAGxB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,gFAAgF;AAChF,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,8EAA8E;AAC9E,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,kIAAkI;AAClI,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allocates a host port this process has not already handed out. Binding
|
|
3
|
+
* `127.0.0.1:0` and immediately closing the socket leaves a window where
|
|
4
|
+
* another process (or another allocate() racing on ITS OWN OS-level choice)
|
|
5
|
+
* could rebind the same port before the caller's container process starts —
|
|
6
|
+
* `GenericContainer`'s port-retry loop is the mitigation for that race, not
|
|
7
|
+
* this function. What this function guarantees is in-process uniqueness:
|
|
8
|
+
* two calls here never return the same port while either is still issued.
|
|
9
|
+
*/
|
|
10
|
+
export declare function allocate(): Promise<number>;
|
|
11
|
+
/** Releases a port back to the pool. Releasing a port never issued by this process is a harmless no-op. */
|
|
12
|
+
export declare function release(port: number): void;
|
|
13
|
+
/** Test-only observability seam: the ports currently considered issued. */
|
|
14
|
+
export declare function issuedView(): ReadonlySet<number>;
|
|
15
|
+
export declare const FreePorts: {
|
|
16
|
+
/** Allocates a host port this process has not already handed out — see `allocate` above. */
|
|
17
|
+
allocate: typeof allocate;
|
|
18
|
+
/** Releases a port back to the pool — see `release` above. */
|
|
19
|
+
release: typeof release;
|
|
20
|
+
/** Test-only observability seam — see `issuedView` above. */
|
|
21
|
+
issuedView: typeof issuedView;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=free-ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free-ports.d.ts","sourceRoot":"","sources":["../../src/core/free-ports.ts"],"names":[],"mappings":"AAgCA;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAShD;AAED,2GAA2G;AAC3G,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,IAAI,WAAW,CAAC,MAAM,CAAC,CAEhD;AAED,eAAO,MAAM,SAAS;IACpB,4FAA4F;;IAE5F,8DAA8D;;IAE9D,6DAA6D;;CAE9D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
const MAX_ALLOCATE_ATTEMPTS = 100;
|
|
3
|
+
// Binds are loopback-only (127.0.0.1), not wildcard — the same conservative
|
|
4
|
+
// choice used everywhere else a host port is touched (publishing, wait
|
|
5
|
+
// probes). A deliberate divergence from binding 0.0.0.0, not an oversight.
|
|
6
|
+
const BIND_HOST = "127.0.0.1";
|
|
7
|
+
function bindEphemeralPort() {
|
|
8
|
+
return new Promise((resolvePort, rejectPort) => {
|
|
9
|
+
const server = net.createServer();
|
|
10
|
+
server.once("error", (err) => {
|
|
11
|
+
rejectPort(err);
|
|
12
|
+
});
|
|
13
|
+
server.listen(0, BIND_HOST, () => {
|
|
14
|
+
const address = server.address();
|
|
15
|
+
if (address === null || typeof address === "string") {
|
|
16
|
+
server.close();
|
|
17
|
+
rejectPort(new Error("failed to read an ephemeral port from the bound server"));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const port = address.port;
|
|
21
|
+
server.close(() => {
|
|
22
|
+
resolvePort(port);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const issued = new Set();
|
|
28
|
+
/**
|
|
29
|
+
* Allocates a host port this process has not already handed out. Binding
|
|
30
|
+
* `127.0.0.1:0` and immediately closing the socket leaves a window where
|
|
31
|
+
* another process (or another allocate() racing on ITS OWN OS-level choice)
|
|
32
|
+
* could rebind the same port before the caller's container process starts —
|
|
33
|
+
* `GenericContainer`'s port-retry loop is the mitigation for that race, not
|
|
34
|
+
* this function. What this function guarantees is in-process uniqueness:
|
|
35
|
+
* two calls here never return the same port while either is still issued.
|
|
36
|
+
*/
|
|
37
|
+
export async function allocate() {
|
|
38
|
+
for (let attempt = 0; attempt < MAX_ALLOCATE_ATTEMPTS; attempt++) {
|
|
39
|
+
const port = await bindEphemeralPort();
|
|
40
|
+
if (!issued.has(port)) {
|
|
41
|
+
issued.add(port);
|
|
42
|
+
return port;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`could not allocate a unique free port after ${MAX_ALLOCATE_ATTEMPTS} attempts`);
|
|
46
|
+
}
|
|
47
|
+
/** Releases a port back to the pool. Releasing a port never issued by this process is a harmless no-op. */
|
|
48
|
+
export function release(port) {
|
|
49
|
+
issued.delete(port);
|
|
50
|
+
}
|
|
51
|
+
/** Test-only observability seam: the ports currently considered issued. */
|
|
52
|
+
export function issuedView() {
|
|
53
|
+
return new Set(issued);
|
|
54
|
+
}
|
|
55
|
+
export const FreePorts = {
|
|
56
|
+
/** Allocates a host port this process has not already handed out — see `allocate` above. */
|
|
57
|
+
allocate,
|
|
58
|
+
/** Releases a port back to the pool — see `release` above. */
|
|
59
|
+
release,
|
|
60
|
+
/** Test-only observability seam — see `issuedView` above. */
|
|
61
|
+
issuedView,
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=free-ports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free-ports.js","sourceRoot":"","sources":["../../src/core/free-ports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAEhC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,4EAA4E;AAC5E,uEAAuE;AACvE,2EAA2E;AAC3E,MAAM,SAAS,GAAG,WAAW,CAAC;AAE9B,SAAS,iBAAiB;IACxB,OAAO,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC3B,UAAU,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE;YAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACpD,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,UAAU,CAAC,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC,CAAC;gBAChF,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAChB,WAAW,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,qBAAqB,EAAE,OAAO,EAAE,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,+CAA+C,qBAAqB,WAAW,CAAC,CAAC;AACnG,CAAC;AAED,2GAA2G;AAC3G,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,4FAA4F;IAC5F,QAAQ;IACR,8DAA8D;IAC9D,OAAO;IACP,6DAA6D;IAC7D,UAAU;CACX,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Network } from "./network.js";
|
|
2
|
+
import type { NetworkMember } from "./network.js";
|
|
3
|
+
import type { WaitStrategy } from "./wait.js";
|
|
4
|
+
import type { SandboxBackend, FollowHandle } from "./backend.js";
|
|
5
|
+
import type { ContainerSpec, ExecResult } from "./model.js";
|
|
6
|
+
import type { MountableFile } from "./mountable-file.js";
|
|
7
|
+
/**
|
|
8
|
+
* The builder, launcher, and lifecycle guard for a single container — the
|
|
9
|
+
* one class every module (`RedisContainer`, `PostgresContainer`, …)
|
|
10
|
+
* subclasses, and the type you reach for directly for any image without a
|
|
11
|
+
* dedicated module.
|
|
12
|
+
*
|
|
13
|
+
* The instance itself is the guard: after `start()` it implements
|
|
14
|
+
* `Symbol.asyncDispose`, so `await using c = await new GenericContainer(img).start()`
|
|
15
|
+
* tears the container down at scope exit — the direct analog of the Kotlin
|
|
16
|
+
* `AutoCloseable` and Rust's RAII guard, expressed with TC39 explicit
|
|
17
|
+
* resource management. Prefer `await using` in tests; use explicit
|
|
18
|
+
* `start()`/`stop()` where a `using` scope can't span a framework hook
|
|
19
|
+
* (Jest/Vitest `beforeAll`/`afterAll`, `node:test`'s `before`/`after`).
|
|
20
|
+
*
|
|
21
|
+
* Builder methods (`withEnv`, `withExposedPorts`, …) mutate and return
|
|
22
|
+
* `this` for chaining; none of them perform I/O. All I/O happens in
|
|
23
|
+
* `start()`, `stop()`, `exec()`, `logs()`, and `followOutput()`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class GenericContainer implements AsyncDisposable, NetworkMember {
|
|
26
|
+
private readonly image;
|
|
27
|
+
private envPairs;
|
|
28
|
+
private exposedPorts;
|
|
29
|
+
private command;
|
|
30
|
+
private network;
|
|
31
|
+
private aliasNames;
|
|
32
|
+
private mounts;
|
|
33
|
+
private waitStrategy;
|
|
34
|
+
private memoryLimitMb;
|
|
35
|
+
private backendOverride;
|
|
36
|
+
private handle;
|
|
37
|
+
private backend;
|
|
38
|
+
private mappedPorts;
|
|
39
|
+
private running;
|
|
40
|
+
/** Builds against `image` (e.g. `"redis:8.6-alpine"`); no I/O happens until `start()`. */
|
|
41
|
+
constructor(image: string);
|
|
42
|
+
/** Convenience: `new GenericContainer(image).start()`. */
|
|
43
|
+
static start(image: string): Promise<GenericContainer>;
|
|
44
|
+
/** Sets an environment variable visible to the workload. Last-write-wins if called again with the same key; insertion order is otherwise preserved. */
|
|
45
|
+
withEnv(key: string, value: string): this;
|
|
46
|
+
/** Publishes these guest ports to pre-allocated host ports (see `FreePorts`); read back with `getMappedPort`. */
|
|
47
|
+
withExposedPorts(...ports: number[]): this;
|
|
48
|
+
/** Overrides the image's default ENTRYPOINT/CMD. Omit this call entirely to run the image unmodified. */
|
|
49
|
+
withCommand(...cmd: string[]): this;
|
|
50
|
+
/** Joins a `Network`, making this container reachable from (and to) its running siblings by alias. */
|
|
51
|
+
withNetwork(net: Network): this;
|
|
52
|
+
/** Names this container answers to on its network — resolved via `Network.resolve(alias, port)` by other members. */
|
|
53
|
+
withNetworkAliases(...names: string[]): this;
|
|
54
|
+
/** Copies a host file into the guest before boot, at `guestPath`. Read-only is enforced on docker; advisory only on msb (current microsandbox releases do not enforce guest-side read-only mounts). */
|
|
55
|
+
withCopyFileToContainer(file: MountableFile, guestPath: string): this;
|
|
56
|
+
/** Sets the readiness check `start()` waits on before returning. Defaults to `Wait.forListeningPort()`. */
|
|
57
|
+
waitingFor(strategy: WaitStrategy): this;
|
|
58
|
+
/** Raises the container's memory ceiling (in MB) above the backend default — required by several JVM-heavy modules; see each module's own doc for its pin. */
|
|
59
|
+
withMemoryLimit(megabytes: number): this;
|
|
60
|
+
/** Test/advanced seam: pin the backend instead of resolving `Backends.active()`. */
|
|
61
|
+
withBackend(backend: SandboxBackend): this;
|
|
62
|
+
/** Module hook: override to adjust the spec once mapped host ports are known (e.g. advertised listeners). */
|
|
63
|
+
protected customizeSpec(spec: ContainerSpec, _mapped: (guest: number) => number): ContainerSpec;
|
|
64
|
+
/** Module hook: runs after the container is ready (e.g. `rs.initiate` for a Mongo replica set). */
|
|
65
|
+
protected containerIsStarted(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Module hook: runs the instant `start()` has resolved a backend — before
|
|
68
|
+
* `ensureNetwork`, port allocation, or the container is created/booted at
|
|
69
|
+
* all. The one hook that lets a module reject a backend-incompatible
|
|
70
|
+
* request (e.g. `FlinkContainer.withTaskManager()` on msb) without paying
|
|
71
|
+
* for a boot first. `containerIsStarted()` (above) fires much later, after
|
|
72
|
+
* this container is already fully up — fine for post-readiness setup, but
|
|
73
|
+
* too late to fail fast on a capability the backend never had.
|
|
74
|
+
*/
|
|
75
|
+
protected containerIsStarting(_backend: SandboxBackend): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Module hook: the backend this container actually started against (only
|
|
78
|
+
* valid once `start()` has resolved it — i.e. from within
|
|
79
|
+
* `containerIsStarted()` or later). Lets a module's post-start hook branch
|
|
80
|
+
* on backend capability, e.g. `FlinkContainer.withTaskManager()` throwing a
|
|
81
|
+
* typed `UnsupportedByBackendError` on msb instead of attempting a
|
|
82
|
+
* network-link topology the exec-tunnel emulation can't carry.
|
|
83
|
+
*/
|
|
84
|
+
protected currentBackend(): SandboxBackend;
|
|
85
|
+
private resolveBackend;
|
|
86
|
+
private buildSpec;
|
|
87
|
+
private allocatePorts;
|
|
88
|
+
private releasePorts;
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a backend, allocates host ports, boots the container, installs
|
|
91
|
+
* any network links, and waits for readiness — in that order. On ANY
|
|
92
|
+
* failure partway through (a bad wait strategy, a network-link rejection,
|
|
93
|
+
* a port conflict exhausting its retries), everything already allocated
|
|
94
|
+
* or started is torn down to completion BEFORE this rejects: a
|
|
95
|
+
* half-started container never leaks, and there is no detached
|
|
96
|
+
* fire-and-forget cleanup running after `start()` has already returned
|
|
97
|
+
* control to the caller.
|
|
98
|
+
*
|
|
99
|
+
* A host-port-bind conflict specifically retries (up to 5 attempts) with
|
|
100
|
+
* a fresh port before giving up — pre-allocated ports can still lose an
|
|
101
|
+
* allocate-then-bind race to an unrelated process.
|
|
102
|
+
*/
|
|
103
|
+
start(): Promise<this>;
|
|
104
|
+
/**
|
|
105
|
+
* Stops and removes the backend-native resource, releases its host ports,
|
|
106
|
+
* and clears the port map. Idempotent — stopping a container that never
|
|
107
|
+
* started, or stopping twice, is a harmless no-op the second time. Errors
|
|
108
|
+
* from the backend's own stop/remove calls are swallowed (best-effort
|
|
109
|
+
* teardown); this method itself never throws.
|
|
110
|
+
*/
|
|
111
|
+
stop(): Promise<void>;
|
|
112
|
+
/** `= stop()`. What `await using c = await new GenericContainer(img).start()` calls at scope exit; never throws. */
|
|
113
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
114
|
+
/** Whether `start()` has completed and `stop()` has not yet run. */
|
|
115
|
+
get isRunning(): boolean;
|
|
116
|
+
/** Always `"127.0.0.1"` — every published port is bound loopback-only, on both backends. */
|
|
117
|
+
get host(): string;
|
|
118
|
+
/** Names this container answers to on its network (set via `withNetworkAliases`). Part of the `NetworkMember` contract `Network` uses to compute links. */
|
|
119
|
+
get aliases(): ReadonlyArray<string>;
|
|
120
|
+
/** Every guest port this container published (set via `withExposedPorts`). Part of the `NetworkMember` contract `Network` uses to compute links. */
|
|
121
|
+
get exposedGuestPorts(): ReadonlyArray<number>;
|
|
122
|
+
/**
|
|
123
|
+
* The host port bound to `guestPort`. Throws distinct messages for the
|
|
124
|
+
* two ways this can be wrong: not running at all ("call start() first"),
|
|
125
|
+
* versus running but never exposed via `withExposedPorts` ("call
|
|
126
|
+
* withExposedPorts(N)").
|
|
127
|
+
*/
|
|
128
|
+
getMappedPort(guestPort: number): number;
|
|
129
|
+
/** Alias for `getMappedPort`, satisfying the `NetworkMember`/`WaitTarget` shape (which name this method `mappedPort`). */
|
|
130
|
+
mappedPort(guestPort: number): number;
|
|
131
|
+
private requireHandle;
|
|
132
|
+
/** Runs a one-shot command inside the running container and waits for it to exit. Throws if the container is not running. */
|
|
133
|
+
exec(...cmd: string[]): Promise<ExecResult>;
|
|
134
|
+
/** Fetches the workload's logs so far (a bounded tail), as a single string. Throws if the container is not running. */
|
|
135
|
+
logs(): Promise<string>;
|
|
136
|
+
/**
|
|
137
|
+
* Streams log lines to `consumer` as they're produced, in order, with no
|
|
138
|
+
* duplicates — including the final unterminated fragment after the
|
|
139
|
+
* workload exits, delivered exactly once. Call `close()` on the returned
|
|
140
|
+
* handle to stop delivery without flushing anything further.
|
|
141
|
+
*/
|
|
142
|
+
followOutput(consumer: (line: string) => void): Promise<FollowHandle>;
|
|
143
|
+
private asWaitTarget;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=generic-container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic-container.d.ts","sourceRoot":"","sources":["../../src/core/generic-container.ts"],"names":[],"mappings":"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"}
|