@fedify/cli 2.3.0-dev.1347 → 2.3.0-dev.1361
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 +29 -189
- package/dist/bench/command.js +13 -43
- package/dist/bench/load/clock.js +2 -20
- package/dist/bench/load/generator.js +9 -42
- package/dist/bench/metrics/stats-client.js +3 -65
- package/dist/bench/mod.js +2 -9
- package/dist/bench/render/markdown.js +0 -1
- package/dist/bench/render/text.js +0 -1
- package/dist/bench/result/build.js +10 -133
- package/dist/bench/result/expect/evaluate.js +1 -1
- package/dist/bench/result/schema.js +3 -353
- package/dist/bench/safety/gate.js +2 -4
- package/dist/bench/scenario/normalize.js +2 -1
- package/dist/bench/scenario/schema.js +9 -50
- package/dist/bench/scenario/validate.js +2 -2
- package/dist/bench/scenarios/inbox.js +12 -4
- package/dist/bench/scenarios/registry.js +1 -19
- package/dist/bench/scenarios/runner.js +1 -21
- package/dist/bench/scenarios/webfinger.js +1 -1
- package/dist/cache.js +1 -1
- package/dist/config.js +1 -1
- package/dist/deno.js +1 -1
- package/dist/docloader.js +1 -1
- package/dist/generate-vocab/action.js +1 -1
- package/dist/generate-vocab/command.js +3 -5
- package/dist/generate-vocab/mod.js +4 -0
- package/dist/imagerenderer.js +2 -2
- package/dist/inbox/command.js +4 -6
- package/dist/inbox.js +4 -4
- package/dist/init/mod.js +3 -0
- package/dist/log.js +2 -2
- package/dist/lookup.js +123 -12
- package/dist/mod.js +23 -2
- package/dist/nodeinfo.js +9 -11
- package/dist/options.js +1 -1
- package/dist/relay/command.js +4 -6
- package/dist/relay.js +2 -2
- package/dist/runner.js +46 -69
- package/dist/tempserver.js +1 -1
- package/dist/tunnel.js +4 -6
- package/dist/utils.js +4 -5
- package/dist/webfinger/action.js +1 -1
- package/dist/webfinger/command.js +4 -6
- package/dist/webfinger/lib.js +1 -1
- package/dist/webfinger/mod.js +4 -0
- package/package.json +12 -13
- package/dist/bench/compare/schema.js +0 -16
- package/dist/bench/compare.js +0 -667
- package/dist/bench/scenarios/actor.js +0 -38
- package/dist/bench/scenarios/failure.js +0 -363
- package/dist/bench/scenarios/fanout.js +0 -261
- package/dist/bench/scenarios/mixed.js +0 -244
- package/dist/bench/scenarios/object-discovery.js +0 -211
- package/dist/bench/scenarios/object.js +0 -54
- package/dist/bench/scenarios/read.js +0 -108
- package/dist/commands.js +0 -110
- package/dist/lookup/command.js +0 -121
|
@@ -3,14 +3,14 @@ import "@js-temporal/polyfill";
|
|
|
3
3
|
/**
|
|
4
4
|
* The embedded JSON Schema (draft 2020-12) for benchmark scenario suite files.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* in sync. The matching TypeScript types live in {@link ./types.ts}.
|
|
6
|
+
* This object is the runtime copy used by the validator; it is published,
|
|
7
|
+
* byte-for-byte, as *schema/bench/scenario-v1.json* and a drift guard keeps the
|
|
8
|
+
* two in sync. The matching TypeScript types live in {@link ./types.ts}.
|
|
9
9
|
*
|
|
10
10
|
* The schema expresses every scenario type discussed for `fedify bench`
|
|
11
11
|
* (`inbox`, `webfinger`, `actor`, `object`, `fanout`, `collection`, `failure`,
|
|
12
|
-
* `mixed`)
|
|
13
|
-
* cross-field rules are enforced here rather than in code:
|
|
12
|
+
* `mixed`), even though only `inbox` and `webfinger` have runners in this
|
|
13
|
+
* version. Three cross-field rules are enforced here rather than in code:
|
|
14
14
|
*
|
|
15
15
|
* - exactly one HTTP request signature scheme per actor group
|
|
16
16
|
* (`contains` + `minContains`/`maxContains`);
|
|
@@ -20,10 +20,8 @@ import "@js-temporal/polyfill";
|
|
|
20
20
|
* @since 2.3.0
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
|
-
/** The hosted URL that serves the
|
|
24
|
-
const SCENARIO_SCHEMA_ID = "https://json-schema.fedify.dev/bench/scenario-
|
|
25
|
-
/** The hosted URL that serves the version 1 scenario schema. */
|
|
26
|
-
const SCENARIO_SCHEMA_ID_V1 = "https://json-schema.fedify.dev/bench/scenario-v1.json";
|
|
23
|
+
/** The hosted URL that serves the scenario schema. */
|
|
24
|
+
const SCENARIO_SCHEMA_ID = "https://json-schema.fedify.dev/bench/scenario-v1.json";
|
|
27
25
|
const READ_METRICS = [
|
|
28
26
|
"successRate",
|
|
29
27
|
"throughputPerSec",
|
|
@@ -53,11 +51,10 @@ const FANOUT_METRICS = [
|
|
|
53
51
|
"queueDrain.p99"
|
|
54
52
|
];
|
|
55
53
|
const MIXED_METRICS = [...new Set([...INBOX_METRICS, ...FANOUT_METRICS])];
|
|
56
|
-
const MIXED_V2_METRICS = [...READ_METRICS, "deliveryThroughput"];
|
|
57
54
|
/** The benchmark scenario suite JSON Schema (draft 2020-12). */
|
|
58
55
|
const scenarioSchemaV1 = {
|
|
59
56
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
60
|
-
$id:
|
|
57
|
+
$id: SCENARIO_SCHEMA_ID,
|
|
61
58
|
title: "Fedify benchmark scenario suite",
|
|
62
59
|
type: "object",
|
|
63
60
|
required: ["version", "scenarios"],
|
|
@@ -357,43 +354,5 @@ const scenarioSchemaV1 = {
|
|
|
357
354
|
}
|
|
358
355
|
}
|
|
359
356
|
};
|
|
360
|
-
/** The current benchmark scenario suite JSON Schema (draft 2020-12). */
|
|
361
|
-
const scenarioSchemaV2 = {
|
|
362
|
-
...scenarioSchemaV1,
|
|
363
|
-
$id: SCENARIO_SCHEMA_ID,
|
|
364
|
-
$defs: {
|
|
365
|
-
...scenarioSchemaV1.$defs,
|
|
366
|
-
scenario: {
|
|
367
|
-
...scenarioSchemaV1.$defs.scenario,
|
|
368
|
-
properties: {
|
|
369
|
-
...scenarioSchemaV1.$defs.scenario.properties,
|
|
370
|
-
sinkBase: { type: "string" }
|
|
371
|
-
},
|
|
372
|
-
allOf: scenarioSchemaV1.$defs.scenario.allOf.map((condition) => condition.if.properties.type.const === "failure" ? {
|
|
373
|
-
if: condition.if,
|
|
374
|
-
then: { properties: condition.then.properties }
|
|
375
|
-
} : condition.if.properties.type.const === "actor" || condition.if.properties.type.const === "object" ? {
|
|
376
|
-
if: condition.if,
|
|
377
|
-
then: {
|
|
378
|
-
required: condition.then.required,
|
|
379
|
-
allOf: [{
|
|
380
|
-
if: {
|
|
381
|
-
required: ["authenticated"],
|
|
382
|
-
properties: { authenticated: { const: true } }
|
|
383
|
-
},
|
|
384
|
-
then: { properties: { expect: { propertyNames: { enum: INBOX_METRICS } } } },
|
|
385
|
-
else: { properties: { expect: { propertyNames: { enum: READ_METRICS } } } }
|
|
386
|
-
}]
|
|
387
|
-
}
|
|
388
|
-
} : condition.if.properties.type.const === "mixed" ? {
|
|
389
|
-
if: condition.if,
|
|
390
|
-
then: {
|
|
391
|
-
required: condition.then.required,
|
|
392
|
-
properties: { expect: { propertyNames: { enum: MIXED_V2_METRICS } } }
|
|
393
|
-
}
|
|
394
|
-
} : condition)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
357
|
//#endregion
|
|
399
|
-
export {
|
|
358
|
+
export { scenarioSchemaV1 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import {
|
|
2
|
+
import { scenarioSchemaV1 } from "./schema.js";
|
|
3
3
|
import { SuiteValidationError } from "./errors.js";
|
|
4
4
|
import { Validator } from "@cfworker/json-schema";
|
|
5
5
|
//#region src/bench/scenario/validate.ts
|
|
@@ -10,7 +10,7 @@ import { Validator } from "@cfworker/json-schema";
|
|
|
10
10
|
*/
|
|
11
11
|
let validator;
|
|
12
12
|
function getValidator() {
|
|
13
|
-
validator ??= new Validator(
|
|
13
|
+
validator ??= new Validator(scenarioSchemaV1, "2020-12");
|
|
14
14
|
return validator;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -4,11 +4,11 @@ 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";
|
|
7
|
-
import { createSigningPipeline } from "../signing/pipeline.js";
|
|
8
|
-
import { estimateTotal, loadPlanOf, measuredWindowMs, sendRequest, validateInboxSelector, withMeasuredWindowStart } from "./runner.js";
|
|
9
7
|
import { createActivityIdMinter } from "../signing/activity-id.js";
|
|
8
|
+
import { createSigningPipeline } from "../signing/pipeline.js";
|
|
10
9
|
import { signInboxDelivery } from "../signing/signer.js";
|
|
11
10
|
import { isGenerateDirective, resolveGenerate } from "../template/generate.js";
|
|
11
|
+
import { estimateTotal, loadPlanOf, measuredWindowMs, sendRequest, withMeasuredWindowStart } from "./runner.js";
|
|
12
12
|
import { Create, Note } from "@fedify/vocab";
|
|
13
13
|
//#region src/bench/scenarios/inbox.ts
|
|
14
14
|
/**
|
|
@@ -84,7 +84,7 @@ const inboxRunner = {
|
|
|
84
84
|
}, rawSend);
|
|
85
85
|
try {
|
|
86
86
|
await pipeline.prime();
|
|
87
|
-
const measurement = aggregateSamples((await runLoad(loadPlanOf(scenario, context.rng), send, context.clock
|
|
87
|
+
const measurement = aggregateSamples((await runLoad(loadPlanOf(scenario, context.rng), send, context.clock)).samples, {
|
|
88
88
|
measuredWindowMs: measuredWindowMs(scenario),
|
|
89
89
|
includeHistogram: true
|
|
90
90
|
});
|
|
@@ -107,7 +107,15 @@ const inboxRunner = {
|
|
|
107
107
|
* mid-run, and a non-http URL would slip through to the send path.
|
|
108
108
|
*/
|
|
109
109
|
function validateInbox(scenario) {
|
|
110
|
-
|
|
110
|
+
const mode = scenario.inbox;
|
|
111
|
+
if (mode == null || mode === "shared" || mode === "personal") return;
|
|
112
|
+
let url;
|
|
113
|
+
try {
|
|
114
|
+
url = new URL(mode);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error(`Scenario "${scenario.name}": inbox must be "shared", "personal", or an http(s) URL; got ${JSON.stringify(mode)}.`);
|
|
117
|
+
}
|
|
118
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.hostname === "" || url.username !== "" || url.password !== "") throw new Error(`Scenario "${scenario.name}": inbox URL must be a bare http(s) URL with a host and no credentials; got ${JSON.stringify(mode)}.`);
|
|
111
119
|
}
|
|
112
120
|
/**
|
|
113
121
|
* Rejects the activity options the inbox runner cannot yet honor: it always
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { actorRunner } from "./actor.js";
|
|
3
|
-
import { fanoutRunner } from "./fanout.js";
|
|
4
|
-
import { failureRunner } from "./failure.js";
|
|
5
2
|
import { inboxRunner } from "./inbox.js";
|
|
6
|
-
import { objectRunner } from "./object.js";
|
|
7
3
|
import { webfingerRunner } from "./webfinger.js";
|
|
8
|
-
import { mixedRunner } from "./mixed.js";
|
|
9
4
|
//#region src/bench/scenarios/registry.ts
|
|
10
5
|
/** The scenario types that have runners in this version. */
|
|
11
|
-
const IMPLEMENTED_SCENARIO_TYPES = [
|
|
12
|
-
"inbox",
|
|
13
|
-
"webfinger",
|
|
14
|
-
"actor",
|
|
15
|
-
"object",
|
|
16
|
-
"fanout",
|
|
17
|
-
"failure",
|
|
18
|
-
"mixed"
|
|
19
|
-
];
|
|
6
|
+
const IMPLEMENTED_SCENARIO_TYPES = ["inbox", "webfinger"];
|
|
20
7
|
/**
|
|
21
8
|
* Returns the runner for a scenario type.
|
|
22
9
|
* @param type The scenario type.
|
|
@@ -27,11 +14,6 @@ function runnerFor(type) {
|
|
|
27
14
|
switch (type) {
|
|
28
15
|
case "inbox": return inboxRunner;
|
|
29
16
|
case "webfinger": return webfingerRunner;
|
|
30
|
-
case "actor": return actorRunner;
|
|
31
|
-
case "object": return objectRunner;
|
|
32
|
-
case "fanout": return fanoutRunner;
|
|
33
|
-
case "failure": return failureRunner;
|
|
34
|
-
case "mixed": return mixedRunner;
|
|
35
17
|
default: throw new Error(`The "${type}" scenario type is not implemented in this version of fedify bench; supported types: ${IMPLEMENTED_SCENARIO_TYPES.join(", ")}.`);
|
|
36
18
|
}
|
|
37
19
|
}
|
|
@@ -46,26 +46,6 @@ function estimateTotal(scenario) {
|
|
|
46
46
|
if (scenario.load.kind !== "open") return void 0;
|
|
47
47
|
return Math.ceil(scenario.load.ratePerSec * (scenario.durationMs / 1e3));
|
|
48
48
|
}
|
|
49
|
-
/** Returns whether a URL is fetchable by benchmark runners without surprises. */
|
|
50
|
-
function isBareHttpUrl(url) {
|
|
51
|
-
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname !== "" && url.username === "" && url.password === "";
|
|
52
|
-
}
|
|
53
|
-
/** Rejects URLs that are not bare http(s) URLs with a host and no credentials. */
|
|
54
|
-
function assertBareHttpUrl(scenarioName, label, url) {
|
|
55
|
-
if (isBareHttpUrl(url)) return;
|
|
56
|
-
throw new Error(`Scenario "${scenarioName}": ${label} must be a bare http(s) URL with a host and no credentials; got ${JSON.stringify(url.href)}.`);
|
|
57
|
-
}
|
|
58
|
-
/** Validates an inbox selector or explicit inbox URL. */
|
|
59
|
-
function validateInboxSelector(scenarioName, inbox) {
|
|
60
|
-
if (inbox == null || inbox === "shared" || inbox === "personal") return;
|
|
61
|
-
let url;
|
|
62
|
-
try {
|
|
63
|
-
url = new URL(inbox);
|
|
64
|
-
} catch {
|
|
65
|
-
throw new Error(`Scenario "${scenarioName}": inbox must be "shared", "personal", or an http(s) URL; got ${JSON.stringify(inbox)}.`);
|
|
66
|
-
}
|
|
67
|
-
assertBareHttpUrl(scenarioName, "inbox URL", url);
|
|
68
|
-
}
|
|
69
49
|
/**
|
|
70
50
|
* Wraps a send function so that `onMeasuredWindowStart` runs exactly once, at
|
|
71
51
|
* the warm-up boundary, and *every* measured request waits for it to settle
|
|
@@ -93,4 +73,4 @@ function withMeasuredWindowStart(warmupMs, onMeasuredWindowStart, send) {
|
|
|
93
73
|
};
|
|
94
74
|
}
|
|
95
75
|
//#endregion
|
|
96
|
-
export {
|
|
76
|
+
export { estimateTotal, loadPlanOf, measuredWindowMs, sendRequest, withMeasuredWindowStart };
|
|
@@ -29,7 +29,7 @@ const webfingerRunner = { async run(context) {
|
|
|
29
29
|
baseline = await fetchServerSnapshot(context.target, fetchImpl);
|
|
30
30
|
baselineTaken = true;
|
|
31
31
|
}, rawSend);
|
|
32
|
-
const measurement = aggregateSamples((await runLoad(loadPlanOf(context.scenario, context.rng), send, context.clock
|
|
32
|
+
const measurement = aggregateSamples((await runLoad(loadPlanOf(context.scenario, context.rng), send, context.clock)).samples, {
|
|
33
33
|
measuredWindowMs: measuredWindowMs(context.scenario),
|
|
34
34
|
includeHistogram: true
|
|
35
35
|
});
|
package/dist/cache.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
5
|
-
import { mkdir } from "node:fs/promises";
|
|
6
6
|
//#region src/cache.ts
|
|
7
7
|
/**
|
|
8
8
|
* Returns the default cache directory path.
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { message } from "@optique/core";
|
|
3
3
|
import { printError } from "@optique/run";
|
|
4
|
+
import { createConfigContext } from "@optique/config";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import { parse } from "smol-toml";
|
|
6
|
-
import { createConfigContext } from "@optique/config";
|
|
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";
|
|
8
8
|
//#region src/config.ts
|
|
9
9
|
/**
|
package/dist/deno.js
CHANGED
package/dist/docloader.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
3
2
|
import { kvCache } from "@fedify/fedify";
|
|
3
|
+
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
4
4
|
import { getKvStore } from "#kv";
|
|
5
5
|
//#region src/docloader.ts
|
|
6
6
|
const documentLoaders = {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
2
3
|
import process from "node:process";
|
|
3
4
|
import { printError } from "@optique/run";
|
|
4
|
-
import { stat } from "node:fs/promises";
|
|
5
5
|
import { generateVocab } from "@fedify/vocab-tools";
|
|
6
6
|
import { message } from "@optique/core/message";
|
|
7
7
|
//#region src/generate-vocab/action.ts
|
|
@@ -12,12 +12,10 @@ const generatedPath = argument(path({
|
|
|
12
12
|
type: "file",
|
|
13
13
|
allowCreate: true
|
|
14
14
|
}), { description: message`Path to output the generated vocabulary classes. Should end with ${".ts"} suffix.` });
|
|
15
|
-
const
|
|
15
|
+
const generateVocabCommand = command("generate-vocab", object("Generation options", {
|
|
16
16
|
command: constant("generate-vocab"),
|
|
17
17
|
schemaDir,
|
|
18
18
|
generatedPath
|
|
19
|
-
});
|
|
20
|
-
const generateVocabMetadata = { description: message`Generate vocabulary classes from schema files.` };
|
|
21
|
-
command("generate-vocab", generateVocabOptions, generateVocabMetadata);
|
|
19
|
+
}), { description: message`Generate vocabulary classes from schema files.` });
|
|
22
20
|
//#endregion
|
|
23
|
-
export {
|
|
21
|
+
export { generateVocabCommand as default };
|
package/dist/imagerenderer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { Jimp } from "./nodeinfo.js";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
3
4
|
import process from "node:process";
|
|
5
|
+
import { validatePublicUrl } from "@fedify/vocab-runtime";
|
|
4
6
|
import os from "node:os";
|
|
5
7
|
import path from "node:path";
|
|
6
|
-
import { validatePublicUrl } from "@fedify/vocab-runtime";
|
|
7
|
-
import fs from "node:fs/promises";
|
|
8
8
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
9
9
|
//#region src/imagerenderer.ts
|
|
10
10
|
const KITTY_IDENTIFIERS = [
|
package/dist/inbox/command.js
CHANGED
|
@@ -6,7 +6,7 @@ import { bindConfig } from "@optique/config";
|
|
|
6
6
|
//#region src/inbox/command.ts
|
|
7
7
|
const DEFAULT_EPHEMERAL_INBOX_NAME = "Fedify Ephemeral Inbox";
|
|
8
8
|
const DEFAULT_EPHEMERAL_INBOX_SUMMARY = "An ephemeral ActivityPub inbox for testing purposes.";
|
|
9
|
-
const
|
|
9
|
+
const inboxCommand = command("inbox", merge(object("Inbox options", {
|
|
10
10
|
command: constant("inbox"),
|
|
11
11
|
follow: bindConfig(multiple(option("-f", "--follow", string({ metavar: "URI" }), { description: message`Follow the given actor. The argument can be either an actor URI or a handle. Can be specified multiple times.` })), {
|
|
12
12
|
context: configContext,
|
|
@@ -33,11 +33,9 @@ const inboxOptions = merge(object("Inbox options", {
|
|
|
33
33
|
key: (config) => config.inbox?.authorizedFetch ?? false,
|
|
34
34
|
default: false
|
|
35
35
|
})
|
|
36
|
-
}), group("Tunnel options", createTunnelOption("inbox")))
|
|
37
|
-
const inboxMetadata = {
|
|
36
|
+
}), group("Tunnel options", createTunnelOption("inbox"))), {
|
|
38
37
|
brief: message`Run an ephemeral ActivityPub inbox server.`,
|
|
39
38
|
description: message`Spins up an ephemeral server that serves the ActivityPub inbox with a one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`
|
|
40
|
-
};
|
|
41
|
-
command("inbox", inboxOptions, inboxMetadata);
|
|
39
|
+
});
|
|
42
40
|
//#endregion
|
|
43
|
-
export {
|
|
41
|
+
export { inboxCommand };
|
package/dist/inbox.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import { getDocumentLoader } from "./docloader.js";
|
|
2
3
|
import { colors, matchesActor } from "./utils.js";
|
|
3
|
-
import { configureLogging, recordingSink } from "./log.js";
|
|
4
4
|
import { version } from "./deno.js";
|
|
5
|
-
import { getDocumentLoader } from "./docloader.js";
|
|
6
5
|
import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
|
|
6
|
+
import { configureLogging, recordingSink } from "./log.js";
|
|
7
7
|
import { tableStyle } from "./table.js";
|
|
8
8
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { MemoryKvStore, createFederation, generateCryptoKeyPair } from "@fedify/fedify";
|
|
11
|
-
import { getLogger } from "@logtape/logtape";
|
|
12
|
-
import ora from "ora";
|
|
13
11
|
import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
|
|
12
|
+
import { getLogger } from "@logtape/logtape";
|
|
14
13
|
import Table from "cli-table3";
|
|
15
14
|
import { Hono } from "hono";
|
|
15
|
+
import ora from "ora";
|
|
16
16
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
17
17
|
//#region src/inbox.tsx
|
|
18
18
|
/** @jsx react-jsx */
|
package/dist/init/mod.js
ADDED
package/dist/log.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
2
3
|
import process from "node:process";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
4
|
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
5
|
-
import { mkdir } from "node:fs/promises";
|
|
6
5
|
import { getFileSink } from "@logtape/file";
|
|
7
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
8
|
//#region src/log.ts
|
|
9
9
|
function getRecordingSink() {
|
|
10
10
|
let records = [];
|
package/dist/lookup.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import {
|
|
2
|
+
import { getContextLoader, getDocumentLoader as getDocumentLoader$1 } from "./docloader.js";
|
|
3
3
|
import { colorEnabled, colors, describeError, formatObject } from "./utils.js";
|
|
4
|
+
import { configContext } from "./config.js";
|
|
5
|
+
import { createTunnelServiceOption, userAgentOption } from "./options.js";
|
|
4
6
|
import { configureLogging } from "./log.js";
|
|
5
|
-
import { getContextLoader, getDocumentLoader as getDocumentLoader$1 } from "./docloader.js";
|
|
6
7
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
7
8
|
import { renderImages } from "./imagerenderer.js";
|
|
8
9
|
import process from "node:process";
|
|
9
|
-
import { message, optionNames } from "@optique/core";
|
|
10
|
-
import { printError } from "@optique/run";
|
|
11
|
-
import { createWriteStream } from "node:fs";
|
|
12
|
-
import { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address } from "@fedify/vocab-runtime";
|
|
13
10
|
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
14
|
-
import {
|
|
15
|
-
import ora from "ora";
|
|
11
|
+
import { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address } from "@fedify/vocab-runtime";
|
|
16
12
|
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
13
|
+
import { argument, choice, command, constant, flag, float, integer, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
14
|
+
import { path, printError } from "@optique/run";
|
|
15
|
+
import { getLogger } from "@logtape/logtape";
|
|
16
|
+
import { bindConfig } from "@optique/config";
|
|
17
|
+
import { createWriteStream } from "node:fs";
|
|
17
18
|
import { url } from "@optique/core/message";
|
|
19
|
+
import ora from "ora";
|
|
18
20
|
import { isIP } from "node:net";
|
|
19
21
|
//#region src/lookup.ts
|
|
20
22
|
const logger = getLogger([
|
|
@@ -22,6 +24,116 @@ const logger = getLogger([
|
|
|
22
24
|
"cli",
|
|
23
25
|
"lookup"
|
|
24
26
|
]);
|
|
27
|
+
const IN_REPLY_TO_IRI = "https://www.w3.org/ns/activitystreams#inReplyTo";
|
|
28
|
+
const QUOTE_IRI = "https://w3id.org/fep/044f#quote";
|
|
29
|
+
const QUOTE_URL_IRI = "https://www.w3.org/ns/activitystreams#quoteUrl";
|
|
30
|
+
const MISSKEY_QUOTE_IRI = "https://misskey-hub.net/ns#_misskey_quote";
|
|
31
|
+
const FEDIBIRD_QUOTE_IRI = "http://fedibird.com/ns#quoteUri";
|
|
32
|
+
const recurseProperties = [
|
|
33
|
+
"replyTarget",
|
|
34
|
+
"quote",
|
|
35
|
+
"quoteUrl",
|
|
36
|
+
IN_REPLY_TO_IRI,
|
|
37
|
+
QUOTE_IRI,
|
|
38
|
+
QUOTE_URL_IRI,
|
|
39
|
+
MISSKEY_QUOTE_IRI,
|
|
40
|
+
FEDIBIRD_QUOTE_IRI
|
|
41
|
+
];
|
|
42
|
+
const suppressErrorsOption = bindConfig(flag("-S", "--suppress-errors", { description: message`Suppress partial errors during traversal or recursion.` }), {
|
|
43
|
+
context: configContext,
|
|
44
|
+
key: (config) => config.lookup?.suppressErrors ?? false,
|
|
45
|
+
default: false
|
|
46
|
+
});
|
|
47
|
+
const allowPrivateAddressOption = bindConfig(flag("-p", "--allow-private-address", { description: message`Allow private IP addresses for URLs discovered \
|
|
48
|
+
during traversal or recursive object fetches. Recursive JSON-LD \
|
|
49
|
+
context URLs always remain blocked. URLs explicitly provided on the \
|
|
50
|
+
command line always allow private addresses.` }), {
|
|
51
|
+
context: configContext,
|
|
52
|
+
key: (config) => config.lookup?.allowPrivateAddress ?? false,
|
|
53
|
+
default: false
|
|
54
|
+
});
|
|
55
|
+
const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
56
|
+
authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }), () => true), {
|
|
57
|
+
context: configContext,
|
|
58
|
+
key: (config) => config.lookup?.authorizedFetch ? true : void 0
|
|
59
|
+
}),
|
|
60
|
+
firstKnock: bindConfig(option("--first-knock", choice(["draft-cavage-http-signatures-12", "rfc9421"]), { description: message`The first-knock spec for ${optionNames(["-a", "--authorized-fetch"])}. It is used for the double-knocking technique.` }), {
|
|
61
|
+
context: configContext,
|
|
62
|
+
key: (config) => config.lookup?.firstKnock ?? "draft-cavage-http-signatures-12",
|
|
63
|
+
default: "draft-cavage-http-signatures-12"
|
|
64
|
+
}),
|
|
65
|
+
tunnelService: optional(createTunnelServiceOption())
|
|
66
|
+
}), {
|
|
67
|
+
authorizedFetch: false,
|
|
68
|
+
firstKnock: void 0,
|
|
69
|
+
tunnelService: void 0
|
|
70
|
+
});
|
|
71
|
+
const lookupModeOption = withDefault(or(object("Recurse options", {
|
|
72
|
+
traverse: constant(false),
|
|
73
|
+
recurse: bindConfig(option("--recurse", choice(recurseProperties, { metavar: "PROPERTY" }), { description: message`Recursively follow a relationship property.` }), {
|
|
74
|
+
context: configContext,
|
|
75
|
+
key: (config) => config.lookup?.recurse
|
|
76
|
+
}),
|
|
77
|
+
recurseDepth: bindConfig(option("--recurse-depth", integer({
|
|
78
|
+
min: 1,
|
|
79
|
+
metavar: "DEPTH"
|
|
80
|
+
}), { description: message`Maximum recursion depth for ${optionNames(["--recurse"])}.` }), {
|
|
81
|
+
context: configContext,
|
|
82
|
+
key: (config) => config.lookup?.recurseDepth,
|
|
83
|
+
default: 20
|
|
84
|
+
}),
|
|
85
|
+
suppressErrors: suppressErrorsOption
|
|
86
|
+
}), object("Traverse options", {
|
|
87
|
+
traverse: bindConfig(flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }), {
|
|
88
|
+
context: configContext,
|
|
89
|
+
key: (config) => config.lookup?.traverse ?? false,
|
|
90
|
+
default: false
|
|
91
|
+
}),
|
|
92
|
+
recurse: constant(void 0),
|
|
93
|
+
recurseDepth: constant(void 0),
|
|
94
|
+
suppressErrors: suppressErrorsOption
|
|
95
|
+
})), {
|
|
96
|
+
traverse: false,
|
|
97
|
+
recurse: void 0,
|
|
98
|
+
recurseDepth: void 0,
|
|
99
|
+
suppressErrors: false
|
|
100
|
+
});
|
|
101
|
+
const lookupCommand = command("lookup", merge(object({ command: constant("lookup") }), lookupModeOption, authorizedFetchOption, merge("Network options", userAgentOption, object({
|
|
102
|
+
allowPrivateAddress: allowPrivateAddressOption,
|
|
103
|
+
timeout: optional(bindConfig(option("-T", "--timeout", float({
|
|
104
|
+
min: 0,
|
|
105
|
+
metavar: "SECONDS"
|
|
106
|
+
}), { description: message`Set timeout for network requests in seconds.` }), {
|
|
107
|
+
context: configContext,
|
|
108
|
+
key: (config) => config.lookup?.timeout
|
|
109
|
+
}))
|
|
110
|
+
})), object("Arguments", { urls: multiple(argument(string({ metavar: "URL_OR_HANDLE" }), { description: message`One or more URLs or handles to look up.` }), { min: 1 }) }), object("Output options", {
|
|
111
|
+
reverse: bindConfig(flag("--reverse", { description: message`Reverse the output order of fetched objects or items.` }), {
|
|
112
|
+
context: configContext,
|
|
113
|
+
key: (config) => config.lookup?.reverse ?? false,
|
|
114
|
+
default: false
|
|
115
|
+
}),
|
|
116
|
+
format: bindConfig(optional(or(map(flag("-r", "--raw", { description: message`Print the fetched JSON-LD document as is.` }), () => "raw"), map(flag("-C", "--compact", { description: message`Compact the fetched JSON-LD document.` }), () => "compact"), map(flag("-e", "--expand", { description: message`Expand the fetched JSON-LD document.` }), () => "expand"))), {
|
|
117
|
+
context: configContext,
|
|
118
|
+
key: (config) => config.lookup?.defaultFormat ?? "default",
|
|
119
|
+
default: "default"
|
|
120
|
+
}),
|
|
121
|
+
separator: bindConfig(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message`Specify the separator between adjacent output objects or collection items.` }), {
|
|
122
|
+
context: configContext,
|
|
123
|
+
key: (config) => config.lookup?.separator ?? "----",
|
|
124
|
+
default: "----"
|
|
125
|
+
}),
|
|
126
|
+
output: optional(option("-o", "--output", path({
|
|
127
|
+
metavar: "OUTPUT_PATH",
|
|
128
|
+
type: "file",
|
|
129
|
+
allowCreate: true
|
|
130
|
+
}), { description: message`Specify the output file path.` }))
|
|
131
|
+
})), {
|
|
132
|
+
brief: message`Look up Activity Streams objects.`,
|
|
133
|
+
description: message`Look up Activity Streams objects by URL or actor handle.
|
|
134
|
+
|
|
135
|
+
The arguments can be either URLs or actor handles (e.g., ${"@username@domain"}), and they can be multiple.`
|
|
136
|
+
});
|
|
25
137
|
var TimeoutError = class extends Error {
|
|
26
138
|
name = "TimeoutError";
|
|
27
139
|
constructor(message) {
|
|
@@ -360,7 +472,6 @@ async function runLookup(command, deps = {}) {
|
|
|
360
472
|
effectiveDeps.exit(cleanupFailed && code === 0 ? 1 : code);
|
|
361
473
|
};
|
|
362
474
|
if (command.authorizedFetch) {
|
|
363
|
-
const firstKnock = command.firstKnock ?? "draft-cavage-http-signatures-12";
|
|
364
475
|
spinner.text = "Generating a one-time key pair...";
|
|
365
476
|
const key = await generateCryptoKeyPair();
|
|
366
477
|
spinner.text = "Spinning up a temporary ActivityPub server...";
|
|
@@ -400,7 +511,7 @@ async function runLookup(command, deps = {}) {
|
|
|
400
511
|
userAgent: command.userAgent,
|
|
401
512
|
specDeterminer: {
|
|
402
513
|
determineSpec() {
|
|
403
|
-
return firstKnock;
|
|
514
|
+
return command.firstKnock;
|
|
404
515
|
},
|
|
405
516
|
rememberSpec() {}
|
|
406
517
|
}
|
|
@@ -410,7 +521,7 @@ async function runLookup(command, deps = {}) {
|
|
|
410
521
|
userAgent: command.userAgent,
|
|
411
522
|
specDeterminer: {
|
|
412
523
|
determineSpec() {
|
|
413
|
-
return firstKnock;
|
|
524
|
+
return command.firstKnock;
|
|
414
525
|
},
|
|
415
526
|
rememberSpec() {}
|
|
416
527
|
}
|
|
@@ -707,4 +818,4 @@ async function runLookup(command, deps = {}) {
|
|
|
707
818
|
if (success && command.output) spinner.succeed(`Successfully wrote output to ${colors.green(command.output)}.`);
|
|
708
819
|
}
|
|
709
820
|
//#endregion
|
|
710
|
-
export { runLookup };
|
|
821
|
+
export { lookupCommand, runLookup };
|
package/dist/mod.js
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import "@js-temporal/polyfill";
|
|
3
|
-
import
|
|
3
|
+
import runBench from "./bench/action.js";
|
|
4
|
+
import "./bench/mod.js";
|
|
5
|
+
import runGenerateVocab from "./generate-vocab/action.js";
|
|
6
|
+
import "./generate-vocab/mod.js";
|
|
7
|
+
import { runInbox } from "./inbox.js";
|
|
8
|
+
import { runInit } from "./init/mod.js";
|
|
9
|
+
import { runNodeInfo } from "./nodeinfo.js";
|
|
10
|
+
import { runLookup } from "./lookup.js";
|
|
11
|
+
import { runRelay } from "./relay.js";
|
|
12
|
+
import { runTunnel } from "./tunnel.js";
|
|
13
|
+
import runWebFinger from "./webfinger/action.js";
|
|
14
|
+
import "./webfinger/mod.js";
|
|
15
|
+
import { runCli } from "./runner.js";
|
|
4
16
|
import process from "node:process";
|
|
5
17
|
//#region src/mod.ts
|
|
6
18
|
async function main() {
|
|
7
|
-
|
|
19
|
+
const result = await runCli(process.argv.slice(2));
|
|
20
|
+
if (result.command === "init") await runInit(result);
|
|
21
|
+
else if (result.command === "lookup") await runLookup(result);
|
|
22
|
+
else if (result.command === "webfinger") await runWebFinger(result);
|
|
23
|
+
else if (result.command === "inbox") await runInbox(result);
|
|
24
|
+
else if (result.command === "nodeinfo") await runNodeInfo(result);
|
|
25
|
+
else if (result.command === "tunnel") await runTunnel(result);
|
|
26
|
+
else if (result.command === "generate-vocab") await runGenerateVocab(result);
|
|
27
|
+
else if (result.command === "relay") await runRelay(result);
|
|
28
|
+
else if (result.command === "bench") await runBench(result);
|
|
8
29
|
}
|
|
9
30
|
await main();
|
|
10
31
|
//#endregion
|
package/dist/nodeinfo.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
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
|
+
import { getNodeInfo } from "@fedify/fedify";
|
|
7
|
+
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
6
8
|
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
7
9
|
import { print, printError } from "@optique/run";
|
|
10
|
+
import { getLogger } from "@logtape/logtape";
|
|
8
11
|
import os from "node:os";
|
|
9
12
|
import { bindConfig } from "@optique/config";
|
|
10
|
-
import
|
|
11
|
-
import { getNodeInfo } from "@fedify/fedify";
|
|
13
|
+
import ora from "ora";
|
|
12
14
|
import { createJimp } from "@jimp/core";
|
|
13
15
|
import webp from "@jimp/wasm-webp";
|
|
14
|
-
import { getLogger } from "@logtape/logtape";
|
|
15
16
|
import { isICO, parseICO } from "icojs";
|
|
16
17
|
import { defaultFormats, defaultPlugins, intToRGBA } from "jimp";
|
|
17
|
-
import ora from "ora";
|
|
18
18
|
//#region src/nodeinfo.ts
|
|
19
19
|
const logger = getLogger([
|
|
20
20
|
"fedify",
|
|
@@ -46,17 +46,15 @@ const nodeInfoOption = merge(object("Display options", {
|
|
|
46
46
|
key: (config) => config.nodeinfo?.bestEffort ?? false,
|
|
47
47
|
default: false
|
|
48
48
|
}) }));
|
|
49
|
-
const
|
|
49
|
+
const nodeInfoCommand = command("nodeinfo", merge(object("Arguments", {
|
|
50
50
|
command: constant("nodeinfo"),
|
|
51
51
|
host: argument(string({ metavar: "HOST" }), { description: message`Bare hostname or a full URL of the instance` })
|
|
52
|
-
}), nodeInfoOption, group("Network options", userAgentOption))
|
|
53
|
-
const nodeInfoMetadata = {
|
|
52
|
+
}), nodeInfoOption, group("Network options", userAgentOption)), {
|
|
54
53
|
brief: message`Get information about a remote node using the NodeInfo protocol`,
|
|
55
54
|
description: message`Get information about a remote node using the NodeInfo protocol.
|
|
56
55
|
|
|
57
56
|
The argument is the hostname of the remote node, or the URL of the remote node.`
|
|
58
|
-
};
|
|
59
|
-
command("nodeinfo", nodeInfoOptions, nodeInfoMetadata);
|
|
57
|
+
});
|
|
60
58
|
async function runNodeInfo(command) {
|
|
61
59
|
const spinner = ora({
|
|
62
60
|
text: "Fetching a NodeInfo document...",
|
|
@@ -272,4 +270,4 @@ function getAsciiArt(image, width = DEFAULT_IMAGE_WIDTH, colorSupport, colors) {
|
|
|
272
270
|
return art;
|
|
273
271
|
}
|
|
274
272
|
//#endregion
|
|
275
|
-
export { Jimp,
|
|
273
|
+
export { Jimp, nodeInfoCommand, runNodeInfo };
|