@fedify/cli 2.3.0-dev.994 → 2.3.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/dist/bench/action.js +469 -0
- package/dist/bench/actor/documents.js +39 -0
- package/dist/bench/actor/fleet.js +39 -0
- package/dist/bench/actor/keys.js +35 -0
- package/dist/bench/command.js +72 -0
- package/dist/bench/compare/schema.js +16 -0
- package/dist/bench/compare.js +667 -0
- package/dist/bench/discovery/discover.js +67 -0
- package/dist/bench/discovery/probe.js +50 -0
- package/dist/bench/load/arrival.js +27 -0
- package/dist/bench/load/clock.js +33 -0
- package/dist/bench/load/generator.js +145 -0
- package/dist/bench/metrics/aggregate.js +64 -0
- package/dist/bench/metrics/histogram.js +141 -0
- package/dist/bench/metrics/stats-client.js +216 -0
- package/dist/bench/mod.js +11 -0
- package/dist/bench/render/format.js +46 -0
- package/dist/bench/render/index.js +20 -0
- package/dist/bench/render/json.js +12 -0
- package/dist/bench/render/markdown.js +63 -0
- package/dist/bench/render/text.js +75 -0
- package/dist/bench/result/build.js +252 -0
- package/dist/bench/result/expect/assert.js +74 -0
- package/dist/bench/result/expect/evaluate.js +128 -0
- package/dist/bench/result/expect/metrics.js +34 -0
- package/dist/bench/result/schema.js +365 -0
- package/dist/bench/safety/gate.js +62 -0
- package/dist/bench/safety/tiers.js +97 -0
- package/dist/bench/scenario/coerce.js +24 -0
- package/dist/bench/scenario/errors.js +36 -0
- package/dist/bench/scenario/load.js +69 -0
- package/dist/bench/scenario/normalize.js +125 -0
- package/dist/bench/scenario/schema.js +399 -0
- package/dist/bench/scenario/units.js +56 -0
- package/dist/bench/scenario/validate.js +29 -0
- package/dist/bench/scenarios/actor.js +38 -0
- package/dist/bench/scenarios/failure.js +363 -0
- package/dist/bench/scenarios/fanout.js +261 -0
- package/dist/bench/scenarios/inbox.js +147 -0
- package/dist/bench/scenarios/mixed.js +244 -0
- package/dist/bench/scenarios/object-discovery.js +211 -0
- package/dist/bench/scenarios/object.js +54 -0
- package/dist/bench/scenarios/read.js +108 -0
- package/dist/bench/scenarios/registry.js +39 -0
- package/dist/bench/scenarios/runner.js +96 -0
- package/dist/bench/scenarios/webfinger.js +44 -0
- package/dist/bench/server/synthetic.js +118 -0
- package/dist/bench/signing/activity-id.js +18 -0
- package/dist/bench/signing/pipeline.js +134 -0
- package/dist/bench/signing/signer.js +39 -0
- package/dist/bench/template/generate.js +90 -0
- package/dist/bench/template/helpers.js +19 -0
- package/dist/bench/template/template.js +132 -0
- package/dist/cache.js +2 -2
- package/dist/commands.js +110 -0
- package/dist/config.js +15 -3
- package/dist/deno.js +1 -1
- package/dist/docloader.js +1 -1
- package/dist/generate-vocab/action.js +3 -3
- package/dist/generate-vocab/command.js +6 -4
- package/dist/imagerenderer.js +3 -3
- package/dist/inbox/command.js +6 -4
- package/dist/inbox/view.js +1 -1
- package/dist/inbox.js +4 -4
- package/dist/log.js +2 -2
- package/dist/lookup/command.js +121 -0
- package/dist/lookup.js +27 -138
- package/dist/mod.js +2 -20
- package/dist/nodeinfo.js +53 -12
- package/dist/options.js +1 -1
- package/dist/relay/command.js +6 -4
- package/dist/relay.js +3 -3
- package/dist/runner.js +70 -45
- package/dist/tunnel.js +8 -6
- package/dist/utils.js +9 -4
- package/dist/webfinger/action.js +1 -1
- package/dist/webfinger/command.js +6 -4
- package/dist/webfinger/error.js +2 -0
- package/dist/webfinger/lib.js +1 -1
- package/package.json +28 -23
- package/dist/generate-vocab/mod.js +0 -4
- package/dist/init/mod.js +0 -3
- package/dist/webfinger/mod.js +0 -4
package/dist/lookup.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import {
|
|
2
|
+
import { FEDIBIRD_QUOTE_IRI, IN_REPLY_TO_IRI, MISSKEY_QUOTE_IRI, QUOTE_IRI, QUOTE_URL_IRI } from "./lookup/command.js";
|
|
3
|
+
import { colorEnabled, colors, describeError, formatObject } from "./utils.js";
|
|
3
4
|
import { configureLogging } from "./log.js";
|
|
5
|
+
import { getContextLoader, getDocumentLoader as getDocumentLoader$1 } from "./docloader.js";
|
|
4
6
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
5
|
-
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
6
|
-
import { configContext } from "./config.js";
|
|
7
|
-
import { createTunnelServiceOption, userAgentOption } from "./options.js";
|
|
8
7
|
import { renderImages } from "./imagerenderer.js";
|
|
9
|
-
import { url } from "@optique/core/message";
|
|
10
|
-
import { path, printError } from "@optique/run";
|
|
11
8
|
import process from "node:process";
|
|
12
|
-
import {
|
|
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
13
|
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
14
|
-
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
15
14
|
import { getLogger } from "@logtape/logtape";
|
|
16
15
|
import ora from "ora";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { createWriteStream } from "node:fs";
|
|
16
|
+
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
17
|
+
import { url } from "@optique/core/message";
|
|
20
18
|
import { isIP } from "node:net";
|
|
21
19
|
//#region src/lookup.ts
|
|
22
20
|
const logger = getLogger([
|
|
@@ -24,116 +22,6 @@ const logger = getLogger([
|
|
|
24
22
|
"cli",
|
|
25
23
|
"lookup"
|
|
26
24
|
]);
|
|
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$1`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$1`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$1`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$1`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$1`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$1`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$1`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$1`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$1`One or more URLs or handles to look up.` }), { min: 1 }) }), object("Output options", {
|
|
111
|
-
reverse: bindConfig(flag("--reverse", { description: message$1`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$1`Print the fetched JSON-LD document as is.` }), () => "raw"), map(flag("-C", "--compact", { description: message$1`Compact the fetched JSON-LD document.` }), () => "compact"), map(flag("-e", "--expand", { description: message$1`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$1`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$1`Specify the output file path.` }))
|
|
131
|
-
})), {
|
|
132
|
-
brief: message$1`Look up Activity Streams objects.`,
|
|
133
|
-
description: message$1`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
|
-
});
|
|
137
25
|
var TimeoutError = class extends Error {
|
|
138
26
|
name = "TimeoutError";
|
|
139
27
|
constructor(message) {
|
|
@@ -282,10 +170,10 @@ function wrapDocumentLoaderWithTimeout(loader, timeoutSeconds) {
|
|
|
282
170
|
function handleTimeoutError(spinner, timeoutSeconds, url) {
|
|
283
171
|
const urlText = url ? ` for: ${colors.red(url)}` : "";
|
|
284
172
|
spinner.fail(`Request timed out after ${timeoutSeconds} seconds${urlText}.`);
|
|
285
|
-
printError(message
|
|
173
|
+
printError(message`Try increasing the timeout with ${optionNames(["-T", "--timeout"])} option or check network connectivity.`);
|
|
286
174
|
}
|
|
287
175
|
function isPrivateAddressError(error) {
|
|
288
|
-
const lowerMessage = (error
|
|
176
|
+
const lowerMessage = describeError(error).toLowerCase();
|
|
289
177
|
if (error instanceof UrlError) return lowerMessage.includes("invalid or private address") || lowerMessage.includes("localhost is not allowed");
|
|
290
178
|
return lowerMessage.includes("private address") || lowerMessage.includes("private ip") || lowerMessage.includes("localhost") || lowerMessage.includes("loopback");
|
|
291
179
|
}
|
|
@@ -308,7 +196,7 @@ function isPrivateAddressTarget(target) {
|
|
|
308
196
|
return getPrivateUrlCandidate(target) != null;
|
|
309
197
|
}
|
|
310
198
|
function getPrivateContextUrl(error) {
|
|
311
|
-
const errorMessage =
|
|
199
|
+
const errorMessage = describeError(error);
|
|
312
200
|
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl" || !errorMessage.includes("valid JSON-LD object")) return null;
|
|
313
201
|
const structuredError = error;
|
|
314
202
|
const structuredUrl = getPrivateUrlCandidate(structuredError.details?.url) ?? getPrivateUrlCandidate(structuredError.url);
|
|
@@ -318,10 +206,10 @@ function getPrivateContextUrl(error) {
|
|
|
318
206
|
return getPrivateUrlCandidate(match[1]);
|
|
319
207
|
}
|
|
320
208
|
function printRecursivePrivateAddressHint() {
|
|
321
|
-
printError(message
|
|
209
|
+
printError(message`The recursive target appears to be private or localhost. Try with ${optionNames(["-p", "--allow-private-address"])}, or use ${optionNames(["-S", "--suppress-errors"])} to skip blocked steps.`);
|
|
322
210
|
}
|
|
323
211
|
function printRecursivePrivateContextHint(privateContextUrl) {
|
|
324
|
-
printError(message
|
|
212
|
+
printError(message`Recursive JSON-LD context URL ${url(privateContextUrl)} is always blocked, even with ${optionNames(["-p", "--allow-private-address"])}. Use ${optionNames(["-S", "--suppress-errors"])} to skip blocked steps.`);
|
|
325
213
|
}
|
|
326
214
|
function getLookupFailureHint(error, options = {}) {
|
|
327
215
|
if (isPrivateAddressError(error)) return options.recursive ? "recursive-private-address" : "private-address";
|
|
@@ -338,13 +226,13 @@ function printLookupFailureHint(authLoader, error, options = {}) {
|
|
|
338
226
|
if (!shouldPrintLookupFailureHint(authLoader, hint)) return;
|
|
339
227
|
switch (hint) {
|
|
340
228
|
case "private-address":
|
|
341
|
-
printError(message
|
|
229
|
+
printError(message`The URL appears to be private or localhost. Try with ${optionNames(["-p", "--allow-private-address"])}.`);
|
|
342
230
|
return;
|
|
343
231
|
case "recursive-private-address":
|
|
344
232
|
printRecursivePrivateAddressHint();
|
|
345
233
|
return;
|
|
346
234
|
case "authorized-fetch":
|
|
347
|
-
printError(message
|
|
235
|
+
printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
348
236
|
return;
|
|
349
237
|
}
|
|
350
238
|
}
|
|
@@ -418,7 +306,7 @@ async function runLookup(command, deps = {}) {
|
|
|
418
306
|
...deps
|
|
419
307
|
};
|
|
420
308
|
if (command.urls.length < 1) {
|
|
421
|
-
printError(message
|
|
309
|
+
printError(message`At least one URL or actor handle must be provided.`);
|
|
422
310
|
effectiveDeps.exit(1);
|
|
423
311
|
}
|
|
424
312
|
if (command.debug) await configureLogging();
|
|
@@ -472,6 +360,7 @@ async function runLookup(command, deps = {}) {
|
|
|
472
360
|
effectiveDeps.exit(cleanupFailed && code === 0 ? 1 : code);
|
|
473
361
|
};
|
|
474
362
|
if (command.authorizedFetch) {
|
|
363
|
+
const firstKnock = command.firstKnock ?? "draft-cavage-http-signatures-12";
|
|
475
364
|
spinner.text = "Generating a one-time key pair...";
|
|
476
365
|
const key = await generateCryptoKeyPair();
|
|
477
366
|
spinner.text = "Spinning up a temporary ActivityPub server...";
|
|
@@ -511,7 +400,7 @@ async function runLookup(command, deps = {}) {
|
|
|
511
400
|
userAgent: command.userAgent,
|
|
512
401
|
specDeterminer: {
|
|
513
402
|
determineSpec() {
|
|
514
|
-
return
|
|
403
|
+
return firstKnock;
|
|
515
404
|
},
|
|
516
405
|
rememberSpec() {}
|
|
517
406
|
}
|
|
@@ -521,7 +410,7 @@ async function runLookup(command, deps = {}) {
|
|
|
521
410
|
userAgent: command.userAgent,
|
|
522
411
|
specDeterminer: {
|
|
523
412
|
determineSpec() {
|
|
524
|
-
return
|
|
413
|
+
return firstKnock;
|
|
525
414
|
},
|
|
526
415
|
rememberSpec() {}
|
|
527
416
|
}
|
|
@@ -559,7 +448,7 @@ async function runLookup(command, deps = {}) {
|
|
|
559
448
|
}
|
|
560
449
|
if (current == null) {
|
|
561
450
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
562
|
-
if (authLoader == null) printError(message
|
|
451
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
563
452
|
await finalizeAndExit(1);
|
|
564
453
|
return;
|
|
565
454
|
}
|
|
@@ -601,7 +490,7 @@ async function runLookup(command, deps = {}) {
|
|
|
601
490
|
else if (error instanceof RecursiveLookupError) {
|
|
602
491
|
spinner.fail(`Failed to recursively fetch object: ${colors.red(error.target)}.`);
|
|
603
492
|
if (!command.allowPrivateAddress && isPrivateAddressTarget(error.target)) printRecursivePrivateAddressHint();
|
|
604
|
-
else if (authLoader == null) printError(message
|
|
493
|
+
else if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
605
494
|
} else {
|
|
606
495
|
spinner.fail("Failed to recursively fetch object.");
|
|
607
496
|
const privateContextUrl = getPrivateContextUrl(error);
|
|
@@ -611,7 +500,7 @@ async function runLookup(command, deps = {}) {
|
|
|
611
500
|
return;
|
|
612
501
|
}
|
|
613
502
|
const hint = getLookupFailureHint(error, { recursive: true });
|
|
614
|
-
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message
|
|
503
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the ${optionNames(["-S", "--suppress-errors"])} option to suppress partial errors.`);
|
|
615
504
|
else printLookupFailureHint(authLoader, error, { recursive: true });
|
|
616
505
|
}
|
|
617
506
|
await finalizeAndExit(1);
|
|
@@ -685,7 +574,7 @@ async function runLookup(command, deps = {}) {
|
|
|
685
574
|
}
|
|
686
575
|
if (collection == null) {
|
|
687
576
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
688
|
-
if (authLoader == null) printError(message
|
|
577
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
689
578
|
await finalizeAndExit(1);
|
|
690
579
|
return;
|
|
691
580
|
}
|
|
@@ -747,7 +636,7 @@ async function runLookup(command, deps = {}) {
|
|
|
747
636
|
else {
|
|
748
637
|
spinner.fail(`Failed to complete the traversal for: ${colors.red(url)}.`);
|
|
749
638
|
const hint = getLookupFailureHint(error);
|
|
750
|
-
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message
|
|
639
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the ${optionNames(["-S", "--suppress-errors"])} option to suppress partial errors.`);
|
|
751
640
|
else printLookupFailureHint(authLoader, error);
|
|
752
641
|
}
|
|
753
642
|
await finalizeAndExit(1);
|
|
@@ -782,7 +671,7 @@ async function runLookup(command, deps = {}) {
|
|
|
782
671
|
const url = command.urls[i];
|
|
783
672
|
if (obj == null) {
|
|
784
673
|
spinner.fail(`Failed to fetch ${colors.red(url)}`);
|
|
785
|
-
if (authLoader == null) printError(message
|
|
674
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
786
675
|
success = false;
|
|
787
676
|
} else {
|
|
788
677
|
spinner.succeed(`Fetched object: ${colors.green(url)}`);
|
|
@@ -818,4 +707,4 @@ async function runLookup(command, deps = {}) {
|
|
|
818
707
|
if (success && command.output) spinner.succeed(`Successfully wrote output to ${colors.green(command.output)}.`);
|
|
819
708
|
}
|
|
820
709
|
//#endregion
|
|
821
|
-
export {
|
|
710
|
+
export { runLookup };
|
package/dist/mod.js
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import "@js-temporal/polyfill";
|
|
3
|
-
import
|
|
4
|
-
import "./generate-vocab/mod.js";
|
|
5
|
-
import { runInbox } from "./inbox.js";
|
|
6
|
-
import { runInit } from "./init/mod.js";
|
|
7
|
-
import { runNodeInfo } from "./nodeinfo.js";
|
|
8
|
-
import { runLookup } from "./lookup.js";
|
|
9
|
-
import { runRelay } from "./relay.js";
|
|
10
|
-
import { runTunnel } from "./tunnel.js";
|
|
11
|
-
import runWebFinger from "./webfinger/action.js";
|
|
12
|
-
import "./webfinger/mod.js";
|
|
13
|
-
import { runCli } from "./runner.js";
|
|
3
|
+
import { parseCliProgram } from "./runner.js";
|
|
14
4
|
import process from "node:process";
|
|
15
5
|
//#region src/mod.ts
|
|
16
6
|
async function main() {
|
|
17
|
-
|
|
18
|
-
if (result.command === "init") await runInit(result);
|
|
19
|
-
else if (result.command === "lookup") await runLookup(result);
|
|
20
|
-
else if (result.command === "webfinger") await runWebFinger(result);
|
|
21
|
-
else if (result.command === "inbox") await runInbox(result);
|
|
22
|
-
else if (result.command === "nodeinfo") await runNodeInfo(result);
|
|
23
|
-
else if (result.command === "tunnel") await runTunnel(result);
|
|
24
|
-
else if (result.command === "generate-vocab") await runGenerateVocab(result);
|
|
25
|
-
else if (result.command === "relay") await runRelay(result);
|
|
7
|
+
await (await parseCliProgram(process.argv.slice(2))).run();
|
|
26
8
|
}
|
|
27
9
|
await main();
|
|
28
10
|
//#endregion
|
package/dist/nodeinfo.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { colors, formatObject } from "./utils.js";
|
|
3
2
|
import { configContext } from "./config.js";
|
|
4
3
|
import { userAgentOption } from "./options.js";
|
|
5
|
-
import {
|
|
4
|
+
import { colors, formatObject } from "./utils.js";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
8
|
-
import {
|
|
9
|
-
import { getLogger } from "@logtape/logtape";
|
|
10
|
-
import ora from "ora";
|
|
11
|
-
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
12
|
-
import { bindConfig } from "@optique/config";
|
|
7
|
+
import { print, printError } from "@optique/run";
|
|
13
8
|
import os from "node:os";
|
|
9
|
+
import { bindConfig } from "@optique/config";
|
|
10
|
+
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
11
|
+
import { getNodeInfo } from "@fedify/fedify";
|
|
14
12
|
import { createJimp } from "@jimp/core";
|
|
15
13
|
import webp from "@jimp/wasm-webp";
|
|
14
|
+
import { getLogger } from "@logtape/logtape";
|
|
16
15
|
import { isICO, parseICO } from "icojs";
|
|
17
16
|
import { defaultFormats, defaultPlugins, intToRGBA } from "jimp";
|
|
17
|
+
import { Buffer } from "node:buffer";
|
|
18
|
+
import ora from "ora";
|
|
19
|
+
import { PNG } from "pngjs";
|
|
18
20
|
//#region src/nodeinfo.ts
|
|
19
21
|
const logger = getLogger([
|
|
20
22
|
"fedify",
|
|
@@ -25,6 +27,43 @@ const Jimp = createJimp({
|
|
|
25
27
|
formats: [...defaultFormats, webp],
|
|
26
28
|
plugins: defaultPlugins
|
|
27
29
|
});
|
|
30
|
+
const PNG_SIGNATURE = [
|
|
31
|
+
137,
|
|
32
|
+
80,
|
|
33
|
+
78,
|
|
34
|
+
71,
|
|
35
|
+
13,
|
|
36
|
+
10,
|
|
37
|
+
26,
|
|
38
|
+
10
|
|
39
|
+
];
|
|
40
|
+
function toUint8Array(buffer) {
|
|
41
|
+
return buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
42
|
+
}
|
|
43
|
+
function toNodeBuffer(buffer) {
|
|
44
|
+
return Buffer.from(toUint8Array(buffer));
|
|
45
|
+
}
|
|
46
|
+
function isPng(buffer) {
|
|
47
|
+
const bytes = toUint8Array(buffer);
|
|
48
|
+
return PNG_SIGNATURE.every((byte, index) => bytes[index] === byte);
|
|
49
|
+
}
|
|
50
|
+
async function decodePng(buffer) {
|
|
51
|
+
return await new Promise((resolve, reject) => {
|
|
52
|
+
new PNG().parse(toNodeBuffer(buffer), (error, png) => {
|
|
53
|
+
if (error != null) reject(error);
|
|
54
|
+
else resolve(png);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function readImage(buffer) {
|
|
59
|
+
if (!isPng(buffer)) return await Jimp.read(toNodeBuffer(buffer));
|
|
60
|
+
const png = await decodePng(buffer);
|
|
61
|
+
return new Jimp({
|
|
62
|
+
width: png.width,
|
|
63
|
+
height: png.height,
|
|
64
|
+
data: png.data
|
|
65
|
+
});
|
|
66
|
+
}
|
|
28
67
|
const nodeInfoOption = merge(object("Display options", {
|
|
29
68
|
raw: bindConfig(flag("-r", "--raw", { description: message`Show NodeInfo document in the raw JSON format` }), {
|
|
30
69
|
context: configContext,
|
|
@@ -46,15 +85,17 @@ const nodeInfoOption = merge(object("Display options", {
|
|
|
46
85
|
key: (config) => config.nodeinfo?.bestEffort ?? false,
|
|
47
86
|
default: false
|
|
48
87
|
}) }));
|
|
49
|
-
const
|
|
88
|
+
const nodeInfoOptions = merge(object("Arguments", {
|
|
50
89
|
command: constant("nodeinfo"),
|
|
51
90
|
host: argument(string({ metavar: "HOST" }), { description: message`Bare hostname or a full URL of the instance` })
|
|
52
|
-
}), nodeInfoOption, group("Network options", userAgentOption))
|
|
91
|
+
}), nodeInfoOption, group("Network options", userAgentOption));
|
|
92
|
+
const nodeInfoMetadata = {
|
|
53
93
|
brief: message`Get information about a remote node using the NodeInfo protocol`,
|
|
54
94
|
description: message`Get information about a remote node using the NodeInfo protocol.
|
|
55
95
|
|
|
56
96
|
The argument is the hostname of the remote node, or the URL of the remote node.`
|
|
57
|
-
}
|
|
97
|
+
};
|
|
98
|
+
command("nodeinfo", nodeInfoOptions, nodeInfoMetadata);
|
|
58
99
|
async function runNodeInfo(command) {
|
|
59
100
|
const spinner = ora({
|
|
60
101
|
text: "Fetching a NodeInfo document...",
|
|
@@ -101,7 +142,7 @@ async function runNodeInfo(command) {
|
|
|
101
142
|
if (images.length < 1) throw new Error("No images found in the ICO file.");
|
|
102
143
|
buffer = images[0].buffer;
|
|
103
144
|
}
|
|
104
|
-
layout = getAsciiArt(await
|
|
145
|
+
layout = getAsciiArt(await readImage(buffer), DEFAULT_IMAGE_WIDTH, checkTerminalColorSupport(), colors).split("\n").map((line) => ` ${line} `);
|
|
105
146
|
defaultWidth = 41;
|
|
106
147
|
} else {
|
|
107
148
|
logger.error("Failed to fetch the favicon: {status} {statusText}", {
|
|
@@ -270,4 +311,4 @@ function getAsciiArt(image, width = DEFAULT_IMAGE_WIDTH, colorSupport, colors) {
|
|
|
270
311
|
return art;
|
|
271
312
|
}
|
|
272
313
|
//#endregion
|
|
273
|
-
export { Jimp,
|
|
314
|
+
export { Jimp, nodeInfoMetadata, nodeInfoOptions, runNodeInfo };
|
package/dist/options.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { configContext } from "./config.js";
|
|
3
3
|
import { choice, constant, flag, map, merge, message, object, option, optional, or, string, withDefault } from "@optique/core";
|
|
4
|
-
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
5
4
|
import { bindConfig } from "@optique/config";
|
|
5
|
+
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
6
6
|
//#region src/options.ts
|
|
7
7
|
/**
|
|
8
8
|
* Available tunneling services for exposing local servers to the public internet.
|
package/dist/relay/command.js
CHANGED
|
@@ -5,7 +5,7 @@ import { command, constant, group, integer, merge, message, multiple, object, op
|
|
|
5
5
|
import { bindConfig } from "@optique/config";
|
|
6
6
|
import { choice as choice$1 } from "@optique/core/valueparser";
|
|
7
7
|
//#region src/relay/command.ts
|
|
8
|
-
const
|
|
8
|
+
const relayOptions = merge(object("Relay options", {
|
|
9
9
|
command: constant("relay"),
|
|
10
10
|
protocol: bindConfig(option("-p", "--protocol", choice$1(["mastodon", "litepub"], { metavar: "TYPE" }), { description: message`The relay protocol to use. ${value("mastodon")} for Mastodon-compatible relay, ${value("litepub")} for LitePub-compatible relay.` }), {
|
|
11
11
|
context: configContext,
|
|
@@ -40,11 +40,13 @@ const relayCommand = command("relay", merge(object("Relay options", {
|
|
|
40
40
|
key: (config) => config.relay?.rejectFollow ?? [],
|
|
41
41
|
default: []
|
|
42
42
|
})
|
|
43
|
-
}), group("Tunnel options", createTunnelOption("relay")))
|
|
43
|
+
}), group("Tunnel options", createTunnelOption("relay")));
|
|
44
|
+
const relayMetadata = {
|
|
44
45
|
brief: message`Run an ephemeral ActivityPub relay server.`,
|
|
45
46
|
description: message`Spins up an ActivityPub relay server that forwards activities between federated instances. The server can use either Mastodon or LitePub compatible relay protocol.
|
|
46
47
|
|
|
47
48
|
By default, the server is tunneled to the public internet for external access. Use ${optionName("--no-tunnel")} to run locally only.`
|
|
48
|
-
}
|
|
49
|
+
};
|
|
50
|
+
command("relay", relayOptions, relayMetadata);
|
|
49
51
|
//#endregion
|
|
50
|
-
export {
|
|
52
|
+
export { relayMetadata, relayOptions };
|
package/dist/relay.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
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";
|
|
9
|
-
import Table from "cli-table3";
|
|
10
9
|
import ora from "ora";
|
|
11
|
-
import { createRelay } from "@fedify/relay";
|
|
12
10
|
import { SqliteKvStore } from "@fedify/sqlite";
|
|
13
11
|
import { DatabaseSync } from "node:sqlite";
|
|
12
|
+
import Table from "cli-table3";
|
|
13
|
+
import { createRelay } from "@fedify/relay";
|
|
14
14
|
//#region src/relay.ts
|
|
15
15
|
const logger = getLogger([
|
|
16
16
|
"fedify",
|
package/dist/runner.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import generateVocabCommand from "./generate-vocab/command.js";
|
|
3
|
-
import "./generate-vocab/mod.js";
|
|
4
|
-
import { version } from "./deno.js";
|
|
5
|
-
import { initCommand } from "./init/mod.js";
|
|
6
2
|
import { configContext, tryLoadToml } from "./config.js";
|
|
7
3
|
import { globalOptions } from "./options.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { relayCommand } from "./relay/command.js";
|
|
12
|
-
import { tunnelCommand } from "./tunnel.js";
|
|
13
|
-
import { webFingerCommand } from "./webfinger/command.js";
|
|
14
|
-
import "./webfinger/mod.js";
|
|
15
|
-
import { printError, run } from "@optique/run";
|
|
4
|
+
import { describeError } from "./utils.js";
|
|
5
|
+
import { activityPubCommands, generatingCommands, networkCommands } from "./commands.js";
|
|
6
|
+
import { version } from "./deno.js";
|
|
16
7
|
import process from "node:process";
|
|
17
|
-
import { group, merge, message, or } from "@optique/core";
|
|
18
|
-
import {
|
|
8
|
+
import { command, group, map, merge, message, or } from "@optique/core";
|
|
9
|
+
import { printError, run } from "@optique/run";
|
|
19
10
|
import { merge as merge$1 } from "es-toolkit";
|
|
20
11
|
import { readFileSync } from "node:fs";
|
|
21
|
-
import { parse } from "smol-toml";
|
|
22
12
|
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { parse } from "smol-toml";
|
|
23
15
|
//#region src/runner.ts
|
|
24
16
|
/**
|
|
25
17
|
* Returns the system-wide configuration file paths.
|
|
@@ -39,36 +31,35 @@ function getUserConfigPath() {
|
|
|
39
31
|
if (process.platform === "win32") return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "fedify", "config.toml");
|
|
40
32
|
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "fedify", "config.toml");
|
|
41
33
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} catch (error) {
|
|
52
|
-
printError(message`Could not load config file at ${parsed.configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
-
process.exit(1);
|
|
34
|
+
const selectedCommand = "__fedifyCliSelectedCommand";
|
|
35
|
+
const selectedRun = "__fedifyCliRunCommand";
|
|
36
|
+
function staticCommandParser(staticCommand) {
|
|
37
|
+
if (staticCommand.path.length < 1) throw new TypeError("Static command path must not be empty.");
|
|
38
|
+
let parser = staticCommand.parser;
|
|
39
|
+
for (let i = staticCommand.path.length - 1; i >= 0; i--) {
|
|
40
|
+
const name = staticCommand.path[i];
|
|
41
|
+
if (name == null) throw new TypeError("Static command path contains an empty segment.");
|
|
42
|
+
parser = command(name, parser, i === staticCommand.path.length - 1 ? staticCommand.metadata : void 0);
|
|
54
43
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
44
|
+
const runCommand = staticCommand.run;
|
|
45
|
+
return map(parser, (value) => ({
|
|
46
|
+
...value,
|
|
47
|
+
[selectedCommand]: staticCommand,
|
|
48
|
+
[selectedRun]: (globalOptions) => runCommand({
|
|
49
|
+
...value,
|
|
50
|
+
...globalOptions
|
|
51
|
+
})
|
|
52
|
+
}));
|
|
64
53
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
function staticCommandsParser(commands) {
|
|
55
|
+
const parsers = commands.map(staticCommandParser);
|
|
56
|
+
if (parsers.length < 1) throw new TypeError("Static command group must not be empty.");
|
|
57
|
+
return parsers.length === 1 ? parsers[0] : or(...parsers);
|
|
58
|
+
}
|
|
59
|
+
const runnableCommand = merge(or(group("Generating code", staticCommandsParser(generatingCommands)), group("ActivityPub tools", staticCommandsParser(activityPubCommands)), group("Network tools", staticCommandsParser(networkCommands))), globalOptions);
|
|
60
|
+
map(runnableCommand, ({ [selectedCommand]: _selectedCommand, [selectedRun]: _selectedRun, ...value }) => value);
|
|
61
|
+
function getRunOptions(args) {
|
|
62
|
+
return {
|
|
72
63
|
contexts: [configContext],
|
|
73
64
|
contextOptions: { load: loadConfig },
|
|
74
65
|
programName: "fedify",
|
|
@@ -90,7 +81,41 @@ function runCli(args) {
|
|
|
90
81
|
maxWidth: process.stdout.columns,
|
|
91
82
|
showDefault: true,
|
|
92
83
|
showChoices: true
|
|
93
|
-
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function loadConfig(parsed) {
|
|
87
|
+
if (parsed.ignoreConfig) return void 0;
|
|
88
|
+
const system = getSystemConfigPaths().map(tryLoadToml).reduce((acc, config) => merge$1(acc, config), {});
|
|
89
|
+
const user = tryLoadToml(getUserConfigPath());
|
|
90
|
+
const project = tryLoadToml(join(process.cwd(), ".fedify.toml"));
|
|
91
|
+
let custom = {};
|
|
92
|
+
if (parsed.configPath) try {
|
|
93
|
+
custom = parse(readFileSync(parsed.configPath, "utf-8"));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
printError(message`Could not load config file at ${parsed.configPath}: ${describeError(error)}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
config: [
|
|
100
|
+
system,
|
|
101
|
+
user,
|
|
102
|
+
project,
|
|
103
|
+
custom
|
|
104
|
+
].reduce((acc, config) => merge$1(acc, config), {}),
|
|
105
|
+
meta: void 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function toCliProgram(parsed) {
|
|
109
|
+
const { [selectedCommand]: command, [selectedRun]: runCommand, ...value } = parsed;
|
|
110
|
+
const publicValue = value;
|
|
111
|
+
return {
|
|
112
|
+
command,
|
|
113
|
+
value: publicValue,
|
|
114
|
+
run: () => runCommand(publicValue)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function parseCliProgram(args) {
|
|
118
|
+
return toCliProgram(await run(runnableCommand, getRunOptions(args)));
|
|
94
119
|
}
|
|
95
120
|
//#endregion
|
|
96
|
-
export {
|
|
121
|
+
export { parseCliProgram };
|