@crvouga/mockingbird-service-daily 3.11.0 → 3.12.1
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/CHANGELOG.md +14 -1
- package/DISCOVERY.md +29 -0
- package/dist/{chunk-55N23QY4.js → chunk-EJHZ3NYW.js} +87 -21
- package/dist/chunk-EJHZ3NYW.js.map +7 -0
- package/dist/chunk-LXWV2LEP.js +6680 -0
- package/dist/chunk-LXWV2LEP.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.d.ts +68 -1329
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-55N23QY4.js.map +0 -7
- package/dist/chunk-YWPYCLMK.js +0 -6674
- package/dist/chunk-YWPYCLMK.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-daily
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.12.1 (2026-11-12)
|
|
4
|
+
|
|
5
|
+
### Fixes and improvements
|
|
6
|
+
|
|
7
|
+
- serve vendor brand data from each emulator instead of the docs site ([0c72a69](https://github.com/crvouga/mockingbird/commit/0c72a693fc76e612936150598faef9787ad1c824))
|
|
8
|
+
- take SIGINT/SIGTERM before readiness is published ([d5a98a0](https://github.com/crvouga/mockingbird/commit/d5a98a09a344e12818c5c94cbea03621b528f841))
|
|
9
|
+
|
|
10
|
+
## 3.12.0 (2026-11-11)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- one dashboard for every admin UI and notification view ([d9fdf4d](https://github.com/crvouga/mockingbird/commit/d9fdf4dc1faeaa5c385f34bd5516323e49284b4d))
|
|
15
|
+
|
|
16
|
+
## 3.11.0 (2026-11-10)
|
|
4
17
|
|
|
5
18
|
### Features
|
|
6
19
|
|
package/DISCOVERY.md
CHANGED
|
@@ -4,6 +4,35 @@ This is the installed-package index for coding agents and tooling. All relative
|
|
|
4
4
|
inside `node_modules/@crvouga/mockingbird-service-daily/`; no repository checkout is needed to discover the emulator's
|
|
5
5
|
supported surface or documented behavior.
|
|
6
6
|
|
|
7
|
+
## Run it in a fleet
|
|
8
|
+
|
|
9
|
+
The recommended way to run this emulator is in a fleet, next to the other emulators the
|
|
10
|
+
application calls: one `mockingbird.json`, one process, each emulator on its own port.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install -D @crvouga/mockingbird-fleet @crvouga/mockingbird-service-daily
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`mockingbird.json` (add one entry per installed emulator; `"port": 0` picks a free port):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"services": {
|
|
21
|
+
"daily": { "port": 0 }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx mockingbird-fleet serve --config mockingbird.json --dashboard --ready-file ready.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Read this emulator's URL from `services.<name>.url` in `ready.json` and point the client at it.
|
|
31
|
+
`adminBase` in the same file is the fleet controller, which resets, snapshots and restores every
|
|
32
|
+
emulator in one call, and `dashboard` is one page with every HTTP emulator's admin UI and the
|
|
33
|
+
notifications the application sent. The guide: <https://mockingbird.chrisvouga.dev/docs/fleets>. The README covers running this
|
|
34
|
+
emulator alone or in-process.
|
|
35
|
+
|
|
7
36
|
## Capability and behavior sources
|
|
8
37
|
|
|
9
38
|
| Question | Authoritative file | What it contains |
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseFaultHeader,
|
|
10
10
|
resolveAdminPrefix,
|
|
11
11
|
restoreClock
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LXWV2LEP.js";
|
|
13
13
|
|
|
14
14
|
// ../../adapters/node/dist/cli.js
|
|
15
15
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
@@ -1662,9 +1662,7 @@ data: ${JSON.stringify(data)}
|
|
|
1662
1662
|
async handle(request, path, namespace, body, admin) {
|
|
1663
1663
|
const route = path.slice("/fleet".length), method = request.method;
|
|
1664
1664
|
if (path === "/ui")
|
|
1665
|
-
return new Response(fleetConsole(this.ctx.config.adminPrefix ?? "/__admin"), {
|
|
1666
|
-
headers: { "content-type": "text/html; charset=utf-8" }
|
|
1667
|
-
});
|
|
1665
|
+
return new Response(fleetConsole(this.ctx.config.adminPrefix ?? "/__admin", this.ctx.dashboard?.()), { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
1668
1666
|
if (route === "" && method === "GET")
|
|
1669
1667
|
return Response.json({
|
|
1670
1668
|
...this.capability(),
|
|
@@ -2634,7 +2632,8 @@ data: ${JSON.stringify(data)}
|
|
|
2634
2632
|
clearInterval(this.expiryTimer);
|
|
2635
2633
|
}
|
|
2636
2634
|
};
|
|
2637
|
-
var
|
|
2635
|
+
var escapeAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
2636
|
+
var fleetConsole = (prefix, dashboard) => `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Mockingbird Fleet</title><style>body{font:16px system-ui;max-width:1000px;margin:3rem auto;padding:1rem;background:#10151b;color:#e0e9f2}input,select,button{padding:.7rem;margin:.3rem;background:#233142;color:inherit;border:1px solid #60768b;border-radius:5px}pre{white-space:pre-wrap;overflow-wrap:anywhere}a{color:#9fdbff}</style><h1>Mockingbird Fleet</h1>${dashboard ? `<p id="dashboard">Every emulator's admin UI and the notifications UI are on the <a href="${escapeAttribute(dashboard)}">dashboard</a>.</p>` : ""}<label>Admin key <input id="key" type="password" autocomplete="off"></label><label>Namespace <input id="ns" value="default"></label><button id="refresh">Refresh</button><section id="children"></section><label>Action <select id="action"><option value="snapshots">Capture checkpoint</option><option value="reset">Reset namespace</option></select></label><button id="run">Run</button><h2>Lifecycle and operations</h2><pre id="result"></pre><script type="module">const base=${JSON.stringify(`${prefix}/fleet`)};const key=document.querySelector('#key'),ns=document.querySelector('#ns'),result=document.querySelector('#result');async function api(path,method='GET',body){const r=await fetch(base+path,{method,headers:{'content-type':'application/json','x-mockingbird-admin-key':key.value},...(body?{body:JSON.stringify(body)}:{})});const data=await r.json();result.textContent=JSON.stringify(data,null,2);return data}document.querySelector('#refresh').onclick=async()=>{const data=await api('');const host=document.querySelector('#children');host.replaceChildren();for(const [name,child]of Object.entries(data.manifest?.services??{})){const link=document.createElement('a');link.href=child.adminUrl;link.textContent=name+' ('+child.protocol+') ';host.append(link)}await api('/operations?namespace='+encodeURIComponent(ns.value))};document.querySelector('#run').onclick=()=>api('/namespaces/'+encodeURIComponent(ns.value)+'/'+document.querySelector('#action').value,'POST',{});</script></html>`;
|
|
2638
2637
|
|
|
2639
2638
|
// ../../adapters/node/dist/fleet-preflight.js
|
|
2640
2639
|
import { readFile, realpath } from "node:fs/promises";
|
|
@@ -2698,6 +2697,28 @@ var fleet_config_schema_default = {
|
|
|
2698
2697
|
}
|
|
2699
2698
|
}
|
|
2700
2699
|
},
|
|
2700
|
+
dashboard: {
|
|
2701
|
+
description: "Serve the dashboard (every emulator's admin UI and the notifications UI) next to the fleet. Honored by mockingbird-fleet serve.",
|
|
2702
|
+
anyOf: [
|
|
2703
|
+
{
|
|
2704
|
+
type: "boolean"
|
|
2705
|
+
},
|
|
2706
|
+
{
|
|
2707
|
+
type: "object",
|
|
2708
|
+
additionalProperties: false,
|
|
2709
|
+
properties: {
|
|
2710
|
+
port: {
|
|
2711
|
+
type: "integer",
|
|
2712
|
+
minimum: 0,
|
|
2713
|
+
maximum: 65535
|
|
2714
|
+
},
|
|
2715
|
+
host: {
|
|
2716
|
+
type: "string"
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
]
|
|
2721
|
+
},
|
|
2701
2722
|
cors: {
|
|
2702
2723
|
type: "object",
|
|
2703
2724
|
required: ["origins"],
|
|
@@ -2982,6 +3003,7 @@ async function preflightFleet(config, options) {
|
|
|
2982
3003
|
throw new Error("protected archive policy requires a protected repository; configure its protector before startup");
|
|
2983
3004
|
resolveAdminPrefix(config.adminPrefix);
|
|
2984
3005
|
normalizeBootControls(config.adminControls);
|
|
3006
|
+
validateDashboard(config.dashboard);
|
|
2985
3007
|
if (config.idBase !== void 0)
|
|
2986
3008
|
normalizeIdBase(config.idBase, "idBase");
|
|
2987
3009
|
const targets = /* @__PURE__ */ new Map();
|
|
@@ -3057,7 +3079,7 @@ async function preflightFleet(config, options) {
|
|
|
3057
3079
|
break;
|
|
3058
3080
|
}
|
|
3059
3081
|
}
|
|
3060
|
-
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.
|
|
3082
|
+
targets.set(name, { ...target, packageVersion: packageVersion ?? "3.12.1" });
|
|
3061
3083
|
} catch (error) {
|
|
3062
3084
|
throw new Error(`service ${name}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
3063
3085
|
}
|
|
@@ -3142,6 +3164,22 @@ function validateFleetPolicies(options) {
|
|
|
3142
3164
|
if (duration !== void 0 && (!Number.isSafeInteger(duration) || duration < 1))
|
|
3143
3165
|
throw new Error("hook deadlines must be positive integer milliseconds");
|
|
3144
3166
|
}
|
|
3167
|
+
function validateDashboard(value) {
|
|
3168
|
+
if (value === void 0 || typeof value === "boolean")
|
|
3169
|
+
return;
|
|
3170
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3171
|
+
throw new Error("dashboard: expected true, false or { port, host }");
|
|
3172
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
3173
|
+
if (key === "port") {
|
|
3174
|
+
if (!Number.isInteger(entry) || entry < 0 || entry > 65535)
|
|
3175
|
+
throw new Error("dashboard.port: expected an integer from 0 to 65535");
|
|
3176
|
+
} else if (key === "host") {
|
|
3177
|
+
if (typeof entry !== "string" || !entry)
|
|
3178
|
+
throw new Error("dashboard.host: expected a host");
|
|
3179
|
+
} else
|
|
3180
|
+
throw new Error(`dashboard.${key}: unknown key; expected port or host`);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3145
3183
|
|
|
3146
3184
|
// ../../adapters/node/dist/serve.js
|
|
3147
3185
|
import { createServer } from "node:http";
|
|
@@ -3597,6 +3635,7 @@ async function startFleet(config, options) {
|
|
|
3597
3635
|
const checkpoints = /* @__PURE__ */ new Map();
|
|
3598
3636
|
let supervisor;
|
|
3599
3637
|
let management;
|
|
3638
|
+
let dashboard;
|
|
3600
3639
|
let manifest;
|
|
3601
3640
|
const privateConnections = () => Object.fromEntries([...children].map(([name, child]) => [
|
|
3602
3641
|
name,
|
|
@@ -3664,6 +3703,7 @@ async function startFleet(config, options) {
|
|
|
3664
3703
|
}
|
|
3665
3704
|
}
|
|
3666
3705
|
const cleanup = await Promise.allSettled([
|
|
3706
|
+
dashboard?.close(),
|
|
3667
3707
|
supervisor?.close(),
|
|
3668
3708
|
removeOwned(options.readyFile, id),
|
|
3669
3709
|
removeOwned(options.connectionsFile, id)
|
|
@@ -4030,6 +4070,7 @@ async function startFleet(config, options) {
|
|
|
4030
4070
|
clock,
|
|
4031
4071
|
transaction,
|
|
4032
4072
|
manifest: () => manifest,
|
|
4073
|
+
dashboard: () => dashboard?.url,
|
|
4033
4074
|
publish,
|
|
4034
4075
|
recovery,
|
|
4035
4076
|
controlling
|
|
@@ -4093,6 +4134,10 @@ async function startFleet(config, options) {
|
|
|
4093
4134
|
}
|
|
4094
4135
|
}
|
|
4095
4136
|
await management.startPolicies();
|
|
4137
|
+
if (options.dashboard) {
|
|
4138
|
+
dashboard = await options.dashboard({ manifest, config });
|
|
4139
|
+
manifest.dashboard = dashboard.url;
|
|
4140
|
+
}
|
|
4096
4141
|
await publish();
|
|
4097
4142
|
if (manifest.state === "ready")
|
|
4098
4143
|
options.onReady?.(manifest);
|
|
@@ -4892,20 +4937,35 @@ var start = async (target, values, config) => {
|
|
|
4892
4937
|
console.log(`${target.name} ${line.replaceAll("/__admin", adminPrefix)}`);
|
|
4893
4938
|
return listening;
|
|
4894
4939
|
};
|
|
4895
|
-
var
|
|
4896
|
-
|
|
4940
|
+
var watchSignals = () => {
|
|
4941
|
+
let received = false;
|
|
4942
|
+
let wake = () => {
|
|
4943
|
+
};
|
|
4944
|
+
const on = () => {
|
|
4945
|
+
received = true;
|
|
4946
|
+
wake();
|
|
4947
|
+
};
|
|
4948
|
+
const dispose = () => {
|
|
4897
4949
|
;
|
|
4898
|
-
process.removeListener("SIGINT",
|
|
4899
|
-
process.removeListener("SIGTERM",
|
|
4900
|
-
const results = await Promise.allSettled(servers.map((s) => s.close()));
|
|
4901
|
-
for (const result of results)
|
|
4902
|
-
if (result.status === "rejected")
|
|
4903
|
-
console.error(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
4904
|
-
resolve4(results.some((r) => r.status === "rejected") ? 1 : 0);
|
|
4950
|
+
process.removeListener("SIGINT", on);
|
|
4951
|
+
process.removeListener("SIGTERM", on);
|
|
4905
4952
|
};
|
|
4906
|
-
process.once("SIGINT",
|
|
4907
|
-
process.once("SIGTERM",
|
|
4908
|
-
|
|
4953
|
+
process.once("SIGINT", on);
|
|
4954
|
+
process.once("SIGTERM", on);
|
|
4955
|
+
return {
|
|
4956
|
+
dispose,
|
|
4957
|
+
async untilSignal(servers) {
|
|
4958
|
+
if (!received)
|
|
4959
|
+
await new Promise((resolve4) => wake = resolve4);
|
|
4960
|
+
dispose();
|
|
4961
|
+
const results = await Promise.allSettled(servers.map((s) => s.close()));
|
|
4962
|
+
for (const result of results)
|
|
4963
|
+
if (result.status === "rejected")
|
|
4964
|
+
console.error(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
4965
|
+
return results.some((r) => r.status === "rejected") ? 1 : 0;
|
|
4966
|
+
}
|
|
4967
|
+
};
|
|
4968
|
+
};
|
|
4909
4969
|
var serveCommand = (target) => ({
|
|
4910
4970
|
fleetTarget: target,
|
|
4911
4971
|
summary: `Serve the ${target.name} emulator or a configured fleet`,
|
|
@@ -4918,12 +4978,15 @@ var serveCommand = (target) => ({
|
|
|
4918
4978
|
}
|
|
4919
4979
|
const configPath = asString(values.config);
|
|
4920
4980
|
if (configPath !== void 0) {
|
|
4981
|
+
const signals2 = watchSignals();
|
|
4921
4982
|
try {
|
|
4922
4983
|
const config = JSON.parse(await readFile5(configPath, "utf8"));
|
|
4923
4984
|
config.adminPrefix = resolveAdminPrefix(config.adminPrefix ?? asString(values["admin-prefix"]) ?? process.env.MOCKINGBIRD_ADMIN_PREFIX);
|
|
4924
4985
|
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
4925
4986
|
if (adminKey2 !== void 0)
|
|
4926
4987
|
config.adminKey = adminKey2;
|
|
4988
|
+
if (config.dashboard)
|
|
4989
|
+
console.error(`${configPath}: "dashboard" is served by mockingbird-fleet (npx @crvouga/mockingbird-fleet serve --config ${configPath}); a service's own serve command starts the fleet without it`);
|
|
4927
4990
|
const adminControls = await resolveServeControls(values, process.env, config.adminControls);
|
|
4928
4991
|
if (adminControls !== void 0)
|
|
4929
4992
|
config.adminControls = adminControls;
|
|
@@ -4945,8 +5008,9 @@ var serveCommand = (target) => ({
|
|
|
4945
5008
|
console.log(`${name} emulator listening on ${endpoint.url}`);
|
|
4946
5009
|
}
|
|
4947
5010
|
});
|
|
4948
|
-
return untilSignal([fleet]);
|
|
5011
|
+
return signals2.untilSignal([fleet]);
|
|
4949
5012
|
} catch (error) {
|
|
5013
|
+
signals2.dispose();
|
|
4950
5014
|
console.error(error instanceof Error ? error.message : String(error));
|
|
4951
5015
|
return 1;
|
|
4952
5016
|
}
|
|
@@ -4959,6 +5023,7 @@ var serveCommand = (target) => ({
|
|
|
4959
5023
|
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
4960
5024
|
const seed = asString(values.seed) ?? process.env.MOCKINGBIRD_SEED;
|
|
4961
5025
|
let listening;
|
|
5026
|
+
const signals = watchSignals();
|
|
4962
5027
|
try {
|
|
4963
5028
|
const adminControls = await resolveServeControls(values);
|
|
4964
5029
|
const idBase = resolveServeIdBase(values);
|
|
@@ -4975,10 +5040,11 @@ var serveCommand = (target) => ({
|
|
|
4975
5040
|
log
|
|
4976
5041
|
});
|
|
4977
5042
|
} catch (error) {
|
|
5043
|
+
signals.dispose();
|
|
4978
5044
|
console.error(error instanceof Error ? error.message : String(error));
|
|
4979
5045
|
return 1;
|
|
4980
5046
|
}
|
|
4981
|
-
return untilSignal([listening]);
|
|
5047
|
+
return signals.untilSignal([listening]);
|
|
4982
5048
|
}
|
|
4983
5049
|
});
|
|
4984
5050
|
|
|
@@ -5103,4 +5169,4 @@ export {
|
|
|
5103
5169
|
createServer2 as createServer,
|
|
5104
5170
|
serveTarget
|
|
5105
5171
|
};
|
|
5106
|
-
//# sourceMappingURL=chunk-
|
|
5172
|
+
//# sourceMappingURL=chunk-EJHZ3NYW.js.map
|