signalk-container-helper 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 +201 -0
- package/README.md +288 -0
- package/SPEC.md +309 -0
- package/dist/adopted-container.d.ts +55 -0
- package/dist/adopted-container.d.ts.map +1 -0
- package/dist/adopted-container.js +76 -0
- package/dist/adopted-container.js.map +1 -0
- package/dist/http.d.ts +66 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +76 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/managed-container.d.ts +203 -0
- package/dist/managed-container.d.ts.map +1 -0
- package/dist/managed-container.js +397 -0
- package/dist/managed-container.js.map +1 -0
- package/dist/manager.d.ts +44 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +50 -0
- package/dist/manager.js.map +1 -0
- package/dist/types.d.ts +493 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +36 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +56 -0
- package/dist/util.js.map +1 -0
- package/dist/version-source.d.ts +23 -0
- package/dist/version-source.d.ts.map +1 -0
- package/dist/version-source.js +12 -0
- package/dist/version-source.js.map +1 -0
- package/package.json +57 -0
package/SPEC.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# signalk-container-helper — Specification
|
|
2
|
+
|
|
3
|
+
An abstraction library (npm module) for Signal K plugin developers whose plugins run
|
|
4
|
+
containers through the [signalk-container](https://github.com/dirkwa/signalk-container)
|
|
5
|
+
plugin. It packages the container-integration patterns that every consumer plugin
|
|
6
|
+
currently hand-copies — discovery, startup, readiness, updates, teardown — into a small,
|
|
7
|
+
typed, zero-dependency API.
|
|
8
|
+
|
|
9
|
+
## 1. Background: what the reference plugins actually do
|
|
10
|
+
|
|
11
|
+
Four plugins were analyzed: `mayara-server-signalk-plugin`, `signalk-backup`,
|
|
12
|
+
`signalk-doctor`, `signalk-updater`, plus the signalk-container source itself (v1.22.0).
|
|
13
|
+
|
|
14
|
+
They fall into **two archetypes**:
|
|
15
|
+
|
|
16
|
+
| | Managed (backup, mayara) | Adopt-only (doctor, updater) |
|
|
17
|
+
| ------------------- | --------------------------------------------------------- | -------------------------------------------------------- |
|
|
18
|
+
| Container lifecycle | Owned by the plugin via `ensureRunning`/`recreate`/`stop` | Owned externally (systemd Quadlet); plugin never mutates |
|
|
19
|
+
| Update service | `register` + `checkOne` + apply route | `register`/`unregister` only |
|
|
20
|
+
| Health | HTTP-poll the containerized app | HTTP-probe with retry + slow detection |
|
|
21
|
+
| Manager wait budget | 30–120 s | 30 s |
|
|
22
|
+
|
|
23
|
+
Every plugin independently re-implements the same building blocks (all four have
|
|
24
|
+
near-identical copies):
|
|
25
|
+
|
|
26
|
+
1. **Manager discovery** — poll `globalThis.__signalk_containerManager` every 500 ms
|
|
27
|
+
until it exists AND `getRuntime()` is non-null (plugins load alphabetically, and
|
|
28
|
+
runtime probing is async). mayara adds a second phase using `whenReady()` (1.6.0+).
|
|
29
|
+
2. **Hand-rolled type mirror** — each plugin maintains its own `src/types.ts` copy of
|
|
30
|
+
signalk-container's API (synced against different versions: 1.6.0, 1.10.2), because
|
|
31
|
+
importing signalk-container at compile time is forbidden (runtime-only coupling) and
|
|
32
|
+
npm `peerDependencies` break against its prereleases.
|
|
33
|
+
3. **`buildContainerConfig(tag)`** — a function producing a declarative `ContainerConfig`
|
|
34
|
+
(split `image` + `tag`, resources defaults, restart `unless-stopped`), passed to
|
|
35
|
+
`ensureRunning` on every start; drift detection recreates as needed.
|
|
36
|
+
4. **Tag validation** — `SAFE_TAG = /^[a-zA-Z0-9._-]+$/` before anything reaches the
|
|
37
|
+
runtime, plus optional `'auto'` → pinned-version resolution (backup).
|
|
38
|
+
5. **Self-heal on startup** — compare the live container's image against the desired
|
|
39
|
+
`image:tag` via `listContainers()` (matching `sk-<name>`), and `recreate` when they
|
|
40
|
+
differ. Feature-detected (`recreate` is 1.12.0+).
|
|
41
|
+
6. **Update apply** — `recreate(name, config)` with a legacy fallback of
|
|
42
|
+
`pullImage` → `remove` → `ensureRunning`, including the "removed but recreation
|
|
43
|
+
failed" limbo error message. Requested tag persisted via `savePluginOptions`
|
|
44
|
+
(persisting `'auto'`, not the resolved version).
|
|
45
|
+
7. **Update registration** — `updates.register({pluginId, containerName, image,
|
|
46
|
+
currentTag: () => …, versionSource: sources.githubReleases(repo), currentVersion?,
|
|
47
|
+
checkInterval?})` at start; `unregister` at stop; both wrapped non-fatally.
|
|
48
|
+
8. **Address resolution** — `resolveContainerAddress(name, port)` with a documented
|
|
49
|
+
production bug workaround: fall back to parsing `listContainers()[].ports` for a
|
|
50
|
+
`"host:port->port/tcp"` entry, because the resolver can return a stale port.
|
|
51
|
+
9. **HTTP readiness** — poll the containerized app's own `/api/health` (deadline +
|
|
52
|
+
interval + per-request `AbortController` timeout). "Container running" ≠ "app ready".
|
|
53
|
+
10. **HTTP health probe** — N attempts, per-attempt timeout, inter-attempt delay, and a
|
|
54
|
+
"reachable but slow" threshold (no SignalK warning tier, so slow → green status text).
|
|
55
|
+
11. **Teardown** — `updates.unregister` then `containers.stop` (stop, never remove — the
|
|
56
|
+
container restarts instantly on re-enable), everything swallowed.
|
|
57
|
+
12. **Never throw out of `start()`** — sync `start()` + `asyncStart().catch(err =>
|
|
58
|
+
setPluginError(...))`, because the server does not await plugin `start()`.
|
|
59
|
+
13. **Schema defaults merge** — `{...SCHEMA_DEFAULTS, ...rawConfig}` because Signal K
|
|
60
|
+
does not seed schema defaults at runtime.
|
|
61
|
+
|
|
62
|
+
## 2. Goals
|
|
63
|
+
|
|
64
|
+
- One import that provides the **managed-container lifecycle** end-to-end:
|
|
65
|
+
wait-for-manager → validate tag → self-heal → `ensureRunning` → register updates →
|
|
66
|
+
resolve address → wait-for-ready.
|
|
67
|
+
- First-class support for the **adopt-only** archetype (update registration + HTTP
|
|
68
|
+
health probing without lifecycle mutation).
|
|
69
|
+
- Ship the **canonical TypeScript mirror** of signalk-container's public API so plugins
|
|
70
|
+
stop hand-maintaining divergent copies. Runtime coupling stays via `globalThis` —
|
|
71
|
+
the library never imports signalk-container.
|
|
72
|
+
- **Feature-detect** optional/newer manager methods (`recreate`, `resolveHostPath`,
|
|
73
|
+
`getLogs`, …) and provide legacy fallbacks, with version floors documented.
|
|
74
|
+
- **Marine-grade error handling**: helpers never throw for expected conditions without
|
|
75
|
+
an actionable message; non-fatal operations are swallowed with `app.debug`; offline
|
|
76
|
+
is a normal state.
|
|
77
|
+
- Zero runtime dependencies. ESM output + `.d.ts`. Node ≥ 24.
|
|
78
|
+
|
|
79
|
+
## 3. Non-goals
|
|
80
|
+
|
|
81
|
+
- Talking to Docker/Podman directly — everything goes through signalk-container.
|
|
82
|
+
- Owning the plugin's config schema, webapp, or final status message.
|
|
83
|
+
- Applying updates automatically (detection-only service stays user-confirmed;
|
|
84
|
+
`autoUpdateOnFloatingTag` remains a per-config opt-in the caller sets).
|
|
85
|
+
- Wrapping `runJob`/one-shot jobs beyond re-exporting the types (no consumer among the
|
|
86
|
+
four uses them; can be added later).
|
|
87
|
+
|
|
88
|
+
## 4. Public API
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import {
|
|
92
|
+
// discovery
|
|
93
|
+
getContainerManager,
|
|
94
|
+
waitForContainerManager,
|
|
95
|
+
// archetypes
|
|
96
|
+
ManagedContainer,
|
|
97
|
+
AdoptedContainer,
|
|
98
|
+
// http utilities
|
|
99
|
+
fetchWithTimeout,
|
|
100
|
+
waitForHttpReady,
|
|
101
|
+
probeHttpHealth,
|
|
102
|
+
// misc utilities
|
|
103
|
+
startSafely,
|
|
104
|
+
errMsg,
|
|
105
|
+
isValidImageTag,
|
|
106
|
+
IMAGE_TAG_PATTERN,
|
|
107
|
+
ContainerHelperError,
|
|
108
|
+
// full type mirror
|
|
109
|
+
type ContainerManagerApi,
|
|
110
|
+
type ContainerConfig /* … */,
|
|
111
|
+
} from "signalk-container-helper";
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4.1 Discovery
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
getContainerManager(): ContainerManagerApi | undefined
|
|
118
|
+
|
|
119
|
+
waitForContainerManager(options?: {
|
|
120
|
+
timeoutMs?: number // default 60_000
|
|
121
|
+
intervalMs?: number // default 500
|
|
122
|
+
onWaiting?: (phase: 'manager' | 'runtime') => void
|
|
123
|
+
}): Promise<{ manager: ContainerManagerApi | undefined; runtime: ContainerRuntimeInfo | null }>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Two-phase wait: poll the global until present, then `whenReady()` (feature-detected,
|
|
127
|
+
falling back to polling `getRuntime()`), then re-check `getRuntime()`. The result
|
|
128
|
+
distinguishes the two failure modes every plugin reports differently:
|
|
129
|
+
`manager === undefined` → "signalk-container is not installed/enabled";
|
|
130
|
+
`manager && !runtime` → "no container runtime detected (podman/docker missing)".
|
|
131
|
+
|
|
132
|
+
### 4.2 `ManagedContainer` (backup/mayara archetype)
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const container = new ManagedContainer({
|
|
136
|
+
app, // setPluginStatus/setPluginError/debug
|
|
137
|
+
pluginId: 'signalk-questdb',
|
|
138
|
+
name: 'questdb', // unprefixed container name
|
|
139
|
+
image: 'ghcr.io/questdb/questdb', // registry path, no tag
|
|
140
|
+
buildConfig: (tag) => ({ image: 'ghcr.io/questdb/questdb', tag, /* … */ }),
|
|
141
|
+
defaultTag: 'latest',
|
|
142
|
+
resolveTag: (t) => t === 'auto' ? PINNED_VERSION : t, // optional
|
|
143
|
+
managerTimeoutMs: 120_000,
|
|
144
|
+
readiness: { port: 9000, path: '/ping', maxMs: 60_000 }, // optional
|
|
145
|
+
updates: { // optional
|
|
146
|
+
versionSource: { githubReleases: 'questdb/questdb' },
|
|
147
|
+
currentTag: () => currentSettings?.version ?? 'auto',
|
|
148
|
+
},
|
|
149
|
+
ensureOptions: { onVolumeIssue, onContainerLog }, // passed through
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
await container.start(tag?) // full bring-up; returns { manager, tag, address }
|
|
153
|
+
await container.stop() // unregister + stop (not remove); never throws
|
|
154
|
+
await container.applyUpdate(tag) // recreate, or legacy pull+remove+ensureRunning
|
|
155
|
+
await container.checkForUpdate() // updates.checkOne → UpdateCheckResult | null
|
|
156
|
+
await container.getState() // ContainerState ('unknown-manager' safe default)
|
|
157
|
+
await container.getInfo() // { state, image } for /status routes
|
|
158
|
+
await container.resolveAddress(p) // resolveContainerAddress + stale-port fallback
|
|
159
|
+
await container.getLogs(opts?) // feature-detected passthrough
|
|
160
|
+
container.registerUpdateRoutes(router, { onApplied }) // GET check / POST apply
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`start()` sequencing (each step from the reference plugins):
|
|
164
|
+
|
|
165
|
+
1. `waitForContainerManager` — on failure sets an actionable `setPluginError` and throws
|
|
166
|
+
`ContainerHelperError` with `code: 'manager-unavailable' | 'no-runtime'` and
|
|
167
|
+
`reported: true` (so `startSafely` doesn't double-report).
|
|
168
|
+
2. Validate requested tag (`IMAGE_TAG_PATTERN`), apply `resolveTag`.
|
|
169
|
+
3. Self-heal: when `manager.recreate` exists, compare the live container's image
|
|
170
|
+
(matched by `unprefixedName`, falling back to `sk-<name>`) to `image:tag`; `recreate`
|
|
171
|
+
on mismatch. Probe failures are non-fatal (debug + fall through).
|
|
172
|
+
4. Otherwise `ensureRunning(name, buildConfig(tag), ensureOptions)`.
|
|
173
|
+
5. Register with the update service (non-fatal; `currentTag` defaults to a live getter
|
|
174
|
+
of the last started tag).
|
|
175
|
+
6. If `readiness` is configured: resolve the address (throws `'address-unresolved'`
|
|
176
|
+
if impossible) and poll `http://<addr><path>` until ready (throws `'not-ready'` on
|
|
177
|
+
deadline).
|
|
178
|
+
|
|
179
|
+
The helper emits progress via `setPluginStatus` ("Waiting for…", "Starting…",
|
|
180
|
+
"Recreating…") but **never sets the final "Running" status** — the plugin owns that.
|
|
181
|
+
|
|
182
|
+
### 4.3 `AdoptedContainer` (doctor/updater archetype)
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const adopted = new AdoptedContainer({
|
|
186
|
+
app,
|
|
187
|
+
pluginId: "signalk-doctor",
|
|
188
|
+
containerName: "signalk-doctor-server",
|
|
189
|
+
image: "ghcr.io/dirkwa/signalk-doctor-server",
|
|
190
|
+
currentTag: "latest", // string or () => string
|
|
191
|
+
currentVersion: () => fetchEngineVersion(), // optional, preferred by comparator
|
|
192
|
+
versionSource: { githubReleases: "dirkwa/signalk-doctor-server" },
|
|
193
|
+
checkInterval: "24h",
|
|
194
|
+
managerTimeoutMs: 30_000,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await adopted.register(); // waits for manager; false + setPluginError if unavailable
|
|
198
|
+
adopted.unregister(); // best-effort, never throws
|
|
199
|
+
await adopted.checkForUpdate();
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
No lifecycle mutation, matching the "adopt, don't manage" rule.
|
|
203
|
+
|
|
204
|
+
### 4.4 HTTP utilities
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
fetchWithTimeout(url, { timeoutMs = 10_000, fetchImpl = fetch, ...init });
|
|
208
|
+
waitForHttpReady(url, {
|
|
209
|
+
maxMs = 60_000,
|
|
210
|
+
intervalMs = 1_000,
|
|
211
|
+
requestTimeoutMs = 2_000,
|
|
212
|
+
});
|
|
213
|
+
probeHttpHealth(url, {
|
|
214
|
+
attempts = 3,
|
|
215
|
+
attemptTimeoutMs = 5_000,
|
|
216
|
+
retryDelayMs = 2_000,
|
|
217
|
+
slowMs = 1_500,
|
|
218
|
+
});
|
|
219
|
+
// → { reachable: boolean, slowMs?: number } — never throws
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Defaults are the constants shared across the four plugins. `fetchImpl` is injectable
|
|
223
|
+
for tests.
|
|
224
|
+
|
|
225
|
+
### 4.5 Misc
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
startSafely(app, () => asyncStart(config));
|
|
229
|
+
// sync wrapper for plugin.start(): catches, reports via setPluginError unless the
|
|
230
|
+
// error is a ContainerHelperError already reported by the helper
|
|
231
|
+
|
|
232
|
+
errMsg(err); // unknown → string
|
|
233
|
+
isValidImageTag(tag); // SAFE_TAG check
|
|
234
|
+
class ContainerHelperError extends Error {
|
|
235
|
+
code:
|
|
236
|
+
| "manager-unavailable"
|
|
237
|
+
| "no-runtime"
|
|
238
|
+
| "invalid-tag"
|
|
239
|
+
| "address-unresolved"
|
|
240
|
+
| "not-ready"
|
|
241
|
+
| "recreate-limbo";
|
|
242
|
+
reported: boolean; // true when the helper already called setPluginError
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 4.6 Types module
|
|
247
|
+
|
|
248
|
+
A documented mirror of signalk-container's public surface, synced against **v1.22.0**:
|
|
249
|
+
`ContainerManagerApi`, `ContainerConfig` (incl. `signalkDataMount`,
|
|
250
|
+
`signalkConfigRootMount`, `signalkAccessiblePorts`, `healthcheck`, `ulimits`, `labels`,
|
|
251
|
+
`digest`, `updateChannel`, `autoUpdateOnFloatingTag`, `user`, `extraHosts`),
|
|
252
|
+
`EnsureRunningOptions`, `VolumeSpec`/`VolumeIssue`, `ContainerResourceLimits`,
|
|
253
|
+
`ContainerInfo`, `ContainerJobConfig`/`Result`, `UpdateServiceApi` and friends, and the
|
|
254
|
+
`globalThis` declaration. Methods newer than the **1.6.0 baseline** are optional in the
|
|
255
|
+
type, forcing feature-detection at call sites:
|
|
256
|
+
|
|
257
|
+
| Optional member | Version floor |
|
|
258
|
+
| --------------------------------------- | ------------------------------------------- |
|
|
259
|
+
| `getLogs` | 1.7.0 |
|
|
260
|
+
| `resolveHostPath` | 1.7.0 (partial earlier) |
|
|
261
|
+
| `doctor.*` | 1.8.0–1.10.0 |
|
|
262
|
+
| `recreate` | 1.12.0 |
|
|
263
|
+
| `execInContainer`, network methods | varies |
|
|
264
|
+
| `removeManagedData` | 1.18.0 |
|
|
265
|
+
| `manifest.*` | 1.13.0+ |
|
|
266
|
+
| Config fields `healthcheck` / `ulimits` | 1.14.0 / 1.17.0 (ignored by older versions) |
|
|
267
|
+
|
|
268
|
+
## 5. Error-handling philosophy
|
|
269
|
+
|
|
270
|
+
- `stop()`, `unregister()`, `getInfo()`, `checkForUpdate()` never throw — degraded
|
|
271
|
+
results (`null`, `'unknown'`) plus `app.debug`.
|
|
272
|
+
- `start()` / `applyUpdate()` throw typed `ContainerHelperError`s with actionable
|
|
273
|
+
messages; the helper sets `setPluginError` first and marks the error `reported`.
|
|
274
|
+
- The legacy update path preserves the "Container removed but recreation failed —
|
|
275
|
+
retry" limbo message (`code: 'recreate-limbo'`).
|
|
276
|
+
- Offline is normal: nothing in the helper converts a network failure into a fatal.
|
|
277
|
+
|
|
278
|
+
## 6. Packaging & compatibility
|
|
279
|
+
|
|
280
|
+
- npm module `signalk-container-helper`, Apache-2.0, zero runtime dependencies.
|
|
281
|
+
- TypeScript → **ESM** (`dist/`) + declarations; `engines.node >= 24`. The package is
|
|
282
|
+
`"type": "module"`; relative imports carry `.js` extensions (NodeNext resolution).
|
|
283
|
+
- Consumers must NOT add signalk-container to `dependencies`/`peerDependencies`
|
|
284
|
+
(its prereleases break npm semver ranges); declare it via `"signalk": { "requires":
|
|
285
|
+
["signalk-container"] }` in package.json. The README documents this.
|
|
286
|
+
- Supported signalk-container **runtime** baseline: **≥ 1.6.0** (for `whenReady`);
|
|
287
|
+
newer features degrade gracefully via feature detection.
|
|
288
|
+
- The library's own type mirror is validated at build time against
|
|
289
|
+
**signalk-container ≥ 1.23.2** (the `signalk-container/types` entrypoint, whose
|
|
290
|
+
update-service types and their options 1.23.1–1.23.2 completed) via a `tsc`-checked contract test
|
|
291
|
+
(`test/types-contract.test-d.ts`), so drift from the canonical public API fails
|
|
292
|
+
CI. This is a dev-only devDependency; it imposes nothing on consumers.
|
|
293
|
+
|
|
294
|
+
## 7. Test plan
|
|
295
|
+
|
|
296
|
+
Unit tests (vitest) against a fake manager installed on `globalThis` and an injected
|
|
297
|
+
`fetchImpl` — no real containers or network:
|
|
298
|
+
|
|
299
|
+
- discovery: late-appearing manager, `whenReady` path, legacy polling path, runtime
|
|
300
|
+
failure vs manager absence, timeout.
|
|
301
|
+
- `ManagedContainer.start`: ensureRunning call shape, self-heal recreate on image
|
|
302
|
+
drift, non-fatal probe failure, update registration, address resolution incl.
|
|
303
|
+
stale-port fallback parse, readiness polling, tag validation, error codes.
|
|
304
|
+
- `applyUpdate`: recreate path, legacy triplet fallback, limbo error.
|
|
305
|
+
- `stop`: unregister + stop ordering, error swallowing.
|
|
306
|
+
- update routes: 503 without manager, check passthrough, apply happy path/400/500.
|
|
307
|
+
- `AdoptedContainer`: register/unregister, manager-missing reporting.
|
|
308
|
+
- http: `probeHttpHealth` retry/slow/exhaustion; `waitForHttpReady` deadline.
|
|
309
|
+
- utils: tag pattern, `errMsg`, `startSafely` double-report suppression.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AppLike, ContainerManagerApi, UpdateCheckResult } from "./types.js";
|
|
2
|
+
import { type VersionSourceSpec } from "./version-source.js";
|
|
3
|
+
export interface AdoptedContainerOptions {
|
|
4
|
+
app: AppLike;
|
|
5
|
+
/** Your plugin id — update-service registration key. */
|
|
6
|
+
pluginId: string;
|
|
7
|
+
/**
|
|
8
|
+
* The container's name for display/registration. Adopted containers are
|
|
9
|
+
* usually NOT namespace-prefixed (systemd/Quadlet-managed peers), so
|
|
10
|
+
* manager.getState() cannot see them — probe their HTTP health directly
|
|
11
|
+
* (see probeHttpHealth) instead.
|
|
12
|
+
*/
|
|
13
|
+
containerName: string;
|
|
14
|
+
/** Image repo without tag. */
|
|
15
|
+
image: string;
|
|
16
|
+
/** The tag the deployment pins (e.g. "latest"); string or live getter. */
|
|
17
|
+
currentTag: string | (() => string);
|
|
18
|
+
/**
|
|
19
|
+
* Ask the running app for its honest version (e.g. GET /api/health
|
|
20
|
+
* .version). Preferred over currentTag by the update comparator —
|
|
21
|
+
* essential when currentTag is floating.
|
|
22
|
+
*/
|
|
23
|
+
currentVersion?: () => Promise<string | null>;
|
|
24
|
+
versionSource: VersionSourceSpec;
|
|
25
|
+
/** e.g. "24h" (default), "1h" minimum. */
|
|
26
|
+
checkInterval?: string;
|
|
27
|
+
/** Budget for waiting on signalk-container + runtime. Default 30_000. */
|
|
28
|
+
managerTimeoutMs?: number;
|
|
29
|
+
/** Poll interval while waiting for the manager global. Default 500. */
|
|
30
|
+
managerPollIntervalMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* "Adopt, don't manage" integration — the signalk-doctor / signalk-updater
|
|
34
|
+
* archetype. The container's lifecycle is owned elsewhere (systemd Quadlet,
|
|
35
|
+
* external host); the plugin only enrolls it with signalk-container's
|
|
36
|
+
* update-detection service and probes its health over HTTP.
|
|
37
|
+
*/
|
|
38
|
+
export declare class AdoptedContainer {
|
|
39
|
+
readonly options: AdoptedContainerOptions;
|
|
40
|
+
manager: ContainerManagerApi | undefined;
|
|
41
|
+
private registered;
|
|
42
|
+
constructor(options: AdoptedContainerOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Wait for the manager, then register for update detection. Returns false
|
|
45
|
+
* (after setPluginError with an actionable message) when signalk-container
|
|
46
|
+
* or a runtime never becomes available — the plugin can continue degraded.
|
|
47
|
+
* Never throws.
|
|
48
|
+
*/
|
|
49
|
+
register(): Promise<boolean>;
|
|
50
|
+
/** Best-effort deregistration for plugin.stop(). Never throws. */
|
|
51
|
+
unregister(): void;
|
|
52
|
+
/** updates.checkOne — null when the manager is unavailable. */
|
|
53
|
+
checkForUpdate(): Promise<UpdateCheckResult | null>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=adopted-container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopted-container.d.ts","sourceRoot":"","sources":["../src/adopted-container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,OAAO,CAAC;IACb,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;IACpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,aAAa,EAAE,iBAAiB,CAAC;IACjC,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,OAAO,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAEzC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,uBAAuB;IAI5C;;;;;OAKG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAmDlC,kEAAkE;IAClE,UAAU,IAAI,IAAI;IAYlB,+DAA+D;IACzD,cAAc,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAK1D"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getContainerManager, waitForContainerManager } from "./manager.js";
|
|
2
|
+
import { buildVersionSource, } from "./version-source.js";
|
|
3
|
+
import { errMsg } from "./util.js";
|
|
4
|
+
/**
|
|
5
|
+
* "Adopt, don't manage" integration — the signalk-doctor / signalk-updater
|
|
6
|
+
* archetype. The container's lifecycle is owned elsewhere (systemd Quadlet,
|
|
7
|
+
* external host); the plugin only enrolls it with signalk-container's
|
|
8
|
+
* update-detection service and probes its health over HTTP.
|
|
9
|
+
*/
|
|
10
|
+
export class AdoptedContainer {
|
|
11
|
+
options;
|
|
12
|
+
manager;
|
|
13
|
+
registered = false;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wait for the manager, then register for update detection. Returns false
|
|
19
|
+
* (after setPluginError with an actionable message) when signalk-container
|
|
20
|
+
* or a runtime never becomes available — the plugin can continue degraded.
|
|
21
|
+
* Never throws.
|
|
22
|
+
*/
|
|
23
|
+
async register() {
|
|
24
|
+
const { app, pluginId, containerName, image, currentTag, currentVersion, versionSource, checkInterval, managerTimeoutMs = 30_000, managerPollIntervalMs = 500, } = this.options;
|
|
25
|
+
const { manager, runtime } = await waitForContainerManager({
|
|
26
|
+
timeoutMs: managerTimeoutMs,
|
|
27
|
+
intervalMs: managerPollIntervalMs,
|
|
28
|
+
});
|
|
29
|
+
if (!manager) {
|
|
30
|
+
app.setPluginError("signalk-container is not loaded — install it and restart the server. Update detection is disabled without it.");
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (!runtime) {
|
|
34
|
+
app.setPluginError("No container runtime detected (Podman or Docker) — update detection is disabled.");
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
this.manager = manager;
|
|
38
|
+
try {
|
|
39
|
+
manager.updates.register({
|
|
40
|
+
pluginId,
|
|
41
|
+
containerName,
|
|
42
|
+
image,
|
|
43
|
+
currentTag: typeof currentTag === "function" ? currentTag : () => currentTag,
|
|
44
|
+
currentVersion,
|
|
45
|
+
checkInterval,
|
|
46
|
+
versionSource: buildVersionSource(manager.updates, versionSource),
|
|
47
|
+
});
|
|
48
|
+
this.registered = true;
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
app.setPluginError(`update registration failed: ${errMsg(err)}`);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Best-effort deregistration for plugin.stop(). Never throws. */
|
|
57
|
+
unregister() {
|
|
58
|
+
if (!this.registered)
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
(this.manager ?? getContainerManager())?.updates.unregister(this.options.pluginId);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
this.options.app.debug(`updates.unregister failed: ${errMsg(err)}`);
|
|
65
|
+
}
|
|
66
|
+
this.registered = false;
|
|
67
|
+
}
|
|
68
|
+
/** updates.checkOne — null when the manager is unavailable. */
|
|
69
|
+
async checkForUpdate() {
|
|
70
|
+
const manager = this.manager ?? getContainerManager();
|
|
71
|
+
if (!manager)
|
|
72
|
+
return null;
|
|
73
|
+
return manager.updates.checkOne(this.options.pluginId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=adopted-container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopted-container.js","sourceRoot":"","sources":["../src/adopted-container.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,kBAAkB,GAEnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAgCnC;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAClB,OAAO,CAA0B;IAC1C,OAAO,CAAkC;IAEjC,UAAU,GAAG,KAAK,CAAC;IAE3B,YAAY,OAAgC;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,EACJ,GAAG,EACH,QAAQ,EACR,aAAa,EACb,KAAK,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,gBAAgB,GAAG,MAAM,EACzB,qBAAqB,GAAG,GAAG,GAC5B,GAAG,IAAI,CAAC,OAAO,CAAC;QAEjB,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,uBAAuB,CAAC;YACzD,SAAS,EAAE,gBAAgB;YAC3B,UAAU,EAAE,qBAAqB;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,cAAc,CAChB,+GAA+G,CAChH,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,cAAc,CAChB,kFAAkF,CACnF,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,IAAI,CAAC;YACH,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;gBACvB,QAAQ;gBACR,aAAa;gBACb,KAAK;gBACL,UAAU,EACR,OAAO,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU;gBAClE,cAAc;gBACd,aAAa;gBACb,aAAa,EAAE,kBAAkB,CAAC,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC;aAClE,CAAC,CAAC;YACH,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,cAAc,CAAC,+BAA+B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjE,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAC7B,IAAI,CAAC;YACH,CAAC,IAAI,CAAC,OAAO,IAAI,mBAAmB,EAAE,CAAC,EAAE,OAAO,CAAC,UAAU,CACzD,IAAI,CAAC,OAAO,CAAC,QAAQ,CACtB,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,8BAA8B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,cAAc;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,mBAAmB,EAAE,CAAC;QACtD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC;CACF"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Injectable fetch for tests; matches the global fetch signature loosely. */
|
|
2
|
+
export type FetchLike = (url: string, init?: {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}) => Promise<{
|
|
6
|
+
ok: boolean;
|
|
7
|
+
status: number;
|
|
8
|
+
json(): Promise<unknown>;
|
|
9
|
+
}>;
|
|
10
|
+
export interface FetchWithTimeoutOptions {
|
|
11
|
+
/** Per-request timeout via AbortController. Default 10_000. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
fetchImpl?: FetchLike;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `fetch` with an AbortController timeout — the bare pattern every reference
|
|
18
|
+
* plugin re-implements for talking to the app inside its container.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchWithTimeout(url: string, options?: FetchWithTimeoutOptions): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
status: number;
|
|
23
|
+
json(): Promise<unknown>;
|
|
24
|
+
}>;
|
|
25
|
+
export interface WaitForHttpReadyOptions {
|
|
26
|
+
/** Overall deadline. Default 60_000. */
|
|
27
|
+
maxMs?: number;
|
|
28
|
+
/** Delay between attempts. Default 1_000. */
|
|
29
|
+
intervalMs?: number;
|
|
30
|
+
/** Per-request timeout. Default 2_000. */
|
|
31
|
+
requestTimeoutMs?: number;
|
|
32
|
+
fetchImpl?: FetchLike;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Poll a URL until it answers 2xx, or throw when the deadline passes.
|
|
36
|
+
* "Container running" is not "app ready" — every managed plugin polls the
|
|
37
|
+
* containerized app's own health endpoint after ensureRunning.
|
|
38
|
+
*/
|
|
39
|
+
export declare function waitForHttpReady(url: string, options?: WaitForHttpReadyOptions): Promise<void>;
|
|
40
|
+
export interface ProbeHttpHealthOptions {
|
|
41
|
+
/** Total attempts. Default 3. */
|
|
42
|
+
attempts?: number;
|
|
43
|
+
/** Per-attempt timeout. Default 5_000. */
|
|
44
|
+
attemptTimeoutMs?: number;
|
|
45
|
+
/** Delay between attempts. Default 2_000. */
|
|
46
|
+
retryDelayMs?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Threshold above which a successful probe is flagged slow (likely disk
|
|
49
|
+
* I/O contention on SD-card hosts). Default 1_500.
|
|
50
|
+
*/
|
|
51
|
+
slowMs?: number;
|
|
52
|
+
fetchImpl?: FetchLike;
|
|
53
|
+
}
|
|
54
|
+
export interface HealthProbeResult {
|
|
55
|
+
reachable: boolean;
|
|
56
|
+
/** Set when reachable but the successful attempt exceeded `slowMs`. */
|
|
57
|
+
slowMs?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Retrying liveness probe with slow-response detection. Signal K's plugin
|
|
61
|
+
* API has no warning tier, so callers typically report a slow-but-healthy
|
|
62
|
+
* result through `setPluginStatus` rather than `setPluginError`.
|
|
63
|
+
* Never throws.
|
|
64
|
+
*/
|
|
65
|
+
export declare function probeHttpHealth(url: string, options?: ProbeHttpHealthOptions): Promise<HealthProbeResult>;
|
|
66
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,KACpD,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAUpE;AAED,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED,MAAM,WAAW,sBAAsB;IACrC,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,iBAAiB,CAAC,CA0B5B"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { errMsg, sleep } from "./util.js";
|
|
2
|
+
/**
|
|
3
|
+
* `fetch` with an AbortController timeout — the bare pattern every reference
|
|
4
|
+
* plugin re-implements for talking to the app inside its container.
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchWithTimeout(url, options = {}) {
|
|
7
|
+
const { timeoutMs = 10_000, fetchImpl, ...init } = options;
|
|
8
|
+
const impl = fetchImpl ?? fetch;
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
try {
|
|
12
|
+
return await impl(url, { ...init, signal: controller.signal });
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Poll a URL until it answers 2xx, or throw when the deadline passes.
|
|
20
|
+
* "Container running" is not "app ready" — every managed plugin polls the
|
|
21
|
+
* containerized app's own health endpoint after ensureRunning.
|
|
22
|
+
*/
|
|
23
|
+
export async function waitForHttpReady(url, options = {}) {
|
|
24
|
+
const { maxMs = 60_000, intervalMs = 1_000, requestTimeoutMs = 2_000, fetchImpl, } = options;
|
|
25
|
+
const deadline = Date.now() + maxMs;
|
|
26
|
+
let lastErr;
|
|
27
|
+
for (;;) {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetchWithTimeout(url, {
|
|
30
|
+
timeoutMs: requestTimeoutMs,
|
|
31
|
+
fetchImpl,
|
|
32
|
+
});
|
|
33
|
+
if (res.ok)
|
|
34
|
+
return;
|
|
35
|
+
lastErr = new Error(`HTTP ${res.status}`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
lastErr = err;
|
|
39
|
+
}
|
|
40
|
+
if (Date.now() >= deadline)
|
|
41
|
+
break;
|
|
42
|
+
await sleep(intervalMs);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`${url} did not become ready within ${maxMs}ms: ${errMsg(lastErr)}`);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Retrying liveness probe with slow-response detection. Signal K's plugin
|
|
48
|
+
* API has no warning tier, so callers typically report a slow-but-healthy
|
|
49
|
+
* result through `setPluginStatus` rather than `setPluginError`.
|
|
50
|
+
* Never throws.
|
|
51
|
+
*/
|
|
52
|
+
export async function probeHttpHealth(url, options = {}) {
|
|
53
|
+
const { attempts = 3, attemptTimeoutMs = 5_000, retryDelayMs = 2_000, slowMs = 1_500, fetchImpl, } = options;
|
|
54
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
55
|
+
if (attempt > 0)
|
|
56
|
+
await sleep(retryDelayMs);
|
|
57
|
+
const started = Date.now();
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetchWithTimeout(url, {
|
|
60
|
+
timeoutMs: attemptTimeoutMs,
|
|
61
|
+
fetchImpl,
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
continue;
|
|
65
|
+
const elapsed = Date.now() - started;
|
|
66
|
+
return elapsed > slowMs
|
|
67
|
+
? { reachable: true, slowMs: elapsed }
|
|
68
|
+
: { reachable: true };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// try again
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { reachable: false };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export { getContainerManager, waitForContainerManager } from "./manager.js";
|
|
3
|
+
export type { WaitForManagerOptions, ManagerWaitResult } from "./manager.js";
|
|
4
|
+
export { ManagedContainer } from "./managed-container.js";
|
|
5
|
+
export type { ManagedContainerOptions, ManagedUpdateOptions, ReadinessOptions, StartResult, RouterLike, ResponseLike, UpdateRoutesOptions, } from "./managed-container.js";
|
|
6
|
+
export { AdoptedContainer } from "./adopted-container.js";
|
|
7
|
+
export type { AdoptedContainerOptions } from "./adopted-container.js";
|
|
8
|
+
export { buildVersionSource } from "./version-source.js";
|
|
9
|
+
export type { VersionSourceSpec } from "./version-source.js";
|
|
10
|
+
export { fetchWithTimeout, waitForHttpReady, probeHttpHealth } from "./http.js";
|
|
11
|
+
export type { FetchLike, FetchWithTimeoutOptions, WaitForHttpReadyOptions, ProbeHttpHealthOptions, HealthProbeResult, } from "./http.js";
|
|
12
|
+
export { startSafely, errMsg, isValidImageTag, IMAGE_TAG_PATTERN, ContainerHelperError, } from "./util.js";
|
|
13
|
+
export type { ContainerHelperErrorCode } from "./util.js";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChF,YAAY,EACV,SAAS,EACT,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export { getContainerManager, waitForContainerManager } from "./manager.js";
|
|
3
|
+
export { ManagedContainer } from "./managed-container.js";
|
|
4
|
+
export { AdoptedContainer } from "./adopted-container.js";
|
|
5
|
+
export { buildVersionSource } from "./version-source.js";
|
|
6
|
+
export { fetchWithTimeout, waitForHttpReady, probeHttpHealth } from "./http.js";
|
|
7
|
+
export { startSafely, errMsg, isValidImageTag, IMAGE_TAG_PATTERN, ContainerHelperError, } from "./util.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAU1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAQhF,OAAO,EACL,WAAW,EACX,MAAM,EACN,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
|