manifest 7.0.0 → 7.2.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/CONTRACT.md +25 -1
- package/LICENSE +21 -0
- package/README.md +64 -19
- package/dist/bin.cjs +410 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +383 -0
- package/dist/chunk-GC5H22R2.js +495 -0
- package/dist/{chunk-DP2C4E42.js → chunk-NHEF7GUP.js} +126 -348
- package/dist/index.cjs +264 -24
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/register.cjs +264 -24
- package/dist/register.js +2 -1
- package/docs/guide.md +70 -10
- package/docs/sdk-flow-diagram.png +0 -0
- package/package.json +8 -3
package/dist/index.cjs
CHANGED
|
@@ -81,6 +81,19 @@ function safeUrl(raw) {
|
|
|
81
81
|
url.search = new URLSearchParams(pairs).toString();
|
|
82
82
|
return url.toString();
|
|
83
83
|
}
|
|
84
|
+
function trackedUrl(raw) {
|
|
85
|
+
try {
|
|
86
|
+
const url = new URL(raw);
|
|
87
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
88
|
+
url.username = "";
|
|
89
|
+
url.password = "";
|
|
90
|
+
url.search = "";
|
|
91
|
+
url.hash = "";
|
|
92
|
+
return url.toString();
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
84
97
|
function safeHeaders(headers) {
|
|
85
98
|
return Object.fromEntries([...headers].map(([key, value]) => [
|
|
86
99
|
key,
|
|
@@ -328,7 +341,8 @@ async function captureResponse(original, limit = RESPONSE_LIMIT, timeoutMs = CAP
|
|
|
328
341
|
var import_node_crypto = require("crypto");
|
|
329
342
|
|
|
330
343
|
// src/api.ts
|
|
331
|
-
var VERSION = "7.
|
|
344
|
+
var VERSION = "7.2.0";
|
|
345
|
+
var MAX_HEALS_IN_FLIGHT = 8;
|
|
332
346
|
var warn = (message) => process.emitWarning(message, { code: "MNFST" });
|
|
333
347
|
var HealApi = class {
|
|
334
348
|
constructor(rawFetch, key, url, timeoutMs = 6e4, reportTimeoutMs = 5e3) {
|
|
@@ -350,16 +364,23 @@ var HealApi = class {
|
|
|
350
364
|
enabled() {
|
|
351
365
|
return performance.now() >= this.disabledUntil;
|
|
352
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Whether a heal call would be sent right now: the project is not disabled
|
|
369
|
+
* and a heal slot is free. A healable failure that cannot be sent is tracked
|
|
370
|
+
* instead, so it is never lost from both ledgers.
|
|
371
|
+
*/
|
|
372
|
+
canHeal() {
|
|
373
|
+
return this.enabled() && this.inFlight < MAX_HEALS_IN_FLIGHT;
|
|
374
|
+
}
|
|
353
375
|
headers() {
|
|
354
376
|
return {
|
|
355
377
|
authorization: `Bearer ${this.key}`,
|
|
356
378
|
"content-type": "application/json",
|
|
357
|
-
"user-agent": `mnfst-node/${VERSION}
|
|
358
|
-
"x-mnfst-source": "node-sdk"
|
|
379
|
+
"user-agent": `mnfst-node/${VERSION}`
|
|
359
380
|
};
|
|
360
381
|
}
|
|
361
382
|
async heal(capture, signal) {
|
|
362
|
-
if (!this.
|
|
383
|
+
if (!this.canHeal()) return null;
|
|
363
384
|
this.inFlight++;
|
|
364
385
|
const controller = new AbortController();
|
|
365
386
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -376,14 +397,20 @@ var HealApi = class {
|
|
|
376
397
|
});
|
|
377
398
|
return null;
|
|
378
399
|
}
|
|
379
|
-
const captured = await captureResponse(
|
|
400
|
+
const captured = await captureResponse(
|
|
401
|
+
response,
|
|
402
|
+
1048576,
|
|
403
|
+
this.timeoutMs
|
|
404
|
+
);
|
|
380
405
|
void captured.response.body?.cancel().catch(() => {
|
|
381
406
|
});
|
|
382
|
-
if (!captured.complete || !isObject(captured.body) || !boundedJson(captured.body))
|
|
407
|
+
if (!captured.complete || !isObject(captured.body) || !boundedJson(captured.body))
|
|
408
|
+
return null;
|
|
383
409
|
if (response.status === 403 && captured.body.error === "project_disabled") {
|
|
384
410
|
this.disabledUntil = performance.now() + 3e5;
|
|
385
411
|
}
|
|
386
|
-
if (response.status !== 200 || typeof captured.body.status !== "string")
|
|
412
|
+
if (response.status !== 200 || typeof captured.body.status !== "string")
|
|
413
|
+
return null;
|
|
387
414
|
return {
|
|
388
415
|
status: captured.body.status,
|
|
389
416
|
...typeof captured.body.healAttemptId === "string" ? { healAttemptId: captured.body.healAttemptId } : {},
|
|
@@ -397,6 +424,72 @@ var HealApi = class {
|
|
|
397
424
|
this.inFlight--;
|
|
398
425
|
}
|
|
399
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Announce this install: "I am installed."
|
|
429
|
+
*
|
|
430
|
+
* Without it, a broken install and a healthy app look identical from the
|
|
431
|
+
* dashboard — both are silence. Four causes hide behind that silence (key
|
|
432
|
+
* unset, key invalid, app not restarted, SDK never loaded), and an app with
|
|
433
|
+
* no failing calls produces exactly the same nothing.
|
|
434
|
+
*
|
|
435
|
+
* Deliberately fire-and-forget and deliberately quiet. It carries no
|
|
436
|
+
* application data: the name and version ride in the user-agent this class
|
|
437
|
+
* already sends, so the body is the runtime and nothing else. A failure is
|
|
438
|
+
* not warned about — the dashboard showing "not connected" IS the signal,
|
|
439
|
+
* and an app that cannot reach Manifest must not print on every boot.
|
|
440
|
+
*
|
|
441
|
+
* Uses `rawFetch`, so the handshake cannot be intercepted by our own patch.
|
|
442
|
+
*/
|
|
443
|
+
hello(runtime) {
|
|
444
|
+
const controller = new AbortController();
|
|
445
|
+
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
446
|
+
void this.rawFetch(new URL("v1/hello", this.url), {
|
|
447
|
+
method: "POST",
|
|
448
|
+
headers: this.headers(),
|
|
449
|
+
body: JSON.stringify({ runtime }),
|
|
450
|
+
signal: controller.signal,
|
|
451
|
+
redirect: "error"
|
|
452
|
+
}).then((response) => {
|
|
453
|
+
void response.body?.cancel().catch(() => {
|
|
454
|
+
});
|
|
455
|
+
}).catch(() => {
|
|
456
|
+
}).finally(() => clearTimeout(timer));
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Ship one batch of tracked calls to `POST /v1/requests`. Throws only when a
|
|
460
|
+
* retry could help (network error, timeout, 429, 5xx), so the buffer retries
|
|
461
|
+
* once; any other answer, including 404 from a backend that predates the
|
|
462
|
+
* route, drops the batch quietly. Uses `rawFetch`, so the send is never
|
|
463
|
+
* tracked or healed by our own patch.
|
|
464
|
+
*/
|
|
465
|
+
async sendRequests(calls, signal) {
|
|
466
|
+
if (!this.enabled() || calls.length === 0) return;
|
|
467
|
+
const controller = new AbortController();
|
|
468
|
+
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
469
|
+
try {
|
|
470
|
+
const response = await this.rawFetch(new URL("v1/requests", this.url), {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: this.headers(),
|
|
473
|
+
body: JSON.stringify({ requests: calls }),
|
|
474
|
+
signal: signal ? AbortSignal.any([signal, controller.signal]) : controller.signal,
|
|
475
|
+
redirect: "error"
|
|
476
|
+
});
|
|
477
|
+
if (response.status === 403) {
|
|
478
|
+
const body = await response.json().catch(() => null);
|
|
479
|
+
if (isObject(body) && body.error === "project_disabled") {
|
|
480
|
+
this.disabledUntil = performance.now() + 3e5;
|
|
481
|
+
}
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
void response.body?.cancel().catch(() => {
|
|
485
|
+
});
|
|
486
|
+
if (response.status === 429 || response.status >= 500) {
|
|
487
|
+
throw new Error(`tracked calls refused (${response.status})`);
|
|
488
|
+
}
|
|
489
|
+
} finally {
|
|
490
|
+
clearTimeout(timer);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
400
493
|
report(id, outcome) {
|
|
401
494
|
if (!id) return;
|
|
402
495
|
if (this.pending.size >= 64) {
|
|
@@ -411,16 +504,20 @@ var HealApi = class {
|
|
|
411
504
|
const controller = new AbortController();
|
|
412
505
|
const timer = setTimeout(() => controller.abort(), this.reportTimeoutMs);
|
|
413
506
|
try {
|
|
414
|
-
const response = await this.rawFetch(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
507
|
+
const response = await this.rawFetch(
|
|
508
|
+
new URL(`v1/heal-attempts/${encodeURIComponent(id)}`, this.url),
|
|
509
|
+
{
|
|
510
|
+
method: "PATCH",
|
|
511
|
+
headers: this.headers(),
|
|
512
|
+
body: JSON.stringify(outcome),
|
|
513
|
+
signal: controller.signal,
|
|
514
|
+
redirect: "error"
|
|
515
|
+
}
|
|
516
|
+
);
|
|
421
517
|
void response.body?.cancel().catch(() => {
|
|
422
518
|
});
|
|
423
|
-
if (!response.ok)
|
|
519
|
+
if (!response.ok)
|
|
520
|
+
warn("Outcome report rejected; attempt remains unconfirmed");
|
|
424
521
|
} catch {
|
|
425
522
|
warn("Outcome report failed; attempt remains unconfirmed");
|
|
426
523
|
} finally {
|
|
@@ -429,28 +526,149 @@ var HealApi = class {
|
|
|
429
526
|
}
|
|
430
527
|
};
|
|
431
528
|
|
|
529
|
+
// src/tracking.ts
|
|
530
|
+
var MAX_BUFFER = 5e3;
|
|
531
|
+
var MAX_BATCH = 500;
|
|
532
|
+
var CallBuffer = class {
|
|
533
|
+
constructor(send, options = {}) {
|
|
534
|
+
this.send = send;
|
|
535
|
+
this.flushAt = options.flushAt ?? 500;
|
|
536
|
+
this.minGapMs = options.minGapMs ?? 1e3;
|
|
537
|
+
this.timer = setInterval(() => void this.kick(), options.intervalMs ?? 5e3);
|
|
538
|
+
this.timer.unref();
|
|
539
|
+
}
|
|
540
|
+
send;
|
|
541
|
+
queue = [];
|
|
542
|
+
inFlight = null;
|
|
543
|
+
lastSentAt = -Infinity;
|
|
544
|
+
/** Past this instant, nothing is retried or waited for, and a send in flight is aborted. */
|
|
545
|
+
deadline = Infinity;
|
|
546
|
+
controller = new AbortController();
|
|
547
|
+
timer;
|
|
548
|
+
flushAt;
|
|
549
|
+
minGapMs;
|
|
550
|
+
record(call) {
|
|
551
|
+
if (this.queue.length >= MAX_BUFFER) return;
|
|
552
|
+
this.queue.push(call);
|
|
553
|
+
if (this.queue.length >= this.flushAt) void this.kick();
|
|
554
|
+
}
|
|
555
|
+
size() {
|
|
556
|
+
return this.queue.length;
|
|
557
|
+
}
|
|
558
|
+
/** Resolves when the send in flight, if any, has settled. */
|
|
559
|
+
async idle() {
|
|
560
|
+
await this.inFlight;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Send everything buffered now, one batch at a time through the same
|
|
564
|
+
* in-flight guard as the timer. With `deadlineMs` (the exit path), it gives
|
|
565
|
+
* up at the deadline: the send in flight is aborted, nothing is retried, and
|
|
566
|
+
* what is left is dropped, so a script never hangs on an unreachable server.
|
|
567
|
+
*/
|
|
568
|
+
async flush(deadlineMs = Infinity) {
|
|
569
|
+
this.deadline = performance.now() + deadlineMs;
|
|
570
|
+
const abortAt = Number.isFinite(deadlineMs) ? setTimeout(() => this.controller.abort(), deadlineMs) : null;
|
|
571
|
+
try {
|
|
572
|
+
while ((this.queue.length > 0 || this.inFlight) && performance.now() < this.deadline) {
|
|
573
|
+
await (this.inFlight ?? this.start());
|
|
574
|
+
}
|
|
575
|
+
} finally {
|
|
576
|
+
if (abortAt) clearTimeout(abortAt);
|
|
577
|
+
if (this.controller.signal.aborted) this.controller = new AbortController();
|
|
578
|
+
this.deadline = Infinity;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
stop() {
|
|
582
|
+
clearInterval(this.timer);
|
|
583
|
+
}
|
|
584
|
+
kick() {
|
|
585
|
+
if (this.inFlight || this.queue.length === 0) return;
|
|
586
|
+
if (performance.now() - this.lastSentAt < this.minGapMs) return;
|
|
587
|
+
void this.start();
|
|
588
|
+
}
|
|
589
|
+
start() {
|
|
590
|
+
this.inFlight = this.sendOne().finally(() => {
|
|
591
|
+
this.inFlight = null;
|
|
592
|
+
});
|
|
593
|
+
return this.inFlight;
|
|
594
|
+
}
|
|
595
|
+
async sendOne() {
|
|
596
|
+
const batch = this.queue.splice(0, MAX_BATCH);
|
|
597
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
598
|
+
if (attempt > 0 && performance.now() >= this.deadline) return;
|
|
599
|
+
await this.waitForGap();
|
|
600
|
+
this.lastSentAt = performance.now();
|
|
601
|
+
try {
|
|
602
|
+
await this.send(batch, this.controller.signal);
|
|
603
|
+
return;
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
if (this.controller.signal.aborted) {
|
|
607
|
+
this.controller = new AbortController();
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// Referenced on purpose: this wait only happens while a send or an exit
|
|
613
|
+
// flush is under way, and an unref'd wait would let the process exit with
|
|
614
|
+
// the batch unsent. It never outlasts the flush deadline.
|
|
615
|
+
async waitForGap() {
|
|
616
|
+
const wait = Math.min(this.lastSentAt + this.minGapMs, this.deadline) - performance.now();
|
|
617
|
+
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
432
621
|
// src/runtime.ts
|
|
433
622
|
var forbidden = /* @__PURE__ */ new Set([401, 402, 403, 429]);
|
|
623
|
+
var bodyless = ["GET", "HEAD", "DELETE", "OPTIONS"];
|
|
624
|
+
var neverBodied = (method) => method === "GET" || method === "HEAD";
|
|
434
625
|
var eligible = (status) => status >= 400 && status < 500 && !forbidden.has(status);
|
|
435
626
|
var Runtime = class {
|
|
436
627
|
constructor(options, original, api) {
|
|
437
628
|
this.options = options;
|
|
438
629
|
this.original = original;
|
|
439
630
|
this.api = api ?? new HealApi(original, options.key, options.url);
|
|
631
|
+
this.tracker = new CallBuffer((batch, signal) => this.api.sendRequests(batch, signal));
|
|
440
632
|
}
|
|
441
633
|
options;
|
|
442
634
|
original;
|
|
443
635
|
api;
|
|
636
|
+
/** Calls that did not go to `/v1/heal`, any status, on their way to `/v1/requests`. */
|
|
637
|
+
tracker;
|
|
638
|
+
/**
|
|
639
|
+
* Record a call that is not being healed. An in-memory append: never awaited,
|
|
640
|
+
* never throws, reads nothing of the response.
|
|
641
|
+
*/
|
|
642
|
+
track(method, url, statusCode, startedAt, responseTimeMs) {
|
|
643
|
+
try {
|
|
644
|
+
const reported = trackedUrl(url());
|
|
645
|
+
const verb = method.toUpperCase();
|
|
646
|
+
if (!reported || reported.length > 4096 || !verb || verb.length > 16 || statusCode < 100 || statusCode > 599) return;
|
|
647
|
+
this.tracker.record({
|
|
648
|
+
traceId: (0, import_node_crypto.randomUUID)(),
|
|
649
|
+
method: verb,
|
|
650
|
+
url: reported,
|
|
651
|
+
statusCode,
|
|
652
|
+
responseTimeMs: Math.round(responseTimeMs),
|
|
653
|
+
occurredAt: new Date(startedAt).toISOString()
|
|
654
|
+
});
|
|
655
|
+
} catch {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
444
658
|
fetch = async (input, init) => {
|
|
445
659
|
const request = new Request(input, init);
|
|
446
660
|
const extras = { ...init };
|
|
447
661
|
delete extras.body;
|
|
448
662
|
delete extras.headers;
|
|
449
663
|
const bodyPromise = captureRequest(request).catch(() => ({ body: null, complete: false }));
|
|
664
|
+
const startedAt = Date.now();
|
|
450
665
|
const started = performance.now();
|
|
451
666
|
const response = await this.original(request, extras);
|
|
452
667
|
const responseTimeMs = performance.now() - started;
|
|
453
|
-
if (!eligible(response.status) || response.redirected || !this.api.
|
|
668
|
+
if (!eligible(response.status) || response.redirected || !this.api.canHeal()) {
|
|
669
|
+
this.track(request.method, () => request.url, response.status, startedAt, responseTimeMs);
|
|
670
|
+
return response;
|
|
671
|
+
}
|
|
454
672
|
return this.handleResponse(request, response, await bodyPromise, responseTimeMs, extras);
|
|
455
673
|
};
|
|
456
674
|
async handleResponse(request, response, body, responseTimeMs, extras = {}) {
|
|
@@ -539,11 +757,11 @@ function buildRetry(request, originalBody, result) {
|
|
|
539
757
|
else return null;
|
|
540
758
|
}
|
|
541
759
|
const body = Object.hasOwn(healed, "body") ? mergeBody(originalBody, healed.body) : originalBody;
|
|
542
|
-
if (body === null && !
|
|
760
|
+
if (body === null && !bodyless.includes(request.method)) return null;
|
|
543
761
|
return new Request(url, {
|
|
544
762
|
method: request.method,
|
|
545
763
|
headers,
|
|
546
|
-
body:
|
|
764
|
+
body: body === null || neverBodied(request.method) ? void 0 : serializeRequestBody(body, contentType),
|
|
547
765
|
signal: request.signal,
|
|
548
766
|
redirect: request.redirect,
|
|
549
767
|
credentials: request.credentials,
|
|
@@ -580,6 +798,7 @@ function wrapRequest(original, protocol, runtime) {
|
|
|
580
798
|
return ((...received) => {
|
|
581
799
|
const args = [...received];
|
|
582
800
|
const callback = typeof args.at(-1) === "function" ? args.pop() : void 0;
|
|
801
|
+
const startedAt = Date.now();
|
|
583
802
|
const started = performance.now();
|
|
584
803
|
const request = original(...args);
|
|
585
804
|
const capture = captureBody(request);
|
|
@@ -588,7 +807,14 @@ function wrapRequest(original, protocol, runtime) {
|
|
|
588
807
|
request.emit = ((event, ...values) => {
|
|
589
808
|
if (event !== "response") return emit(event, ...values);
|
|
590
809
|
const response = values[0];
|
|
591
|
-
if (!eligible(response.statusCode ?? 0) || !runtime.api.
|
|
810
|
+
if (!eligible(response.statusCode ?? 0) || !runtime.api.canHeal()) {
|
|
811
|
+
runtime.track(
|
|
812
|
+
request.method,
|
|
813
|
+
() => requestUrl(request, protocol).toString(),
|
|
814
|
+
response.statusCode ?? 0,
|
|
815
|
+
startedAt,
|
|
816
|
+
performance.now() - started
|
|
817
|
+
);
|
|
592
818
|
return emit(event, ...values);
|
|
593
819
|
}
|
|
594
820
|
void handleResponse(runtime, request, response, protocol, capture.body(), signal, started).then((healed) => emit("response", healed)).catch((error) => {
|
|
@@ -607,6 +833,10 @@ async function handleResponse(runtime, clientRequest, incoming, protocol, body,
|
|
|
607
833
|
const healed = await runtime.handleResponse(request, response, body, performance.now() - started);
|
|
608
834
|
return incomingResponse(healed, clientRequest);
|
|
609
835
|
}
|
|
836
|
+
function requestUrl(request, protocol) {
|
|
837
|
+
const authority = String(request.getHeader("host") ?? request.host);
|
|
838
|
+
return new URL(request.path, `${protocol}//${authority}`);
|
|
839
|
+
}
|
|
610
840
|
function webRequest(request, protocol, captured, signal) {
|
|
611
841
|
const headers = new Headers();
|
|
612
842
|
for (const name of request.getHeaderNames()) {
|
|
@@ -615,8 +845,7 @@ function webRequest(request, protocol, captured, signal) {
|
|
|
615
845
|
if (item !== void 0) headers.append(name, String(item));
|
|
616
846
|
}
|
|
617
847
|
}
|
|
618
|
-
const
|
|
619
|
-
const url = new URL(request.path, `${protocol}//${authority}`);
|
|
848
|
+
const url = requestUrl(request, protocol);
|
|
620
849
|
const method = request.method;
|
|
621
850
|
return new Request(url, {
|
|
622
851
|
method,
|
|
@@ -725,6 +954,7 @@ function cancellation(request, external) {
|
|
|
725
954
|
|
|
726
955
|
// src/index.ts
|
|
727
956
|
var STATE = /* @__PURE__ */ Symbol.for("mnfst.node.runtime.v1");
|
|
957
|
+
var EXIT_FLUSH_MS = 2e3;
|
|
728
958
|
var globals = globalThis;
|
|
729
959
|
function manifest(options = {}) {
|
|
730
960
|
const key = options.key || process.env.MNFST_KEY;
|
|
@@ -732,16 +962,22 @@ function manifest(options = {}) {
|
|
|
732
962
|
warn("MNFST_KEY is not set; Manifest is disabled");
|
|
733
963
|
return;
|
|
734
964
|
}
|
|
735
|
-
const url = new URL(
|
|
965
|
+
const url = new URL(
|
|
966
|
+
options.url || process.env.MNFST_URL || "https://api.manifest.build"
|
|
967
|
+
);
|
|
736
968
|
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
737
|
-
throw new TypeError(
|
|
969
|
+
throw new TypeError(
|
|
970
|
+
"Manifest URL must be an HTTP(S) base URL without credentials, query or fragment"
|
|
971
|
+
);
|
|
738
972
|
}
|
|
739
973
|
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
740
974
|
const resolved = { ...options, key, url: url.toString() };
|
|
741
975
|
const existing = globals[STATE];
|
|
742
976
|
if (existing) {
|
|
743
977
|
if (existing.options.key !== key || existing.options.url !== resolved.url || existing.options.onHeal !== options.onHeal) {
|
|
744
|
-
warn(
|
|
978
|
+
warn(
|
|
979
|
+
"Manifest is already configured; changing configuration requires a process restart"
|
|
980
|
+
);
|
|
745
981
|
}
|
|
746
982
|
return;
|
|
747
983
|
}
|
|
@@ -749,6 +985,10 @@ function manifest(options = {}) {
|
|
|
749
985
|
globalThis.fetch = runtime.fetch;
|
|
750
986
|
installHttp(runtime);
|
|
751
987
|
globals[STATE] = runtime;
|
|
988
|
+
runtime.api.hello(`node-${process.versions.node}`);
|
|
989
|
+
process.once("beforeExit", () => {
|
|
990
|
+
if (runtime.tracker.size() > 0) void runtime.tracker.flush(EXIT_FLUSH_MS);
|
|
991
|
+
});
|
|
752
992
|
}
|
|
753
993
|
// Annotate the CommonJS export names for ESM import in node:
|
|
754
994
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -12,7 +12,7 @@ interface HealEvent {
|
|
|
12
12
|
operations?: unknown[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
declare const VERSION = "7.
|
|
15
|
+
declare const VERSION = "7.2.0";
|
|
16
16
|
|
|
17
17
|
/** Install once, before libraries capture their own reference to global fetch. */
|
|
18
18
|
declare function manifest(options?: ManifestOptions): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ interface HealEvent {
|
|
|
12
12
|
operations?: unknown[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
declare const VERSION = "7.
|
|
15
|
+
declare const VERSION = "7.2.0";
|
|
16
16
|
|
|
17
17
|
/** Install once, before libraries capture their own reference to global fetch. */
|
|
18
18
|
declare function manifest(options?: ManifestOptions): void;
|