@fedify/cli 2.3.0-dev.1258 → 2.3.0-dev.1273
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/dist/bench/action.js +132 -26
- package/dist/bench/command.js +1 -1
- package/dist/bench/safety/gate.js +22 -16
- package/dist/bench/safety/tiers.js +57 -1
- package/dist/bench/scenarios/inbox.js +2 -2
- package/dist/config.js +1 -1
- package/dist/deno.js +1 -1
- package/dist/inbox/view.js +1 -1
- package/dist/inbox.js +1 -1
- package/dist/lookup.js +5 -5
- package/dist/nodeinfo.js +4 -4
- package/dist/relay.js +1 -1
- package/dist/runner.js +4 -3
- package/dist/utils.js +6 -3
- package/dist/webfinger/action.js +1 -1
- package/package.json +9 -9
package/dist/bench/action.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { getContextLoader, getDocumentLoader } from "../docloader.js";
|
|
3
|
+
import { describeError } from "../utils.js";
|
|
3
4
|
import { buildFleet } from "./actor/fleet.js";
|
|
5
|
+
import { convertUrlIfHandle } from "../webfinger/lib.js";
|
|
6
|
+
import { discoverInbox, selectInbox } from "./discovery/discover.js";
|
|
4
7
|
import { validateExpectBlock } from "./result/expect/evaluate.js";
|
|
5
8
|
import { buildReport, buildScenarioResult, configHash, detectEnvironment } from "./result/build.js";
|
|
6
9
|
import { probeBenchmarkMode } from "./discovery/probe.js";
|
|
@@ -8,8 +11,8 @@ import { renderReport } from "./render/index.js";
|
|
|
8
11
|
import { loadSuiteFile, renderSuiteTemplates } from "./scenario/load.js";
|
|
9
12
|
import { normalizeSuite } from "./scenario/normalize.js";
|
|
10
13
|
import { validateSuite } from "./scenario/validate.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
14
|
+
import { UnsafeTargetError, assertInboxDestinationAllowed, assertTargetAllowed, assertUnsafeOverrideAllowed } from "./safety/gate.js";
|
|
15
|
+
import { classifyResolvedTarget } from "./safety/tiers.js";
|
|
13
16
|
import { runnerFor } from "./scenarios/registry.js";
|
|
14
17
|
import { resolveAdvertiseHost, spawnSyntheticServer } from "./server/synthetic.js";
|
|
15
18
|
import { writeFile } from "node:fs/promises";
|
|
@@ -38,7 +41,7 @@ async function runBench(command, deps = {}) {
|
|
|
38
41
|
validated = validateSuite(renderSuiteTemplates(await loadSuiteFile(command.scenario), command.target), command.scenario);
|
|
39
42
|
suite = normalizeSuite(validated, { target: command.target });
|
|
40
43
|
} catch (error) {
|
|
41
|
-
log(
|
|
44
|
+
log(describeError(error));
|
|
42
45
|
exit(2);
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
@@ -52,23 +55,25 @@ async function runBench(command, deps = {}) {
|
|
|
52
55
|
});
|
|
53
56
|
if (command.advertiseHost != null) resolveAdvertiseHost(command.advertiseHost);
|
|
54
57
|
} catch (error) {
|
|
55
|
-
log(
|
|
58
|
+
log(describeError(error));
|
|
56
59
|
exit(2);
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
await writeOutput(renderPlan(suite), command.output);
|
|
61
|
-
exit(0);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
const tier = classifyTarget(suite.target);
|
|
62
|
+
const tier = await classifyResolvedTarget(suite.target, deps.resolveTargetAddresses);
|
|
65
63
|
const probe = await probeBenchmarkMode(suite.target, fetchImpl);
|
|
66
64
|
try {
|
|
65
|
+
if (!command.dryRun) assertUnsafeOverrideAllowed({
|
|
66
|
+
tier,
|
|
67
|
+
benchmarkMode: probe.benchmarkMode,
|
|
68
|
+
allowUnsafe: command.allowUnsafeTarget,
|
|
69
|
+
explicitCliTarget: command.target != null,
|
|
70
|
+
scenarios: unsafeOverrideScenarios(validated)
|
|
71
|
+
});
|
|
67
72
|
assertTargetAllowed({
|
|
68
73
|
tier,
|
|
69
74
|
benchmarkMode: probe.benchmarkMode,
|
|
70
75
|
allowUnsafe: command.allowUnsafeTarget,
|
|
71
|
-
dryRun:
|
|
76
|
+
dryRun: command.dryRun
|
|
72
77
|
});
|
|
73
78
|
} catch (error) {
|
|
74
79
|
if (error instanceof UnsafeTargetError) {
|
|
@@ -78,11 +83,6 @@ async function runBench(command, deps = {}) {
|
|
|
78
83
|
}
|
|
79
84
|
throw error;
|
|
80
85
|
}
|
|
81
|
-
if (tier !== "loopback" && command.advertiseHost == null && suite.scenarios.some((s) => SIGNED_TYPES.has(s.type))) {
|
|
82
|
-
log("Signed scenarios (inbox) need the benchmark's synthetic actor server to be reachable from the target. A loopback target reaches it automatically; for a non-loopback target, pass --advertise-host with an address the target can reach (the synthetic server then binds all interfaces), or use a read scenario such as webfinger.");
|
|
83
|
-
exit(2);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
86
|
const allowPrivateAddress = tier !== "public";
|
|
87
87
|
const documentLoader = await getDocumentLoader({
|
|
88
88
|
allowPrivateAddress,
|
|
@@ -92,12 +92,44 @@ async function runBench(command, deps = {}) {
|
|
|
92
92
|
allowPrivateAddress,
|
|
93
93
|
userAgent: command.userAgent
|
|
94
94
|
});
|
|
95
|
-
const assertDestinationAllowed = (url) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
const assertDestinationAllowed = async (url, scenario) => {
|
|
96
|
+
const destinationTier = url.origin === suite.target.origin ? tier : await classifyResolvedTarget(url, deps.resolveTargetAddresses);
|
|
97
|
+
assertInboxDestinationAllowed(url, {
|
|
98
|
+
targetOrigin: suite.target.origin,
|
|
99
|
+
targetTier: tier,
|
|
100
|
+
destinationTier,
|
|
101
|
+
targetBenchmarkMode: probe.benchmarkMode,
|
|
102
|
+
allowUnsafe: command.allowUnsafeTarget,
|
|
103
|
+
advertised: command.advertiseHost != null
|
|
104
|
+
});
|
|
105
|
+
assertPublicDestinationOverrideAllowed(url, scenario, {
|
|
106
|
+
targetOrigin: suite.target.origin,
|
|
107
|
+
targetBenchmarkMode: probe.benchmarkMode,
|
|
108
|
+
allowUnsafe: command.allowUnsafeTarget,
|
|
109
|
+
explicitCliTarget: command.target != null,
|
|
110
|
+
destinationTier,
|
|
111
|
+
defaults: validated.defaults
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
if (command.dryRun) try {
|
|
115
|
+
await writeOutput(await renderPlan(suite, {
|
|
116
|
+
documentLoader,
|
|
117
|
+
contextLoader,
|
|
118
|
+
allowPrivateAddress,
|
|
119
|
+
assertDestinationAllowed
|
|
120
|
+
}), command.output);
|
|
121
|
+
exit(0);
|
|
122
|
+
return;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
log(describeError(error));
|
|
125
|
+
exit(2);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (tier !== "loopback" && command.advertiseHost == null && suite.scenarios.some((s) => SIGNED_TYPES.has(s.type))) {
|
|
129
|
+
log("Signed scenarios (inbox) need the benchmark's synthetic actor server to be reachable from the target. A loopback target reaches it automatically; for a non-loopback target, pass --advertise-host with an address the target can reach (the synthetic server then binds all interfaces), or use a read scenario such as webfinger.");
|
|
130
|
+
exit(2);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
101
133
|
let fleet;
|
|
102
134
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
103
135
|
try {
|
|
@@ -114,7 +146,7 @@ async function runBench(command, deps = {}) {
|
|
|
114
146
|
allowPrivateAddress,
|
|
115
147
|
fleet: fleet ?? null,
|
|
116
148
|
fetch: fetchImpl,
|
|
117
|
-
assertDestinationAllowed
|
|
149
|
+
assertDestinationAllowed: (url) => assertDestinationAllowed(url, scenario)
|
|
118
150
|
});
|
|
119
151
|
results.push(buildScenarioResult(scenario, measurement));
|
|
120
152
|
}
|
|
@@ -185,19 +217,93 @@ async function defaultWriteOutput(content, outputPath) {
|
|
|
185
217
|
}
|
|
186
218
|
await writeFile(outputPath, content, { encoding: "utf-8" });
|
|
187
219
|
}
|
|
188
|
-
function renderPlan(suite) {
|
|
220
|
+
async function renderPlan(suite, context) {
|
|
189
221
|
const lines = [
|
|
190
222
|
"Fedify benchmark plan (dry run)",
|
|
191
223
|
"",
|
|
192
224
|
`Target: ${suite.target.href}`,
|
|
193
225
|
""
|
|
194
226
|
];
|
|
195
|
-
for (const scenario of suite.scenarios)
|
|
196
|
-
|
|
227
|
+
for (const scenario of suite.scenarios) {
|
|
228
|
+
lines.push(`- ${scenario.name} (${scenario.type}): ${describePlan(scenario)}`);
|
|
229
|
+
lines.push(...await describeDiscoveryPlan(scenario, suite, context));
|
|
230
|
+
}
|
|
231
|
+
lines.push("", "No benchmark load was sent. Discovery and stats probe requests may have been sent.");
|
|
197
232
|
return `${lines.join("\n")}\n`;
|
|
198
233
|
}
|
|
199
234
|
function describePlan(scenario) {
|
|
200
235
|
return `${scenario.load.kind === "open" ? `open-loop ${scenario.load.ratePerSec}/s ${scenario.load.arrival}` : `closed-loop concurrency ${scenario.load.concurrency}`}, duration ${scenario.durationMs}ms, signing ${scenario.signing}`;
|
|
201
236
|
}
|
|
237
|
+
async function describeDiscoveryPlan(scenario, suite, context) {
|
|
238
|
+
switch (scenario.type) {
|
|
239
|
+
case "inbox": return await describeInboxDiscoveryPlan(scenario, context);
|
|
240
|
+
case "webfinger": return describeWebFingerPlan(scenario, suite.target);
|
|
241
|
+
default: return [" discovery: not available for this scenario type"];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function describeInboxDiscoveryPlan(scenario, context) {
|
|
245
|
+
const lines = [];
|
|
246
|
+
for (const recipient of scenario.recipients) {
|
|
247
|
+
let discovered;
|
|
248
|
+
try {
|
|
249
|
+
discovered = await discoverInbox(recipient, {
|
|
250
|
+
documentLoader: context.documentLoader,
|
|
251
|
+
contextLoader: context.contextLoader,
|
|
252
|
+
allowPrivateAddress: context.allowPrivateAddress
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
lines.push(` recipient ${recipient}: discovery failed (${describeError(error)})`);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const inbox = selectInbox(discovered, scenario.inbox);
|
|
259
|
+
lines.push(` recipient ${recipient}: actor ${discovered.actorUri.href}, inbox ${inbox.href}`);
|
|
260
|
+
lines.push(` destination safety: ${await describeDestinationSafety(inbox, scenario, context)}`);
|
|
261
|
+
}
|
|
262
|
+
return lines;
|
|
263
|
+
}
|
|
264
|
+
function describeWebFingerPlan(scenario, target) {
|
|
265
|
+
return (scenario.recipients.length > 0 ? scenario.recipients : [target.href]).map((recipient) => {
|
|
266
|
+
const resource = convertUrlIfHandle(recipient).href;
|
|
267
|
+
const url = new URL("/.well-known/webfinger", target);
|
|
268
|
+
url.searchParams.set("resource", resource);
|
|
269
|
+
return ` webfinger ${resource}: GET ${url.href}`;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
async function describeDestinationSafety(inbox, scenario, context) {
|
|
273
|
+
try {
|
|
274
|
+
await context.assertDestinationAllowed(inbox, scenario);
|
|
275
|
+
return "allowed";
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (error instanceof UnsafeTargetError) return `would be refused: ${error.message}`;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function assertPublicDestinationOverrideAllowed(url, scenario, context) {
|
|
282
|
+
const inheritsTargetGate = url.origin === context.targetOrigin && context.targetBenchmarkMode;
|
|
283
|
+
if (context.destinationTier !== "public" || inheritsTargetGate || !context.allowUnsafe) return;
|
|
284
|
+
assertUnsafeOverrideAllowed({
|
|
285
|
+
tier: "public",
|
|
286
|
+
benchmarkMode: false,
|
|
287
|
+
allowUnsafe: true,
|
|
288
|
+
explicitCliTarget: context.explicitCliTarget,
|
|
289
|
+
scenarios: [unsafeOverrideScenario(scenario, context.defaults)]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function unsafeOverrideScenarios(suite) {
|
|
293
|
+
return suite.scenarios.map((scenario) => unsafeOverrideScenario(scenario, suite.defaults));
|
|
294
|
+
}
|
|
295
|
+
function unsafeOverrideScenario(scenario, defaults) {
|
|
296
|
+
const defaultDuration = defaults?.duration != null;
|
|
297
|
+
const defaultLoad = hasExplicitLoad(defaults?.load);
|
|
298
|
+
const raw = "raw" in scenario ? scenario.raw : scenario;
|
|
299
|
+
return {
|
|
300
|
+
name: scenario.name,
|
|
301
|
+
explicitDuration: raw.duration != null || defaultDuration,
|
|
302
|
+
explicitLoad: hasExplicitLoad(raw.load) || defaultLoad
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function hasExplicitLoad(load) {
|
|
306
|
+
return load != null && typeof load === "object" && ("rate" in load && load.rate != null || "concurrency" in load && load.concurrency != null);
|
|
307
|
+
}
|
|
202
308
|
//#endregion
|
|
203
309
|
export { runBench as default };
|
package/dist/bench/command.js
CHANGED
|
@@ -22,7 +22,7 @@ const benchCommand = command("bench", merge("Benchmark options", object({
|
|
|
22
22
|
target: optional(option("-t", "--target", string({ metavar: "URL" }), { description: message`Override the target URL declared in the suite.` })),
|
|
23
23
|
format: formatOption,
|
|
24
24
|
output: optional(option("-o", "--output", string({ metavar: "OUTPUT_PATH" }), { description: message`Write the report to a file instead of standard output.` })),
|
|
25
|
-
dryRun: withDefault(flag("--dry-run", { description: message`
|
|
25
|
+
dryRun: withDefault(flag("--dry-run", { description: message`Resolve discovery and print the benchmark plan without \
|
|
26
26
|
sending load.` }), false),
|
|
27
27
|
advertiseHost: optional(option("--advertise-host", string({ metavar: "HOST" }), { description: message`Host (name or IP) a non-loopback target can reach the \
|
|
28
28
|
benchmark's synthetic actor server at. Required for signed scenarios against a \
|
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { classifyTarget } from "./tiers.js";
|
|
3
2
|
//#region src/bench/safety/gate.ts
|
|
4
|
-
/**
|
|
5
|
-
* The client-side safety gate.
|
|
6
|
-
*
|
|
7
|
-
* A run is allowed without friction when the target is loopback/private or
|
|
8
|
-
* advertises benchmark mode (the operator's "not production" assertion). Only
|
|
9
|
-
* a public target that does not advertise benchmark mode is gated, behind an
|
|
10
|
-
* explicit `--allow-unsafe-target`. There is no interactive prompt, so the
|
|
11
|
-
* flag is mandatory in CI and any non-TTY context. An inspection-only run
|
|
12
|
-
* (the `dryRun` flag) sends no load, so it bypasses the gate.
|
|
13
|
-
* @since 2.3.0
|
|
14
|
-
* @module
|
|
15
|
-
*/
|
|
16
3
|
/** An error raised when a target is refused by the safety gate. */
|
|
17
4
|
var UnsafeTargetError = class extends Error {};
|
|
18
5
|
/**
|
|
@@ -29,6 +16,24 @@ function assertTargetAllowed(context) {
|
|
|
29
16
|
throw new UnsafeTargetError("Refusing to benchmark a public target that does not advertise benchmark mode. If you control this target, pass --allow-unsafe-target (mandatory in CI and any non-interactive context).");
|
|
30
17
|
}
|
|
31
18
|
/**
|
|
19
|
+
* Asserts that an unsafe public-target override is specific and bounded.
|
|
20
|
+
*
|
|
21
|
+
* The override is only meaningful for a public target that does not advertise
|
|
22
|
+
* benchmark mode. In that caution tier, the operator must name the target on
|
|
23
|
+
* the command line for this run and must explicitly set load and duration, so
|
|
24
|
+
* the built-in defaults cannot accidentally create a long public benchmark.
|
|
25
|
+
* @param context The unsafe override decision inputs.
|
|
26
|
+
* @throws {UnsafeTargetError} If the unsafe override is too broad.
|
|
27
|
+
*/
|
|
28
|
+
function assertUnsafeOverrideAllowed(context) {
|
|
29
|
+
if (context.tier !== "public" || context.benchmarkMode || !context.allowUnsafe) return;
|
|
30
|
+
if (!context.explicitCliTarget) throw new UnsafeTargetError("The --allow-unsafe-target override must be paired with an explicit --target for this run.");
|
|
31
|
+
for (const scenario of context.scenarios) {
|
|
32
|
+
if (!scenario.explicitLoad) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark load default. Set rate or concurrency explicitly before using --allow-unsafe-target against a public target.`);
|
|
33
|
+
if (!scenario.explicitDuration) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark duration default. Set duration explicitly before using --allow-unsafe-target against a public target.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
32
37
|
* Asserts that a resolved inbox URL — the actual destination of signed
|
|
33
38
|
* benchmark load — may be sent to. The suite's `target` is gated separately by
|
|
34
39
|
* {@link assertTargetAllowed}; this catches a destination that differs from it
|
|
@@ -45,10 +50,11 @@ function assertTargetAllowed(context) {
|
|
|
45
50
|
* @throws {UnsafeTargetError} If the destination is refused.
|
|
46
51
|
*/
|
|
47
52
|
function assertInboxDestinationAllowed(url, context) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
53
|
+
const sameOrigin = url.origin === context.targetOrigin;
|
|
54
|
+
const tier = sameOrigin ? context.targetTier : context.destinationTier;
|
|
55
|
+
const inheritsTargetGate = sameOrigin && context.targetBenchmarkMode;
|
|
50
56
|
if (tier === "public" && !inheritsTargetGate && !context.allowUnsafe) throw new UnsafeTargetError(`Refusing to send benchmark load to ${url.href}: it is a public inbox that is neither part of the benchmarked target nor covered by benchmark mode. Pass --allow-unsafe-target to override.`);
|
|
51
57
|
if (tier !== "loopback" && !context.advertised) throw new UnsafeTargetError(`Refusing to send signed benchmark load to ${url.href}: the synthetic actor server is unreachable from a non-loopback inbox. Pass --advertise-host with an address it can reach.`);
|
|
52
58
|
}
|
|
53
59
|
//#endregion
|
|
54
|
-
export { UnsafeTargetError, assertInboxDestinationAllowed, assertTargetAllowed };
|
|
60
|
+
export { UnsafeTargetError, assertInboxDestinationAllowed, assertTargetAllowed, assertUnsafeOverrideAllowed };
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
2
3
|
//#region src/bench/safety/tiers.ts
|
|
3
4
|
/**
|
|
5
|
+
* Target risk classification.
|
|
6
|
+
*
|
|
7
|
+
* A target is `loopback` or `private` when it is clearly one of the operator's
|
|
8
|
+
* own boxes, and `public` otherwise. Classification is conservative: a host
|
|
9
|
+
* that is not obviously loopback or private is treated as `public` (the gated
|
|
10
|
+
* tier), since the tool cannot tell staging from production without resolving
|
|
11
|
+
* and trusting DNS.
|
|
12
|
+
* @since 2.3.0
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
4
16
|
* Classifies a target URL into a risk tier from its host.
|
|
5
17
|
* @param target The target URL.
|
|
6
18
|
* @returns The risk tier.
|
|
@@ -14,6 +26,50 @@ function classifyTarget(target) {
|
|
|
14
26
|
if (host.includes(":")) return classifyIpv6(host);
|
|
15
27
|
return "public";
|
|
16
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Classifies a target URL, resolving DNS for public-looking hostnames.
|
|
31
|
+
*
|
|
32
|
+
* Literal addresses and known local hostname suffixes are classified directly.
|
|
33
|
+
* Other hostnames are resolved and classified from their addresses; any public
|
|
34
|
+
* address in the answer keeps the target public, and DNS failure is treated as
|
|
35
|
+
* public so the safety gate remains conservative.
|
|
36
|
+
* @param target The target URL.
|
|
37
|
+
* @param resolveAddresses Hostname resolver, overridable for tests.
|
|
38
|
+
* @returns The resolved target tier.
|
|
39
|
+
*/
|
|
40
|
+
async function classifyResolvedTarget(target, resolveAddresses = defaultResolveTargetAddresses) {
|
|
41
|
+
const host = normalizedHost(target);
|
|
42
|
+
const direct = classifyTarget(target);
|
|
43
|
+
if (direct !== "public" || isIpLiteral(host)) return direct;
|
|
44
|
+
try {
|
|
45
|
+
const resolved = await resolveAddresses(host);
|
|
46
|
+
if (!Array.isArray(resolved) || resolved.length < 1) return "public";
|
|
47
|
+
let aggregate = "loopback";
|
|
48
|
+
for (const address of resolved) {
|
|
49
|
+
const tier = classifyTarget(new URL(`http://${hostForAddress(address)}/`));
|
|
50
|
+
if (tier === "public") return "public";
|
|
51
|
+
if (tier === "private") aggregate = "private";
|
|
52
|
+
}
|
|
53
|
+
return aggregate;
|
|
54
|
+
} catch {
|
|
55
|
+
return "public";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Resolves a hostname with the platform DNS resolver. */
|
|
59
|
+
async function defaultResolveTargetAddresses(hostname) {
|
|
60
|
+
return (await lookup(hostname, { all: true })).map((entry) => entry.address);
|
|
61
|
+
}
|
|
62
|
+
function normalizedHost(target) {
|
|
63
|
+
let host = target.hostname.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
64
|
+
if (host.endsWith(".")) host = host.slice(0, -1);
|
|
65
|
+
return host;
|
|
66
|
+
}
|
|
67
|
+
function isIpLiteral(host) {
|
|
68
|
+
return isIpv4(host) || host.includes(":");
|
|
69
|
+
}
|
|
70
|
+
function hostForAddress(address) {
|
|
71
|
+
return address.includes(":") ? `[${address}]` : address;
|
|
72
|
+
}
|
|
17
73
|
function isIpv4(host) {
|
|
18
74
|
const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
19
75
|
return match != null && match.slice(1).every((octet) => Number(octet) <= 255);
|
|
@@ -38,4 +94,4 @@ function classifyIpv6(host) {
|
|
|
38
94
|
return "public";
|
|
39
95
|
}
|
|
40
96
|
//#endregion
|
|
41
|
-
export {
|
|
97
|
+
export { classifyResolvedTarget };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { asList } from "../scenario/coerce.js";
|
|
3
2
|
import { discoverInbox, selectInbox } from "../discovery/discover.js";
|
|
3
|
+
import { asList } from "../scenario/coerce.js";
|
|
4
4
|
import { runLoad } from "../load/generator.js";
|
|
5
5
|
import { aggregateSamples } from "../metrics/aggregate.js";
|
|
6
6
|
import { diffSnapshots, fetchServerSnapshot, snapshotToMetrics } from "../metrics/stats-client.js";
|
|
@@ -41,7 +41,7 @@ const inboxRunner = {
|
|
|
41
41
|
allowPrivateAddress: context.allowPrivateAddress
|
|
42
42
|
});
|
|
43
43
|
const inbox = selectInbox(discovered, scenario.inbox);
|
|
44
|
-
context.assertDestinationAllowed?.(inbox);
|
|
44
|
+
await context.assertDestinationAllowed?.(inbox);
|
|
45
45
|
targets.push({
|
|
46
46
|
inbox,
|
|
47
47
|
actorUri: discovered.actorUri
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { message } from "@optique/core";
|
|
3
|
-
import { createConfigContext } from "@optique/config";
|
|
4
3
|
import { printError } from "@optique/run";
|
|
4
|
+
import { createConfigContext } from "@optique/config";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { parse } from "smol-toml";
|
|
7
7
|
import { array, boolean, check, forward, integer as integer$1, minValue, number, object as object$1, optional as optional$1, picklist, pipe, string as string$1 } from "valibot";
|
package/dist/deno.js
CHANGED
package/dist/inbox/view.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { renderActivity, renderRawActivity, renderRequest, renderResponse } from "./rendercode.js";
|
|
3
|
+
import util from "node:util";
|
|
3
4
|
import { getStatusText } from "@poppanator/http-constants";
|
|
4
5
|
import { Fragment } from "hono/jsx";
|
|
5
6
|
import { getSingletonHighlighter } from "shiki";
|
|
6
|
-
import util from "node:util";
|
|
7
7
|
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
8
8
|
//#region src/inbox/view.tsx
|
|
9
9
|
const Layout = (props) => {
|
package/dist/inbox.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { getDocumentLoader } from "./docloader.js";
|
|
3
|
+
import { colors, matchesActor } from "./utils.js";
|
|
3
4
|
import { version } from "./deno.js";
|
|
4
5
|
import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
|
|
5
6
|
import { configureLogging, recordingSink } from "./log.js";
|
|
6
7
|
import { tableStyle } from "./table.js";
|
|
7
8
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
8
|
-
import { colors, matchesActor } from "./utils.js";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { MemoryKvStore, createFederation, generateCryptoKeyPair } from "@fedify/fedify";
|
|
11
11
|
import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
|
package/dist/lookup.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { getContextLoader, getDocumentLoader as getDocumentLoader$1 } from "./docloader.js";
|
|
3
|
+
import { colorEnabled, colors, describeError, formatObject } from "./utils.js";
|
|
3
4
|
import { configContext } from "./config.js";
|
|
4
5
|
import { createTunnelServiceOption, userAgentOption } from "./options.js";
|
|
5
6
|
import { configureLogging } from "./log.js";
|
|
6
7
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
7
|
-
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
8
8
|
import { renderImages } from "./imagerenderer.js";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
11
11
|
import { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address } from "@fedify/vocab-runtime";
|
|
12
12
|
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
13
|
-
import { getLogger } from "@logtape/logtape";
|
|
14
13
|
import { argument, choice, command, constant, flag, float, integer, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
15
|
-
import { bindConfig } from "@optique/config";
|
|
16
14
|
import { path, printError } from "@optique/run";
|
|
15
|
+
import { getLogger } from "@logtape/logtape";
|
|
16
|
+
import { bindConfig } from "@optique/config";
|
|
17
17
|
import { createWriteStream } from "node:fs";
|
|
18
18
|
import { url } from "@optique/core/message";
|
|
19
19
|
import ora from "ora";
|
|
@@ -285,7 +285,7 @@ function handleTimeoutError(spinner, timeoutSeconds, url) {
|
|
|
285
285
|
printError(message`Try increasing the timeout with ${optionNames(["-T", "--timeout"])} option or check network connectivity.`);
|
|
286
286
|
}
|
|
287
287
|
function isPrivateAddressError(error) {
|
|
288
|
-
const lowerMessage = (error
|
|
288
|
+
const lowerMessage = describeError(error).toLowerCase();
|
|
289
289
|
if (error instanceof UrlError) return lowerMessage.includes("invalid or private address") || lowerMessage.includes("localhost is not allowed");
|
|
290
290
|
return lowerMessage.includes("private address") || lowerMessage.includes("private ip") || lowerMessage.includes("localhost") || lowerMessage.includes("loopback");
|
|
291
291
|
}
|
|
@@ -308,7 +308,7 @@ function isPrivateAddressTarget(target) {
|
|
|
308
308
|
return getPrivateUrlCandidate(target) != null;
|
|
309
309
|
}
|
|
310
310
|
function getPrivateContextUrl(error) {
|
|
311
|
-
const errorMessage =
|
|
311
|
+
const errorMessage = describeError(error);
|
|
312
312
|
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl" || !errorMessage.includes("valid JSON-LD object")) return null;
|
|
313
313
|
const structuredError = error;
|
|
314
314
|
const structuredUrl = getPrivateUrlCandidate(structuredError.details?.url) ?? getPrivateUrlCandidate(structuredError.url);
|
package/dist/nodeinfo.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { colors, formatObject } from "./utils.js";
|
|
2
3
|
import { configContext } from "./config.js";
|
|
3
4
|
import { userAgentOption } from "./options.js";
|
|
4
|
-
import { colors, formatObject } from "./utils.js";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { getNodeInfo } from "@fedify/fedify";
|
|
7
7
|
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
8
|
-
import os from "node:os";
|
|
9
|
-
import { getLogger } from "@logtape/logtape";
|
|
10
8
|
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
11
|
-
import { bindConfig } from "@optique/config";
|
|
12
9
|
import { print, printError } from "@optique/run";
|
|
10
|
+
import { getLogger } from "@logtape/logtape";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import { bindConfig } from "@optique/config";
|
|
13
13
|
import ora from "ora";
|
|
14
14
|
import { createJimp } from "@jimp/core";
|
|
15
15
|
import webp from "@jimp/wasm-webp";
|
package/dist/relay.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { colors, matchesActor } from "./utils.js";
|
|
2
3
|
import { configureLogging } from "./log.js";
|
|
3
4
|
import { tableStyle } from "./table.js";
|
|
4
5
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
5
|
-
import { colors, matchesActor } from "./utils.js";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { MemoryKvStore } from "@fedify/fedify";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
package/dist/runner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { describeError } from "./utils.js";
|
|
2
3
|
import { version } from "./deno.js";
|
|
3
4
|
import { configContext, tryLoadToml } from "./config.js";
|
|
4
5
|
import { globalOptions } from "./options.js";
|
|
@@ -14,13 +15,13 @@ import { tunnelCommand } from "./tunnel.js";
|
|
|
14
15
|
import { webFingerCommand } from "./webfinger/command.js";
|
|
15
16
|
import "./webfinger/mod.js";
|
|
16
17
|
import process from "node:process";
|
|
17
|
-
import { homedir } from "node:os";
|
|
18
18
|
import { group, merge, message, or } from "@optique/core";
|
|
19
19
|
import { printError, run } from "@optique/run";
|
|
20
|
+
import { merge as merge$1 } from "es-toolkit";
|
|
21
|
+
import { homedir } from "node:os";
|
|
20
22
|
import { readFileSync } from "node:fs";
|
|
21
23
|
import { parse } from "smol-toml";
|
|
22
24
|
import { join } from "node:path";
|
|
23
|
-
import { merge as merge$1 } from "es-toolkit";
|
|
24
25
|
//#region src/runner.ts
|
|
25
26
|
/**
|
|
26
27
|
* Returns the system-wide configuration file paths.
|
|
@@ -50,7 +51,7 @@ function loadConfig(parsed) {
|
|
|
50
51
|
if (parsed.configPath) try {
|
|
51
52
|
custom = parse(readFileSync(parsed.configPath, "utf-8"));
|
|
52
53
|
} catch (error) {
|
|
53
|
-
printError(message`Could not load config file at ${parsed.configPath}: ${
|
|
54
|
+
printError(message`Could not load config file at ${parsed.configPath}: ${describeError(error)}`);
|
|
54
55
|
process.exit(1);
|
|
55
56
|
}
|
|
56
57
|
return {
|
package/dist/utils.js
CHANGED
|
@@ -2,13 +2,13 @@ import "@js-temporal/polyfill";
|
|
|
2
2
|
import "node:fs/promises";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { getActorHandle } from "@fedify/vocab";
|
|
5
|
+
import "@fxts/core";
|
|
5
6
|
import { message } from "@optique/core";
|
|
6
7
|
import { print, printError } from "@optique/run";
|
|
7
|
-
import util from "node:util";
|
|
8
|
-
import "@fxts/core";
|
|
9
8
|
import { Chalk } from "chalk";
|
|
10
9
|
import { highlight } from "cli-highlight";
|
|
11
10
|
import { flow } from "es-toolkit";
|
|
11
|
+
import util from "node:util";
|
|
12
12
|
//#region src/utils.ts
|
|
13
13
|
const colorEnabled = process.stdout.isTTY && !("NO_COLOR" in process.env && process.env.NO_COLOR !== "");
|
|
14
14
|
const colors = new Chalk(colorEnabled ? {} : { level: 0 });
|
|
@@ -34,7 +34,10 @@ async function matchesActor(actor, actorList) {
|
|
|
34
34
|
}
|
|
35
35
|
return false;
|
|
36
36
|
}
|
|
37
|
+
function describeError(error) {
|
|
38
|
+
return error instanceof Error ? error.message : String(error);
|
|
39
|
+
}
|
|
37
40
|
flow(message, print);
|
|
38
41
|
flow(message, printError);
|
|
39
42
|
//#endregion
|
|
40
|
-
export { colorEnabled, colors, formatObject, matchesActor };
|
|
43
|
+
export { colorEnabled, colors, describeError, formatObject, matchesActor };
|
package/dist/webfinger/action.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { formatObject } from "../utils.js";
|
|
2
3
|
import { NotFoundError, getErrorMessage } from "./error.js";
|
|
3
4
|
import { convertUrlIfHandle } from "./lib.js";
|
|
4
|
-
import { formatObject } from "../utils.js";
|
|
5
5
|
import { print } from "@optique/run";
|
|
6
6
|
import { formatMessage, message } from "@optique/core/message";
|
|
7
7
|
import ora from "ora";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/cli",
|
|
3
|
-
"version": "2.3.0-dev.
|
|
3
|
+
"version": "2.3.0-dev.1273+6b46a8b5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"README.md",
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"srvx": "^0.8.7",
|
|
90
90
|
"valibot": "^1.4.0",
|
|
91
91
|
"yaml": "^2.9.0",
|
|
92
|
-
"@fedify/fedify": "2.3.0-dev.
|
|
93
|
-
"@fedify/init": "2.3.0-dev.
|
|
94
|
-
"@fedify/
|
|
95
|
-
"@fedify/sqlite": "2.3.0-dev.
|
|
96
|
-
"@fedify/
|
|
97
|
-
"@fedify/
|
|
98
|
-
"@fedify/vocab": "2.3.0-dev.
|
|
99
|
-
"@fedify/vocab-
|
|
92
|
+
"@fedify/fedify": "2.3.0-dev.1273+6b46a8b5",
|
|
93
|
+
"@fedify/init": "2.3.0-dev.1273+6b46a8b5",
|
|
94
|
+
"@fedify/vocab": "2.3.0-dev.1273+6b46a8b5",
|
|
95
|
+
"@fedify/sqlite": "2.3.0-dev.1273+6b46a8b5",
|
|
96
|
+
"@fedify/relay": "2.3.0-dev.1273+6b46a8b5",
|
|
97
|
+
"@fedify/webfinger": "2.3.0-dev.1273+6b46a8b5",
|
|
98
|
+
"@fedify/vocab-runtime": "2.3.0-dev.1273+6b46a8b5",
|
|
99
|
+
"@fedify/vocab-tools": "2.3.0-dev.1273+6b46a8b5"
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
102
|
"@types/bun": "^1.2.23",
|