@fedify/cli 2.0.0-pr.479.1922 → 2.0.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/LICENSE +1 -1
- package/README.md +3 -3
- package/dist/cache.js +17 -3
- package/dist/config.js +105 -0
- package/dist/deno.js +18 -8
- package/dist/generate-vocab/action.js +1 -1
- package/dist/imagerenderer.js +1 -1
- package/dist/inbox/rendercode.js +11 -21
- package/dist/inbox.js +162 -132
- package/dist/init/mod.js +3 -3
- package/dist/log.js +35 -1
- package/dist/lookup.js +55 -23
- package/dist/mod.js +95 -18
- package/dist/nodeinfo.js +39 -22
- package/dist/options.js +84 -0
- package/dist/relay.js +136 -0
- package/dist/tempserver.js +15 -8
- package/dist/tunnel.js +6 -10
- package/dist/utils.js +19 -108
- package/dist/webfinger/action.js +1 -1
- package/dist/webfinger/command.js +17 -9
- package/dist/webfinger/lib.js +3 -3
- package/package.json +50 -28
- package/deno.json +0 -71
- package/dist/globals.js +0 -49
- package/dist/init/action/configs.js +0 -91
- package/dist/init/action/const.js +0 -10
- package/dist/init/action/deps.js +0 -50
- package/dist/init/action/dir.js +0 -16
- package/dist/init/action/env.js +0 -13
- package/dist/init/action/install.js +0 -20
- package/dist/init/action/mod.js +0 -39
- package/dist/init/action/notice.js +0 -55
- package/dist/init/action/patch.js +0 -147
- package/dist/init/action/precommand.js +0 -28
- package/dist/init/action/recommend.js +0 -24
- package/dist/init/action/set.js +0 -31
- package/dist/init/action/templates.js +0 -58
- package/dist/init/action/utils.js +0 -50
- package/dist/init/ask/dir.js +0 -82
- package/dist/init/ask/kv.js +0 -44
- package/dist/init/ask/mod.js +0 -16
- package/dist/init/ask/mq.js +0 -46
- package/dist/init/ask/pm.js +0 -49
- package/dist/init/ask/wf.js +0 -29
- package/dist/init/command.js +0 -50
- package/dist/init/const.js +0 -31
- package/dist/init/json/biome.js +0 -24
- package/dist/init/json/kv.js +0 -53
- package/dist/init/json/mq.js +0 -72
- package/dist/init/json/pm.js +0 -44
- package/dist/init/json/rt.js +0 -39
- package/dist/init/json/vscode-settings-for-deno.js +0 -53
- package/dist/init/json/vscode-settings.js +0 -49
- package/dist/init/lib.js +0 -136
- package/dist/init/templates/defaults/federation.ts.tpl +0 -23
- package/dist/init/templates/defaults/logging.ts.tpl +0 -23
- package/dist/init/templates/express/app.ts.tpl +0 -16
- package/dist/init/templates/express/index.ts.tpl +0 -6
- package/dist/init/templates/hono/app.tsx.tpl +0 -14
- package/dist/init/templates/hono/index/bun.ts.tpl +0 -10
- package/dist/init/templates/hono/index/deno.ts.tpl +0 -13
- package/dist/init/templates/hono/index/node.ts.tpl +0 -14
- package/dist/init/templates/next/middleware.ts.tpl +0 -45
- package/dist/init/templates/nitro/.env.test.tpl +0 -1
- package/dist/init/templates/nitro/nitro.config.ts.tpl +0 -14
- package/dist/init/templates/nitro/server/error.ts.tpl +0 -3
- package/dist/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
- package/dist/init/test/action.js +0 -17
- package/dist/init/test/create.js +0 -100
- package/dist/init/test/fill.js +0 -32
- package/dist/init/test/lookup.js +0 -190
- package/dist/init/test/run.js +0 -25
- package/dist/init/test/utils.js +0 -17
- package/dist/init/webframeworks.js +0 -136
- package/scripts/pack.ts +0 -71
- package/src/cache.ts +0 -17
- package/src/docloader.ts +0 -67
- package/src/generate-vocab/action.ts +0 -17
- package/src/generate-vocab/command.ts +0 -44
- package/src/generate-vocab/mod.ts +0 -2
- package/src/globals.ts +0 -43
- package/src/imagerenderer.ts +0 -149
- package/src/inbox/entry.ts +0 -10
- package/src/inbox/rendercode.ts +0 -68
- package/src/inbox/view.tsx +0 -598
- package/src/inbox.tsx +0 -536
- package/src/init/action/configs.ts +0 -133
- package/src/init/action/const.ts +0 -9
- package/src/init/action/deps.ts +0 -161
- package/src/init/action/dir.ts +0 -11
- package/src/init/action/env.ts +0 -14
- package/src/init/action/install.ts +0 -24
- package/src/init/action/mod.ts +0 -66
- package/src/init/action/notice.ts +0 -103
- package/src/init/action/patch.ts +0 -233
- package/src/init/action/precommand.ts +0 -29
- package/src/init/action/recommend.ts +0 -38
- package/src/init/action/set.ts +0 -65
- package/src/init/action/templates.ts +0 -96
- package/src/init/action/utils.ts +0 -64
- package/src/init/ask/dir.ts +0 -98
- package/src/init/ask/kv.ts +0 -82
- package/src/init/ask/mod.ts +0 -23
- package/src/init/ask/mq.ts +0 -86
- package/src/init/ask/pm.ts +0 -58
- package/src/init/ask/wf.ts +0 -27
- package/src/init/command.ts +0 -135
- package/src/init/const.ts +0 -4
- package/src/init/json/biome.json +0 -17
- package/src/init/json/kv.json +0 -39
- package/src/init/json/mq.json +0 -95
- package/src/init/json/pm.json +0 -47
- package/src/init/json/rt.json +0 -42
- package/src/init/json/vscode-settings-for-deno.json +0 -43
- package/src/init/json/vscode-settings.json +0 -41
- package/src/init/lib.ts +0 -223
- package/src/init/mod.ts +0 -3
- package/src/init/templates/defaults/federation.ts.tpl +0 -23
- package/src/init/templates/defaults/logging.ts.tpl +0 -23
- package/src/init/templates/express/app.ts.tpl +0 -16
- package/src/init/templates/express/index.ts.tpl +0 -6
- package/src/init/templates/hono/app.tsx.tpl +0 -14
- package/src/init/templates/hono/index/bun.ts.tpl +0 -10
- package/src/init/templates/hono/index/deno.ts.tpl +0 -13
- package/src/init/templates/hono/index/node.ts.tpl +0 -14
- package/src/init/templates/next/middleware.ts.tpl +0 -45
- package/src/init/templates/nitro/.env.test.tpl +0 -1
- package/src/init/templates/nitro/nitro.config.ts.tpl +0 -14
- package/src/init/templates/nitro/server/error.ts.tpl +0 -3
- package/src/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
- package/src/init/test/action.ts +0 -28
- package/src/init/test/create.ts +0 -137
- package/src/init/test/fill.ts +0 -67
- package/src/init/test/lookup.ts +0 -254
- package/src/init/test/run.ts +0 -39
- package/src/init/test/types.ts +0 -27
- package/src/init/test/utils.ts +0 -21
- package/src/init/types.ts +0 -89
- package/src/init/webframeworks.ts +0 -168
- package/src/kv.bun.ts +0 -12
- package/src/kv.node.ts +0 -11
- package/src/log.ts +0 -64
- package/src/lookup.test.ts +0 -182
- package/src/lookup.ts +0 -563
- package/src/mod.ts +0 -62
- package/src/nodeinfo.test.ts +0 -229
- package/src/nodeinfo.ts +0 -454
- package/src/table.ts +0 -17
- package/src/tempserver.ts +0 -87
- package/src/tunnel.test.ts +0 -157
- package/src/tunnel.ts +0 -94
- package/src/utils.ts +0 -254
- package/src/webfinger/action.ts +0 -50
- package/src/webfinger/command.ts +0 -64
- package/src/webfinger/error.ts +0 -47
- package/src/webfinger/lib.ts +0 -37
- package/src/webfinger/mod.test.ts +0 -79
- package/src/webfinger/mod.ts +0 -2
- package/tsdown.config.ts +0 -35
package/dist/log.js
CHANGED
|
@@ -47,6 +47,40 @@ await configure({
|
|
|
47
47
|
contextLocalStorage: new AsyncLocalStorage(),
|
|
48
48
|
reset: true
|
|
49
49
|
});
|
|
50
|
+
async function configureLogging() {
|
|
51
|
+
const logFile$1 = process.env["FEDIFY_LOG_FILE"];
|
|
52
|
+
await configure({
|
|
53
|
+
sinks: {
|
|
54
|
+
console: getConsoleSink(),
|
|
55
|
+
recording: recordingSink,
|
|
56
|
+
file: logFile$1 == null ? () => void 0 : getFileSink(logFile$1)
|
|
57
|
+
},
|
|
58
|
+
filters: {},
|
|
59
|
+
loggers: [
|
|
60
|
+
{
|
|
61
|
+
category: "fedify",
|
|
62
|
+
lowestLevel: "debug",
|
|
63
|
+
sinks: [
|
|
64
|
+
"console",
|
|
65
|
+
"recording",
|
|
66
|
+
"file"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
category: "localtunnel",
|
|
71
|
+
lowestLevel: "debug",
|
|
72
|
+
sinks: ["console", "file"]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
category: ["logtape", "meta"],
|
|
76
|
+
lowestLevel: "warning",
|
|
77
|
+
sinks: ["console", "file"]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
reset: true,
|
|
81
|
+
contextLocalStorage: new AsyncLocalStorage()
|
|
82
|
+
});
|
|
83
|
+
}
|
|
50
84
|
|
|
51
85
|
//#endregion
|
|
52
|
-
export { recordingSink };
|
|
86
|
+
export { configureLogging, recordingSink };
|
package/dist/lookup.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
|
+
import { configContext } from "./config.js";
|
|
4
5
|
import { getContextLoader, getDocumentLoader } from "./docloader.js";
|
|
5
|
-
import { configureLogging
|
|
6
|
+
import { configureLogging } from "./log.js";
|
|
7
|
+
import { createTunnelServiceOption, userAgentOption } from "./options.js";
|
|
6
8
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
7
9
|
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
8
10
|
import { renderImages } from "./imagerenderer.js";
|
|
11
|
+
import process from "node:process";
|
|
9
12
|
import { argument, choice, command, constant, flag, float, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
10
13
|
import { path, print, printError } from "@optique/run";
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
14
|
+
import { createWriteStream } from "node:fs";
|
|
15
|
+
import { bindConfig } from "@optique/config";
|
|
16
|
+
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
17
|
+
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
13
18
|
import { getLogger } from "@logtape/logtape";
|
|
14
19
|
import ora from "ora";
|
|
15
|
-
import { createWriteStream } from "node:fs";
|
|
16
20
|
|
|
17
21
|
//#region src/lookup.ts
|
|
18
22
|
const logger = getLogger([
|
|
@@ -20,28 +24,56 @@ const logger = getLogger([
|
|
|
20
24
|
"cli",
|
|
21
25
|
"lookup"
|
|
22
26
|
]);
|
|
23
|
-
const authorizedFetchOption = withDefault(object({
|
|
24
|
-
authorizedFetch: flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
28
|
+
authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }), () => true), {
|
|
29
|
+
context: configContext,
|
|
30
|
+
key: (config) => config.lookup?.authorizedFetch ? true : void 0
|
|
31
|
+
}),
|
|
32
|
+
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.` }), {
|
|
33
|
+
context: configContext,
|
|
34
|
+
key: (config) => config.lookup?.firstKnock ?? "draft-cavage-http-signatures-12",
|
|
35
|
+
default: "draft-cavage-http-signatures-12"
|
|
36
|
+
}),
|
|
37
|
+
tunnelService: createTunnelServiceOption()
|
|
38
|
+
}), {
|
|
39
|
+
authorizedFetch: false,
|
|
40
|
+
firstKnock: void 0,
|
|
41
|
+
tunnelService: void 0
|
|
42
|
+
});
|
|
43
|
+
const traverseOption = object("Traverse options", {
|
|
44
|
+
traverse: bindConfig(flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }), {
|
|
45
|
+
context: configContext,
|
|
46
|
+
key: (config) => config.lookup?.traverse ?? false,
|
|
47
|
+
default: false
|
|
48
|
+
}),
|
|
49
|
+
suppressErrors: bindConfig(flag("-S", "--suppress-errors", { description: message`Suppress partial errors while traversing the collection.` }), {
|
|
50
|
+
context: configContext,
|
|
51
|
+
key: (config) => config.lookup?.suppressErrors ?? false,
|
|
52
|
+
default: false
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
const lookupCommand = command("lookup", merge(object({ command: constant("lookup") }), traverseOption, authorizedFetchOption, merge("Network options", userAgentOption, object({ timeout: optional(bindConfig(option("-T", "--timeout", float({
|
|
56
|
+
min: 0,
|
|
57
|
+
metavar: "SECONDS"
|
|
58
|
+
}), { description: message`Set timeout for network requests in seconds.` }), {
|
|
59
|
+
context: configContext,
|
|
60
|
+
key: (config) => config.lookup?.timeout
|
|
61
|
+
})) })), 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", {
|
|
62
|
+
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"))), {
|
|
63
|
+
context: configContext,
|
|
64
|
+
key: (config) => config.lookup?.defaultFormat ?? "default",
|
|
65
|
+
default: "default"
|
|
66
|
+
}),
|
|
67
|
+
separator: bindConfig(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message`Specify the separator between adjacent output objects or collection items.` }), {
|
|
68
|
+
context: configContext,
|
|
69
|
+
key: (config) => config.lookup?.separator ?? "----",
|
|
70
|
+
default: "----"
|
|
71
|
+
}),
|
|
36
72
|
output: optional(option("-o", "--output", path({
|
|
37
73
|
metavar: "OUTPUT_PATH",
|
|
38
74
|
type: "file",
|
|
39
75
|
allowCreate: true
|
|
40
|
-
}), { description: message`Specify the output file path.` }))
|
|
41
|
-
timeout: optional(option("-T", "--timeout", float({
|
|
42
|
-
min: 0,
|
|
43
|
-
metavar: "SECONDS"
|
|
44
|
-
}), { description: message`Set timeout for network requests in seconds.` }))
|
|
76
|
+
}), { description: message`Specify the output file path.` }))
|
|
45
77
|
})), {
|
|
46
78
|
brief: message`Look up Activity Streams objects.`,
|
|
47
79
|
description: message`Look up Activity Streams objects by URL or actor handle.
|
|
@@ -172,7 +204,7 @@ async function runLookup(command$1) {
|
|
|
172
204
|
inbox: new URL("/inbox", serverUrl),
|
|
173
205
|
outbox: new URL("/outbox", serverUrl)
|
|
174
206
|
}), { contextLoader });
|
|
175
|
-
});
|
|
207
|
+
}, { service: command$1.tunnelService });
|
|
176
208
|
const baseAuthLoader = getAuthenticatedDocumentLoader({
|
|
177
209
|
keyId: new URL("#main-key", server.url),
|
|
178
210
|
privateKey: key.privateKey
|
package/dist/mod.js
CHANGED
|
@@ -1,39 +1,116 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
|
|
3
3
|
import { Temporal } from "@js-temporal/polyfill";
|
|
4
4
|
|
|
5
|
+
import { configContext, tryLoadToml } from "./config.js";
|
|
5
6
|
import { runGenerateVocab } from "./generate-vocab/action.js";
|
|
6
7
|
import command_default from "./generate-vocab/command.js";
|
|
7
8
|
import "./generate-vocab/mod.js";
|
|
9
|
+
import deno_default from "./deno.js";
|
|
10
|
+
import { globalOptions } from "./options.js";
|
|
8
11
|
import { inboxCommand, runInbox } from "./inbox.js";
|
|
9
|
-
import
|
|
10
|
-
import { initCommand, testInitCommand } from "./init/command.js";
|
|
11
|
-
import action_default$1 from "./init/test/action.js";
|
|
12
|
-
import "./init/mod.js";
|
|
12
|
+
import { initCommand, runInit } from "./init/mod.js";
|
|
13
13
|
import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.js";
|
|
14
14
|
import { lookupCommand, runLookup } from "./lookup.js";
|
|
15
|
+
import { relayCommand, runRelay } from "./relay.js";
|
|
15
16
|
import { runTunnel, tunnelCommand } from "./tunnel.js";
|
|
16
17
|
import { runWebFinger } from "./webfinger/action.js";
|
|
17
18
|
import { webFingerCommand } from "./webfinger/command.js";
|
|
18
19
|
import "./webfinger/mod.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import process from "node:process";
|
|
23
|
+
import { runWithConfig } from "@optique/config/run";
|
|
24
|
+
import { group, merge, message, or } from "@optique/core";
|
|
25
|
+
import { printError } from "@optique/run";
|
|
26
|
+
import { merge as merge$1 } from "es-toolkit";
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
import { parse } from "smol-toml";
|
|
21
29
|
|
|
22
30
|
//#region src/mod.ts
|
|
23
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Returns the system-wide configuration file paths.
|
|
33
|
+
* - Linux/macOS: Searches `$XDG_CONFIG_DIRS` (default: /etc/xdg)
|
|
34
|
+
* - Windows: Uses `%ProgramData%` (default: C:\ProgramData)
|
|
35
|
+
*/
|
|
36
|
+
function getSystemConfigPaths() {
|
|
37
|
+
if (process.platform === "win32") {
|
|
38
|
+
const programData = process.env.ProgramData || "C:\\ProgramData";
|
|
39
|
+
return [join(programData, "fedify", "config.toml")];
|
|
40
|
+
}
|
|
41
|
+
return (process.env.XDG_CONFIG_DIRS || "/etc/xdg").split(":").map((dir) => join(dir, "fedify", "config.toml"));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns the user-level configuration file path.
|
|
45
|
+
* - Linux/macOS: `$XDG_CONFIG_HOME/fedify/config.toml` (default: ~/.config)
|
|
46
|
+
* - Windows: `%APPDATA%\fedify\config.toml`
|
|
47
|
+
*/
|
|
48
|
+
function getUserConfigPath() {
|
|
49
|
+
if (process.platform === "win32") {
|
|
50
|
+
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
|
51
|
+
return join(appData, "fedify", "config.toml");
|
|
52
|
+
}
|
|
53
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
54
|
+
return join(xdgConfigHome, "fedify", "config.toml");
|
|
55
|
+
}
|
|
56
|
+
const command$1 = merge(or(group("Generating code", or(initCommand, command_default)), group("ActivityPub tools", or(webFingerCommand, lookupCommand, inboxCommand, nodeInfoCommand, relayCommand)), group("Network tools", tunnelCommand)), globalOptions);
|
|
24
57
|
async function main() {
|
|
25
|
-
const result =
|
|
58
|
+
const result = await runWithConfig(command$1, configContext, {
|
|
26
59
|
programName: "fedify",
|
|
27
|
-
|
|
60
|
+
load: (parsed) => {
|
|
61
|
+
if (parsed.ignoreConfig) return {};
|
|
62
|
+
const systemConfigs = getSystemConfigPaths().map(tryLoadToml);
|
|
63
|
+
const system = systemConfigs.reduce((acc, config) => merge$1(acc, config), {});
|
|
64
|
+
const user = tryLoadToml(getUserConfigPath());
|
|
65
|
+
const project = tryLoadToml(join(process.cwd(), ".fedify.toml"));
|
|
66
|
+
let custom = {};
|
|
67
|
+
if (parsed.configPath) try {
|
|
68
|
+
custom = parse(readFileSync(parsed.configPath, "utf-8"));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
printError(message`Could not load config file at ${parsed.configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
return [
|
|
74
|
+
system,
|
|
75
|
+
user,
|
|
76
|
+
project,
|
|
77
|
+
custom
|
|
78
|
+
].reduce((acc, config) => merge$1(acc, config), {});
|
|
79
|
+
},
|
|
80
|
+
args: process.argv.slice(2),
|
|
81
|
+
help: {
|
|
82
|
+
mode: "both",
|
|
83
|
+
onShow: () => process.exit(0),
|
|
84
|
+
group: "Meta commands"
|
|
85
|
+
},
|
|
86
|
+
version: {
|
|
87
|
+
mode: "both",
|
|
88
|
+
value: deno_default.version,
|
|
89
|
+
group: "Meta commands"
|
|
90
|
+
},
|
|
91
|
+
completion: {
|
|
92
|
+
mode: "command",
|
|
93
|
+
name: "both",
|
|
94
|
+
helpVisibility: "plural",
|
|
95
|
+
group: "Meta commands"
|
|
96
|
+
},
|
|
97
|
+
onError: () => process.exit(1),
|
|
98
|
+
colors: process.stdout.isTTY && (process.env.NO_COLOR == null || process.env.NO_COLOR === ""),
|
|
99
|
+
maxWidth: process.stdout.columns,
|
|
100
|
+
showDefault: true,
|
|
101
|
+
showChoices: true
|
|
28
102
|
});
|
|
29
|
-
if (result.command === "init") await
|
|
30
|
-
if (result.command === "lookup") await runLookup(result);
|
|
31
|
-
if (result.command === "webfinger") await runWebFinger(result);
|
|
32
|
-
if (result.command === "inbox") runInbox(result);
|
|
33
|
-
if (result.command === "nodeinfo") runNodeInfo(result);
|
|
34
|
-
if (result.command === "tunnel") await runTunnel(result);
|
|
35
|
-
if (result.command === "generate-vocab") await runGenerateVocab(result);
|
|
36
|
-
if (result.command === "
|
|
103
|
+
if (result.command === "init") await runInit(result);
|
|
104
|
+
else if (result.command === "lookup") await runLookup(result);
|
|
105
|
+
else if (result.command === "webfinger") await runWebFinger(result);
|
|
106
|
+
else if (result.command === "inbox") runInbox(result);
|
|
107
|
+
else if (result.command === "nodeinfo") runNodeInfo(result);
|
|
108
|
+
else if (result.command === "tunnel") await runTunnel(result);
|
|
109
|
+
else if (result.command === "generate-vocab") await runGenerateVocab(result);
|
|
110
|
+
else if (result.command === "relay") await runRelay(result);
|
|
111
|
+
else {
|
|
112
|
+
const _exhaustiveCheck = result;
|
|
113
|
+
}
|
|
37
114
|
}
|
|
38
115
|
await main();
|
|
39
116
|
|
package/dist/nodeinfo.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { configContext } from "./config.js";
|
|
5
|
+
import { userAgentOption } from "./options.js";
|
|
5
6
|
import { colors, formatObject } from "./utils.js";
|
|
6
|
-
import
|
|
7
|
-
import { print, printError } from "@optique/run";
|
|
7
|
+
import os from "node:os";
|
|
8
8
|
import process from "node:process";
|
|
9
|
+
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
10
|
+
import { print, printError } from "@optique/run";
|
|
11
|
+
import { bindConfig } from "@optique/config";
|
|
9
12
|
import { getNodeInfo } from "@fedify/fedify";
|
|
10
13
|
import { getLogger } from "@logtape/logtape";
|
|
11
14
|
import ora from "ora";
|
|
12
15
|
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
13
|
-
import os from "node:os";
|
|
14
16
|
import { createJimp } from "@jimp/core";
|
|
15
17
|
import webp from "@jimp/wasm-webp";
|
|
16
18
|
import { isICO, parseICO } from "icojs";
|
|
@@ -26,16 +28,31 @@ const Jimp = createJimp({
|
|
|
26
28
|
formats: [...defaultFormats, webp],
|
|
27
29
|
plugins: defaultPlugins
|
|
28
30
|
});
|
|
29
|
-
const nodeInfoOption =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const nodeInfoOption = merge(object("Display options", {
|
|
32
|
+
raw: bindConfig(flag("-r", "--raw", { description: message`Show NodeInfo document in the raw JSON format` }), {
|
|
33
|
+
context: configContext,
|
|
34
|
+
key: (config) => config.nodeinfo?.raw ?? false,
|
|
35
|
+
default: false
|
|
36
|
+
}),
|
|
37
|
+
noFavicon: bindConfig(flag("--no-favicon", { description: message`Disable fetching the favicon of the instance` }), {
|
|
38
|
+
context: configContext,
|
|
39
|
+
key: (config) => config.nodeinfo?.showFavicon === false,
|
|
40
|
+
default: false
|
|
41
|
+
}),
|
|
42
|
+
metadata: bindConfig(flag("-m", "--metadata", { description: message`Show the extra metadata of the NodeInfo, i.e., the metadata field of the document.` }), {
|
|
43
|
+
context: configContext,
|
|
44
|
+
key: (config) => config.nodeinfo?.showMetadata ?? false,
|
|
45
|
+
default: false
|
|
46
|
+
})
|
|
47
|
+
}), object("Parsing options", { bestEffort: bindConfig(flag("-b", "--best-effort", { description: message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.` }), {
|
|
48
|
+
context: configContext,
|
|
49
|
+
key: (config) => config.nodeinfo?.bestEffort ?? false,
|
|
50
|
+
default: false
|
|
51
|
+
}) }));
|
|
52
|
+
const nodeInfoCommand = command("nodeinfo", merge(object("Arguments", {
|
|
36
53
|
command: constant("nodeinfo"),
|
|
37
54
|
host: argument(string({ metavar: "HOST" }), { description: message`Bare hostname or a full URL of the instance` })
|
|
38
|
-
}),
|
|
55
|
+
}), nodeInfoOption, group("Network options", userAgentOption)), {
|
|
39
56
|
brief: message`Get information about a remote node using the NodeInfo protocol`,
|
|
40
57
|
description: message`Get information about a remote node using the NodeInfo protocol.
|
|
41
58
|
|
|
@@ -47,7 +64,7 @@ async function runNodeInfo(command$1) {
|
|
|
47
64
|
discardStdin: false
|
|
48
65
|
}).start();
|
|
49
66
|
const url = new URL(URL.canParse(command$1.host) ? command$1.host : `https://${command$1.host}`);
|
|
50
|
-
if (
|
|
67
|
+
if (command$1.raw) {
|
|
51
68
|
const nodeInfo$1 = await getNodeInfo(url, {
|
|
52
69
|
parse: "none",
|
|
53
70
|
userAgent: command$1.userAgent
|
|
@@ -62,19 +79,19 @@ async function runNodeInfo(command$1) {
|
|
|
62
79
|
return;
|
|
63
80
|
}
|
|
64
81
|
const nodeInfo = await getNodeInfo(url, {
|
|
65
|
-
parse:
|
|
82
|
+
parse: command$1.bestEffort ? "best-effort" : "strict",
|
|
66
83
|
userAgent: command$1.userAgent
|
|
67
84
|
});
|
|
68
85
|
logger.debug("NodeInfo document: {nodeInfo}", { nodeInfo });
|
|
69
86
|
if (nodeInfo == void 0) {
|
|
70
87
|
spinner.fail("No NodeInfo document found or it is invalid.");
|
|
71
88
|
printError(message`No NodeInfo document found or it is invalid.`);
|
|
72
|
-
if (!
|
|
89
|
+
if (!command$1.bestEffort) printError(message`Use the -b/--best-effort option to try to parse the document anyway.`);
|
|
73
90
|
process.exit(1);
|
|
74
91
|
}
|
|
75
92
|
let layout;
|
|
76
93
|
let defaultWidth = 0;
|
|
77
|
-
if (!
|
|
94
|
+
if (!command$1.noFavicon) {
|
|
78
95
|
spinner.text = "Fetching the favicon...";
|
|
79
96
|
try {
|
|
80
97
|
const faviconUrl = await getFaviconUrl(url, command$1.userAgent);
|
|
@@ -147,9 +164,9 @@ async function runNodeInfo(command$1) {
|
|
|
147
164
|
layout[next()] += colors.bold(colors.dim("Open registrations:"));
|
|
148
165
|
layout[next()] += " " + (nodeInfo.openRegistrations ? "Yes" : "No");
|
|
149
166
|
}
|
|
150
|
-
if (
|
|
167
|
+
if (command$1.metadata && nodeInfo.metadata != null && Object.keys(nodeInfo.metadata).length > 0) {
|
|
151
168
|
layout[next()] += colors.bold(colors.dim("Metadata:"));
|
|
152
|
-
for (const [key, value] of Object.entries(nodeInfo.metadata)) layout[next()] += ` ${colors.dim(key + ":")} ${indent(typeof value === "string" ? value : formatObject(value), defaultWidth + 4 + key.length)}`;
|
|
169
|
+
for (const [key, value$1] of Object.entries(nodeInfo.metadata)) layout[next()] += ` ${colors.dim(key + ":")} ${indent(typeof value$1 === "string" ? value$1 : formatObject(value$1), defaultWidth + 4 + key.length)}`;
|
|
153
170
|
}
|
|
154
171
|
console.log(layout.join("\n"));
|
|
155
172
|
}
|
|
@@ -164,8 +181,8 @@ async function getFaviconUrl(url, userAgent) {
|
|
|
164
181
|
for (const match of text$1.matchAll(LINK_REGEXP)) {
|
|
165
182
|
const attrs = {};
|
|
166
183
|
for (const attrMatch of match[1].matchAll(LINK_ATTRS_REGEXP)) {
|
|
167
|
-
const [, key, value] = attrMatch;
|
|
168
|
-
attrs[key] = value.startsWith("\"") || value.startsWith("'") ? value.slice(1, -1) : value;
|
|
184
|
+
const [, key, value$1] = attrMatch;
|
|
185
|
+
attrs[key] = value$1.startsWith("\"") || value$1.startsWith("'") ? value$1.slice(1, -1) : value$1;
|
|
169
186
|
}
|
|
170
187
|
const rel = attrs.rel?.toLowerCase()?.trim()?.split(/\s+/) ?? [];
|
|
171
188
|
if (!rel.includes("icon") && !rel.includes("apple-touch-icon")) continue;
|
|
@@ -203,11 +220,11 @@ const CUBE_VALUES = [
|
|
|
203
220
|
215,
|
|
204
221
|
255
|
|
205
222
|
];
|
|
206
|
-
const findClosestIndex = (value) => {
|
|
223
|
+
const findClosestIndex = (value$1) => {
|
|
207
224
|
let minDiff = Infinity;
|
|
208
225
|
let closestIndex = 0;
|
|
209
226
|
for (let idx = 0; idx < CUBE_VALUES.length; idx++) {
|
|
210
|
-
const diff = Math.abs(value - CUBE_VALUES[idx]);
|
|
227
|
+
const diff = Math.abs(value$1 - CUBE_VALUES[idx]);
|
|
211
228
|
if (diff < minDiff) {
|
|
212
229
|
minDiff = diff;
|
|
213
230
|
closestIndex = idx;
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { configContext } from "./config.js";
|
|
5
|
+
import { choice, constant, flag, map, merge, message, object, option, or, string, withDefault } from "@optique/core";
|
|
6
|
+
import { bindConfig } from "@optique/config";
|
|
7
|
+
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
8
|
+
|
|
9
|
+
//#region src/options.ts
|
|
10
|
+
/**
|
|
11
|
+
* Available tunneling services for exposing local servers to the public internet.
|
|
12
|
+
*/
|
|
13
|
+
const TUNNEL_SERVICES = [
|
|
14
|
+
"localhost.run",
|
|
15
|
+
"serveo.net",
|
|
16
|
+
"pinggy.io"
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Creates a tunnel service option with customizable option names.
|
|
20
|
+
*/
|
|
21
|
+
function createTunnelServiceOption(optionNames$1 = ["--tunnel-service"]) {
|
|
22
|
+
return withDefault(bindConfig(option(...optionNames$1, choice(TUNNEL_SERVICES, { metavar: "SERVICE" }), { description: message`The tunneling service to use.
|
|
23
|
+
By default, any of the supported tunneling services will be used
|
|
24
|
+
(randomly selected for each tunnel).` }), {
|
|
25
|
+
context: configContext,
|
|
26
|
+
key: (config) => config.tunnelService
|
|
27
|
+
}), void 0);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates a tunnel option that binds to a specific config section's noTunnel field.
|
|
31
|
+
* Use this when tunneling can be disabled (e.g., in `inbox` and `relay`).
|
|
32
|
+
*
|
|
33
|
+
* @param section - The config section to read noTunnel from ("inbox" or "relay")
|
|
34
|
+
* @returns An option object with `tunnel` (boolean) and `tunnelService` fields
|
|
35
|
+
*/
|
|
36
|
+
function createTunnelOption(section) {
|
|
37
|
+
return object({
|
|
38
|
+
tunnel: bindConfig(withDefault(map(flag("-T", "--no-tunnel", { description: message`Do not tunnel the server to the public Internet.` }), () => false), true), {
|
|
39
|
+
context: configContext,
|
|
40
|
+
key: (config) => !(config[section]?.noTunnel ?? false),
|
|
41
|
+
default: true
|
|
42
|
+
}),
|
|
43
|
+
tunnelService: createTunnelServiceOption()
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const debugOption = object({ debug: bindConfig(option("-d", "--debug", { description: message`Enable debug mode.` }), {
|
|
47
|
+
context: configContext,
|
|
48
|
+
key: (config) => config.debug ?? false,
|
|
49
|
+
default: false
|
|
50
|
+
}) });
|
|
51
|
+
const userAgentOption = object({ userAgent: bindConfig(option("-u", "--user-agent", string({ metavar: "USER_AGENT" }), { description: message`The custom User-Agent header value.` }), {
|
|
52
|
+
context: configContext,
|
|
53
|
+
key: (config) => config.userAgent ?? getUserAgent(),
|
|
54
|
+
default: getUserAgent()
|
|
55
|
+
}) });
|
|
56
|
+
/**
|
|
57
|
+
* Configuration file options.
|
|
58
|
+
*
|
|
59
|
+
* These options are mutually exclusive:
|
|
60
|
+
* - `--config PATH` loads an additional config file on top of standard hierarchy
|
|
61
|
+
* - `--ignore-config` skips all config files (useful for CI reproducibility)
|
|
62
|
+
*
|
|
63
|
+
* Returns either:
|
|
64
|
+
* - `{ ignoreConfig: true }` when `--ignore-config` is specified
|
|
65
|
+
* - `{ ignoreConfig: false, configPath: string }` when `--config` is specified
|
|
66
|
+
* - `{ ignoreConfig: false }` when neither is specified (default)
|
|
67
|
+
*/
|
|
68
|
+
const configOption = withDefault(or(object({ ignoreConfig: map(flag("--ignore-config", { description: message`Ignore all configuration files.` }), () => true) }), object({
|
|
69
|
+
ignoreConfig: constant(false),
|
|
70
|
+
configPath: option("--config", string({ metavar: "PATH" }), { description: message`Load an additional configuration file.` })
|
|
71
|
+
})), {
|
|
72
|
+
ignoreConfig: false,
|
|
73
|
+
configPath: void 0
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Global options that apply to all commands.
|
|
77
|
+
*
|
|
78
|
+
* Combines debug mode and configuration file options into a single
|
|
79
|
+
* "Global options" group.
|
|
80
|
+
*/
|
|
81
|
+
const globalOptions = merge("Global options", debugOption, configOption);
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { createTunnelOption, createTunnelServiceOption, globalOptions, userAgentOption };
|
package/dist/relay.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { configContext } from "./config.js";
|
|
5
|
+
import { configureLogging } from "./log.js";
|
|
6
|
+
import { createTunnelOption } from "./options.js";
|
|
7
|
+
import { tableStyle } from "./table.js";
|
|
8
|
+
import { spawnTemporaryServer } from "./tempserver.js";
|
|
9
|
+
import { colors, matchesActor } from "./utils.js";
|
|
10
|
+
import { SqliteKvStore } from "@fedify/sqlite";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
import { DatabaseSync } from "node:sqlite";
|
|
13
|
+
import { command, constant, group, integer, merge, message, multiple, object, option, optionName, optional, string, value } from "@optique/core";
|
|
14
|
+
import { bindConfig } from "@optique/config";
|
|
15
|
+
import { MemoryKvStore } from "@fedify/fedify";
|
|
16
|
+
import { getLogger } from "@logtape/logtape";
|
|
17
|
+
import Table from "cli-table3";
|
|
18
|
+
import ora from "ora";
|
|
19
|
+
import { createRelay } from "@fedify/relay";
|
|
20
|
+
import { choice as choice$1 } from "@optique/core/valueparser";
|
|
21
|
+
|
|
22
|
+
//#region src/relay.ts
|
|
23
|
+
const logger = getLogger([
|
|
24
|
+
"fedify",
|
|
25
|
+
"cli",
|
|
26
|
+
"relay"
|
|
27
|
+
]);
|
|
28
|
+
const relayCommand = command("relay", merge(object("Relay options", {
|
|
29
|
+
command: constant("relay"),
|
|
30
|
+
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.` }), {
|
|
31
|
+
context: configContext,
|
|
32
|
+
key: (config) => config.relay?.protocol ?? "mastodon",
|
|
33
|
+
default: "mastodon"
|
|
34
|
+
}),
|
|
35
|
+
persistent: optional(bindConfig(option("--persistent", string({ metavar: "PATH" }), { description: message`Path to SQLite database file for persistent storage. If not specified, uses in-memory storage which is lost when the server stops.` }), {
|
|
36
|
+
context: configContext,
|
|
37
|
+
key: (config) => config.relay?.persistent
|
|
38
|
+
})),
|
|
39
|
+
port: bindConfig(option("-P", "--port", integer({
|
|
40
|
+
min: 0,
|
|
41
|
+
max: 65535,
|
|
42
|
+
metavar: "PORT"
|
|
43
|
+
}), { description: message`The local port to listen on.` }), {
|
|
44
|
+
context: configContext,
|
|
45
|
+
key: (config) => config.relay?.port ?? 8e3,
|
|
46
|
+
default: 8e3
|
|
47
|
+
}),
|
|
48
|
+
name: bindConfig(option("-n", "--name", string({ metavar: "NAME" }), { description: message`The relay display name.` }), {
|
|
49
|
+
context: configContext,
|
|
50
|
+
key: (config) => config.relay?.name ?? "Fedify Relay",
|
|
51
|
+
default: "Fedify Relay"
|
|
52
|
+
}),
|
|
53
|
+
acceptFollow: bindConfig(multiple(option("-a", "--accept-follow", string({ metavar: "URI" }), { description: message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.` })), {
|
|
54
|
+
context: configContext,
|
|
55
|
+
key: (config) => config.relay?.acceptFollow ?? [],
|
|
56
|
+
default: []
|
|
57
|
+
}),
|
|
58
|
+
rejectFollow: bindConfig(multiple(option("-r", "--reject-follow", string({ metavar: "URI" }), { description: message`Reject follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be rejected.` })), {
|
|
59
|
+
context: configContext,
|
|
60
|
+
key: (config) => config.relay?.rejectFollow ?? [],
|
|
61
|
+
default: []
|
|
62
|
+
})
|
|
63
|
+
}), group("Tunnel options", createTunnelOption("relay"))), {
|
|
64
|
+
brief: message`Run an ephemeral ActivityPub relay server.`,
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
By default, the server is tunneled to the public internet for external access. Use ${optionName("--no-tunnel")} to run locally only.`
|
|
68
|
+
});
|
|
69
|
+
async function runRelay(command$1) {
|
|
70
|
+
if (command$1.debug) await configureLogging();
|
|
71
|
+
const spinner = ora({
|
|
72
|
+
text: "Starting relay server...",
|
|
73
|
+
discardStdin: false
|
|
74
|
+
}).start();
|
|
75
|
+
let kv;
|
|
76
|
+
if (command$1.persistent) {
|
|
77
|
+
logger.debug("Using SQLite storage at {path}.", { path: command$1.persistent });
|
|
78
|
+
const db = new DatabaseSync(command$1.persistent);
|
|
79
|
+
kv = new SqliteKvStore(db);
|
|
80
|
+
} else {
|
|
81
|
+
logger.debug("Using in-memory storage.");
|
|
82
|
+
kv = new MemoryKvStore();
|
|
83
|
+
}
|
|
84
|
+
let relay;
|
|
85
|
+
let server = null;
|
|
86
|
+
const acceptFollows = [];
|
|
87
|
+
const rejectFollows = [];
|
|
88
|
+
if (command$1.acceptFollow != null && command$1.acceptFollow.length > 0) acceptFollows.push(...command$1.acceptFollow ?? []);
|
|
89
|
+
if (command$1.rejectFollow != null && command$1.rejectFollow.length > 0) rejectFollows.push(...command$1.rejectFollow ?? []);
|
|
90
|
+
server = await spawnTemporaryServer(async (request) => {
|
|
91
|
+
return await relay.fetch(request);
|
|
92
|
+
}, {
|
|
93
|
+
noTunnel: !command$1.tunnel,
|
|
94
|
+
port: command$1.port,
|
|
95
|
+
...command$1.tunnel && { service: command$1.tunnelService }
|
|
96
|
+
});
|
|
97
|
+
relay = createRelay(command$1.protocol, {
|
|
98
|
+
origin: server?.url.origin,
|
|
99
|
+
name: command$1.name,
|
|
100
|
+
kv,
|
|
101
|
+
subscriptionHandler: async (_ctx, actor) => {
|
|
102
|
+
const isInAcceptList = await matchesActor(actor, acceptFollows);
|
|
103
|
+
const isInRejectList = await matchesActor(actor, rejectFollows);
|
|
104
|
+
return isInAcceptList && !isInRejectList;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
spinner.succeed(`Relay server is running: ${colors.green(server.url.href)}`);
|
|
108
|
+
await printRelayInfo(relay, {
|
|
109
|
+
protocol: command$1.protocol,
|
|
110
|
+
name: command$1.name,
|
|
111
|
+
persistent: command$1.persistent
|
|
112
|
+
});
|
|
113
|
+
process.on("SIGINT", async () => {
|
|
114
|
+
spinner.start("Shutting down relay server...");
|
|
115
|
+
await server.close();
|
|
116
|
+
spinner.succeed("Relay server stopped.");
|
|
117
|
+
process.exit(0);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function printRelayInfo(relay, options) {
|
|
121
|
+
const actorUri = await relay.getActorUri();
|
|
122
|
+
const sharedInboxUri = await relay.getSharedInboxUri();
|
|
123
|
+
const table = new Table({
|
|
124
|
+
chars: tableStyle,
|
|
125
|
+
style: {
|
|
126
|
+
head: [],
|
|
127
|
+
border: []
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
table.push({ "Actor URI:": colors.green(actorUri.href) }, { "Shared Inbox:": colors.green(sharedInboxUri.href) }, { "Protocol:": colors.green(options.protocol) }, { "Name:": colors.green(options.name) }, { "Storage:": colors.green(options.persistent ?? "in-memory") });
|
|
131
|
+
console.log(table.toString());
|
|
132
|
+
console.log("\nPress ^C to stop the relay server.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
export { relayCommand, runRelay };
|