@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/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -11,12 +11,12 @@ The `fedify` is a CLI toolchain for Fedify and debugging ActivityPub-enabled
|
|
|
11
11
|
federated server apps. Although it is primarily designed for developers who use
|
|
12
12
|
[Fedify], it can be used with any ActivityPub-enabled server.
|
|
13
13
|
|
|
14
|
-
[JSR]: https://jsr.io/@fedify/cli
|
|
15
14
|
[JSR badge]: https://jsr.io/badges/@fedify/cli
|
|
16
|
-
[
|
|
15
|
+
[JSR]: https://jsr.io/@fedify/cli
|
|
17
16
|
[npm badge]: https://img.shields.io/npm/v/@fedify/cli?logo=npm
|
|
18
|
-
[
|
|
17
|
+
[npm]: https://www.npmjs.com/package/@fedify/cli
|
|
19
18
|
[GitHub Releases badge]: https://img.shields.io/github/v/release/fedify-dev/fedify?sort=semver&logo=github
|
|
19
|
+
[GitHub Releases]: https://github.com/fedify-dev/fedify/releases
|
|
20
20
|
[Fedify]: https://fedify.dev/
|
|
21
21
|
|
|
22
22
|
|
package/dist/cache.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import process from "node:process";
|
|
6
8
|
|
|
7
9
|
//#region src/cache.ts
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Returns the default cache directory path.
|
|
12
|
+
* - Linux/macOS: `$XDG_CACHE_HOME/fedify` (default: ~/.cache/fedify)
|
|
13
|
+
* - Windows: `%LOCALAPPDATA%\fedify`
|
|
14
|
+
*/
|
|
15
|
+
function getDefaultCacheDir() {
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
const localAppData = process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
18
|
+
return join(localAppData, "fedify");
|
|
19
|
+
}
|
|
20
|
+
const xdgCacheHome = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
21
|
+
return join(xdgCacheHome, "fedify");
|
|
22
|
+
}
|
|
23
|
+
const DEFAULT_CACHE_DIR = getDefaultCacheDir();
|
|
10
24
|
let currentCacheDir = DEFAULT_CACHE_DIR;
|
|
11
25
|
async function getCacheDir() {
|
|
12
26
|
await mkdir(currentCacheDir, { recursive: true });
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { message } from "@optique/core";
|
|
5
|
+
import { printError } from "@optique/run";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { parse } from "smol-toml";
|
|
8
|
+
import { createConfigContext } from "@optique/config";
|
|
9
|
+
import { array, boolean, number, object as object$1, optional as optional$1, picklist, string as string$1 } from "valibot";
|
|
10
|
+
|
|
11
|
+
//#region src/config.ts
|
|
12
|
+
/**
|
|
13
|
+
* Schema for the webfinger command configuration.
|
|
14
|
+
*/
|
|
15
|
+
const webfingerSchema = object$1({
|
|
16
|
+
allowPrivateAddress: optional$1(boolean()),
|
|
17
|
+
maxRedirection: optional$1(number())
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Schema for the lookup command configuration.
|
|
21
|
+
*/
|
|
22
|
+
const lookupSchema = object$1({
|
|
23
|
+
authorizedFetch: optional$1(boolean()),
|
|
24
|
+
firstKnock: optional$1(picklist(["draft-cavage-http-signatures-12", "rfc9421"])),
|
|
25
|
+
traverse: optional$1(boolean()),
|
|
26
|
+
suppressErrors: optional$1(boolean()),
|
|
27
|
+
defaultFormat: optional$1(picklist([
|
|
28
|
+
"default",
|
|
29
|
+
"raw",
|
|
30
|
+
"compact",
|
|
31
|
+
"expand"
|
|
32
|
+
])),
|
|
33
|
+
separator: optional$1(string$1()),
|
|
34
|
+
timeout: optional$1(number())
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Schema for the inbox command configuration.
|
|
38
|
+
*/
|
|
39
|
+
const inboxSchema = object$1({
|
|
40
|
+
actorName: optional$1(string$1()),
|
|
41
|
+
actorSummary: optional$1(string$1()),
|
|
42
|
+
authorizedFetch: optional$1(boolean()),
|
|
43
|
+
noTunnel: optional$1(boolean()),
|
|
44
|
+
follow: optional$1(array(string$1())),
|
|
45
|
+
acceptFollow: optional$1(array(string$1()))
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* Schema for the relay command configuration.
|
|
49
|
+
*/
|
|
50
|
+
const relaySchema = object$1({
|
|
51
|
+
protocol: optional$1(picklist(["mastodon", "litepub"])),
|
|
52
|
+
port: optional$1(number()),
|
|
53
|
+
name: optional$1(string$1()),
|
|
54
|
+
persistent: optional$1(string$1()),
|
|
55
|
+
noTunnel: optional$1(boolean()),
|
|
56
|
+
acceptFollow: optional$1(array(string$1())),
|
|
57
|
+
rejectFollow: optional$1(array(string$1()))
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Schema for the nodeinfo command configuration.
|
|
61
|
+
*/
|
|
62
|
+
const nodeinfoSchema = object$1({
|
|
63
|
+
raw: optional$1(boolean()),
|
|
64
|
+
bestEffort: optional$1(boolean()),
|
|
65
|
+
showFavicon: optional$1(boolean()),
|
|
66
|
+
showMetadata: optional$1(boolean())
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Schema for the complete configuration file.
|
|
70
|
+
*/
|
|
71
|
+
const configSchema = object$1({
|
|
72
|
+
debug: optional$1(boolean()),
|
|
73
|
+
userAgent: optional$1(string$1()),
|
|
74
|
+
tunnelService: optional$1(picklist([
|
|
75
|
+
"localhost.run",
|
|
76
|
+
"serveo.net",
|
|
77
|
+
"pinggy.io"
|
|
78
|
+
])),
|
|
79
|
+
webfinger: optional$1(webfingerSchema),
|
|
80
|
+
lookup: optional$1(lookupSchema),
|
|
81
|
+
inbox: optional$1(inboxSchema),
|
|
82
|
+
relay: optional$1(relaySchema),
|
|
83
|
+
nodeinfo: optional$1(nodeinfoSchema)
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Config context for use with bindConfig().
|
|
87
|
+
*/
|
|
88
|
+
const configContext = createConfigContext({ schema: configSchema });
|
|
89
|
+
/**
|
|
90
|
+
* Try to load and parse a TOML config file.
|
|
91
|
+
* Returns an empty object if the file doesn't exist.
|
|
92
|
+
* Logs a warning and returns empty object for other errors (parsing, permissions).
|
|
93
|
+
*/
|
|
94
|
+
function tryLoadToml(filePath) {
|
|
95
|
+
try {
|
|
96
|
+
return parse(readFileSync(filePath, "utf-8"));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
99
|
+
printError(message`Could not load or parse config file at ${filePath}. It will be ignored.`);
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { configContext, tryLoadToml };
|
package/dist/deno.js
CHANGED
|
@@ -3,28 +3,26 @@
|
|
|
3
3
|
|
|
4
4
|
//#region deno.json
|
|
5
5
|
var name = "@fedify/cli";
|
|
6
|
-
var version = "2.0.
|
|
6
|
+
var version = "2.0.1";
|
|
7
7
|
var license = "MIT";
|
|
8
8
|
var exports = "./src/mod.ts";
|
|
9
9
|
var imports = {
|
|
10
|
-
"@fxts/core": "npm:@fxts/core@^1.15.0",
|
|
11
10
|
"@hongminhee/localtunnel": "jsr:@hongminhee/localtunnel@^0.3.0",
|
|
12
11
|
"@inquirer/prompts": "npm:@inquirer/prompts@^7.8.4",
|
|
13
12
|
"@jimp/core": "npm:@jimp/core@^1.6.0",
|
|
14
13
|
"@jimp/wasm-webp": "npm:@jimp/wasm-webp@^1.6.0",
|
|
15
|
-
"@optique/core": "jsr:@optique/core@^0.6.1",
|
|
16
|
-
"@optique/run": "jsr:@optique/run@^0.6.1",
|
|
17
14
|
"@poppanator/http-constants": "npm:@poppanator/http-constants@^1.1.1",
|
|
18
15
|
"chalk": "npm:chalk@^5.6.2",
|
|
19
16
|
"cli-table3": "npm:cli-table3@^0.6.5",
|
|
20
|
-
"env-paths": "npm:env-paths@^3.0.0",
|
|
21
17
|
"fetch-mock": "npm:fetch-mock@^12.5.4",
|
|
22
18
|
"hono": "jsr:@hono/hono@^4.8.3",
|
|
23
19
|
"icojs": "npm:icojs@^0.19.5",
|
|
24
20
|
"inquirer-toggle": "npm:inquirer-toggle@^1.0.1",
|
|
25
21
|
"ora": "npm:ora@^8.2.0",
|
|
26
22
|
"shiki": "npm:shiki@^1.6.4",
|
|
23
|
+
"smol-toml": "npm:smol-toml@^1.6.0",
|
|
27
24
|
"srvx": "npm:srvx@^0.8.7",
|
|
25
|
+
"valibot": "jsr:@valibot/valibot@^1.2.0",
|
|
28
26
|
"#kv": "./src/kv.node.ts"
|
|
29
27
|
};
|
|
30
28
|
var exclude = [
|
|
@@ -33,10 +31,15 @@ var exclude = [
|
|
|
33
31
|
"fedify-cli-*.tgz",
|
|
34
32
|
"fedify-cli-*.zip"
|
|
35
33
|
];
|
|
34
|
+
var publish = { "exclude": [
|
|
35
|
+
"**/*.test.ts",
|
|
36
|
+
"tsdown.config.ts",
|
|
37
|
+
"scripts/"
|
|
38
|
+
] };
|
|
36
39
|
var tasks = {
|
|
37
|
-
"codegen": "deno task -f @fedify/
|
|
40
|
+
"codegen": "deno task -f @fedify/vocab compile",
|
|
38
41
|
"check": {
|
|
39
|
-
"command": "deno
|
|
42
|
+
"command": "deno fmt --check && deno lint && deno check src/**/*.ts",
|
|
40
43
|
"dependencies": ["codegen"]
|
|
41
44
|
},
|
|
42
45
|
"run": {
|
|
@@ -51,10 +54,15 @@ var tasks = {
|
|
|
51
54
|
"test": {
|
|
52
55
|
"command": "deno test --allow-all",
|
|
53
56
|
"dependencies": ["codegen"]
|
|
57
|
+
},
|
|
58
|
+
"test-init": {
|
|
59
|
+
"command": "FEDIFY_TEST_MODE=true deno run --allow-all src/init/test/mod.ts test-init",
|
|
60
|
+
"dependencies": ["codegen"]
|
|
54
61
|
}
|
|
55
62
|
};
|
|
56
63
|
var fmt = { "exclude": ["src/init/templates/**"] };
|
|
57
64
|
var lint = { "exclude": ["src/init/templates/**"] };
|
|
65
|
+
var test = { "exclude": ["src/init/test/**"] };
|
|
58
66
|
var deno_default = {
|
|
59
67
|
name,
|
|
60
68
|
version,
|
|
@@ -62,9 +70,11 @@ var deno_default = {
|
|
|
62
70
|
exports,
|
|
63
71
|
imports,
|
|
64
72
|
exclude,
|
|
73
|
+
publish,
|
|
65
74
|
tasks,
|
|
66
75
|
fmt,
|
|
67
|
-
lint
|
|
76
|
+
lint,
|
|
77
|
+
test
|
|
68
78
|
};
|
|
69
79
|
|
|
70
80
|
//#endregion
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
|
+
import process from "node:process";
|
|
5
6
|
import { printError } from "@optique/run";
|
|
6
7
|
import { generateVocab } from "@fedify/vocab-tools";
|
|
7
8
|
import { message } from "@optique/core/message";
|
|
8
|
-
import process from "node:process";
|
|
9
9
|
|
|
10
10
|
//#region src/generate-vocab/action.ts
|
|
11
11
|
async function runGenerateVocab({ schemaDir, generatedPath }) {
|
package/dist/imagerenderer.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { Jimp } from "./nodeinfo.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
|
-
import process from "node:process";
|
|
8
7
|
import os from "node:os";
|
|
8
|
+
import process from "node:process";
|
|
9
9
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
10
10
|
|
|
11
11
|
//#region src/imagerenderer.ts
|
package/dist/inbox/rendercode.js
CHANGED
|
@@ -5,31 +5,21 @@ import { getContextLoader } from "../docloader.js";
|
|
|
5
5
|
import { getStatusText } from "@poppanator/http-constants";
|
|
6
6
|
|
|
7
7
|
//#region src/inbox/rendercode.ts
|
|
8
|
-
|
|
8
|
+
function renderRequest(request) {
|
|
9
9
|
request = request.clone();
|
|
10
10
|
const url = new URL(request.url);
|
|
11
|
-
|
|
12
|
-
for (const [key, value] of request.headers.entries()) code += `${capitalize(key)}: ${value}\n`;
|
|
13
|
-
let body;
|
|
14
|
-
try {
|
|
15
|
-
body = await request.text();
|
|
16
|
-
} catch (_) {
|
|
17
|
-
body = "[Failed to decode body; it may be binary.]";
|
|
18
|
-
}
|
|
19
|
-
code += `\n${body}`;
|
|
20
|
-
return code;
|
|
11
|
+
return renderMessage(`${request.method} ${url.pathname + url.search}`, request.headers, request);
|
|
21
12
|
}
|
|
22
|
-
|
|
13
|
+
function renderResponse(response) {
|
|
23
14
|
response = response.clone();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
code += `\n${body}`;
|
|
15
|
+
const code = `${response.status} ${response.statusText === "" ? getStatusText(response.status) : response.statusText}`;
|
|
16
|
+
return renderMessage(code, response.headers, response);
|
|
17
|
+
}
|
|
18
|
+
async function renderMessage(code, headers, body) {
|
|
19
|
+
code += "\n";
|
|
20
|
+
for (const [key, value] of headers.entries()) code += `${capitalize(key)}: ${value}\n`;
|
|
21
|
+
const bodyText = await body.text().catch((_) => "[Failed to decode body; it may be binary.]");
|
|
22
|
+
code += `\n${bodyText}`;
|
|
33
23
|
return code;
|
|
34
24
|
}
|
|
35
25
|
async function renderRawActivity(request) {
|
package/dist/inbox.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
|
+
import { configContext } from "./config.js";
|
|
4
5
|
import deno_default from "./deno.js";
|
|
5
6
|
import { getDocumentLoader } from "./docloader.js";
|
|
6
|
-
import { recordingSink } from "./log.js";
|
|
7
|
-
import { configureLogging, debugOption } from "./globals.js";
|
|
8
7
|
import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
|
|
8
|
+
import { configureLogging, recordingSink } from "./log.js";
|
|
9
|
+
import { createTunnelOption } from "./options.js";
|
|
9
10
|
import { tableStyle } from "./table.js";
|
|
10
11
|
import { spawnTemporaryServer } from "./tempserver.js";
|
|
11
|
-
import { colors } from "./utils.js";
|
|
12
|
-
import { command, constant, merge, message, multiple, object, option, optional, string, withDefault } from "@optique/core";
|
|
12
|
+
import { colors, matchesActor } from "./utils.js";
|
|
13
13
|
import process from "node:process";
|
|
14
|
-
import {
|
|
14
|
+
import { command, constant, group, merge, message, multiple, object, option, string } from "@optique/core";
|
|
15
|
+
import { bindConfig } from "@optique/config";
|
|
16
|
+
import { MemoryKvStore, createFederation, generateCryptoKeyPair } from "@fedify/fedify";
|
|
17
|
+
import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
|
|
15
18
|
import { getLogger } from "@logtape/logtape";
|
|
16
19
|
import Table from "cli-table3";
|
|
17
20
|
import { Hono } from "hono";
|
|
@@ -26,30 +29,172 @@ const logger = getLogger([
|
|
|
26
29
|
]);
|
|
27
30
|
const inboxCommand = command("inbox", merge(object("Inbox options", {
|
|
28
31
|
command: constant("inbox"),
|
|
29
|
-
follow:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}),
|
|
32
|
+
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.` })), {
|
|
33
|
+
context: configContext,
|
|
34
|
+
key: (config) => config.inbox?.follow ?? [],
|
|
35
|
+
default: []
|
|
36
|
+
}),
|
|
37
|
+
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.` })), {
|
|
38
|
+
context: configContext,
|
|
39
|
+
key: (config) => config.inbox?.acceptFollow ?? [],
|
|
40
|
+
default: []
|
|
41
|
+
}),
|
|
42
|
+
actorName: bindConfig(option("--actor-name", string({ metavar: "NAME" }), { description: message`Customize the actor display name.` }), {
|
|
43
|
+
context: configContext,
|
|
44
|
+
key: (config) => config.inbox?.actorName ?? "Fedify Ephemeral Inbox",
|
|
45
|
+
default: "Fedify Ephemeral Inbox"
|
|
46
|
+
}),
|
|
47
|
+
actorSummary: bindConfig(option("--actor-summary", string({ metavar: "SUMMARY" }), { description: message`Customize the actor description.` }), {
|
|
48
|
+
context: configContext,
|
|
49
|
+
key: (config) => config.inbox?.actorSummary ?? "An ephemeral ActivityPub inbox for testing purposes.",
|
|
50
|
+
default: "An ephemeral ActivityPub inbox for testing purposes."
|
|
51
|
+
}),
|
|
52
|
+
authorizedFetch: bindConfig(option("-A", "--authorized-fetch", { description: message`Enable authorized fetch mode. Incoming requests without valid HTTP signatures will be rejected with 401 Unauthorized.` }), {
|
|
53
|
+
context: configContext,
|
|
54
|
+
key: (config) => config.inbox?.authorizedFetch ?? false,
|
|
55
|
+
default: false
|
|
56
|
+
})
|
|
57
|
+
}), group("Tunnel options", createTunnelOption("inbox"))), {
|
|
35
58
|
brief: message`Run an ephemeral ActivityPub inbox server.`,
|
|
36
59
|
description: message`Spins up an ephemeral server that serves the ActivityPub inbox with an one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`
|
|
37
60
|
});
|
|
61
|
+
const activities = [];
|
|
62
|
+
const acceptFollows = [];
|
|
63
|
+
const peers = {};
|
|
64
|
+
const followers = {};
|
|
38
65
|
async function runInbox(command$1) {
|
|
39
|
-
|
|
66
|
+
activities.length = 0;
|
|
67
|
+
acceptFollows.length = 0;
|
|
68
|
+
for (const key of Object.keys(peers)) delete peers[key];
|
|
69
|
+
for (const key of Object.keys(followers)) delete followers[key];
|
|
70
|
+
if (command$1.debug) await configureLogging();
|
|
71
|
+
const federationDocumentLoader = await getDocumentLoader();
|
|
72
|
+
const authorizedFetchEnabled = command$1.authorizedFetch ?? false;
|
|
73
|
+
const authorize = async (ctx) => {
|
|
74
|
+
if (!authorizedFetchEnabled) return true;
|
|
75
|
+
return await ctx.getSignedKey() != null;
|
|
76
|
+
};
|
|
77
|
+
const instanceActor = async (ctx, identifier) => {
|
|
78
|
+
if (identifier === "ia") return true;
|
|
79
|
+
return await authorize(ctx);
|
|
80
|
+
};
|
|
81
|
+
const federation = createFederation({
|
|
82
|
+
kv: new MemoryKvStore(),
|
|
83
|
+
documentLoaderFactory: () => federationDocumentLoader,
|
|
84
|
+
skipSignatureVerification: !authorizedFetchEnabled
|
|
85
|
+
});
|
|
86
|
+
const time = Temporal.Now.instant();
|
|
87
|
+
let actorKeyPairs = void 0;
|
|
88
|
+
let instanceActorKeyPairs = void 0;
|
|
89
|
+
federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
90
|
+
if (identifier !== "i" && identifier !== "ia") return null;
|
|
91
|
+
const keyPairs = await ctx.getActorKeyPairs(identifier);
|
|
92
|
+
return new Application({
|
|
93
|
+
id: ctx.getActorUri(identifier),
|
|
94
|
+
preferredUsername: identifier,
|
|
95
|
+
name: identifier === "ia" ? "Instance Actor" : ctx.data.actorName,
|
|
96
|
+
summary: identifier === "ia" ? "Instance actor for signing requests" : ctx.data.actorSummary,
|
|
97
|
+
inbox: ctx.getInboxUri(identifier),
|
|
98
|
+
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
|
|
99
|
+
followers: ctx.getFollowersUri(identifier),
|
|
100
|
+
following: ctx.getFollowingUri(identifier),
|
|
101
|
+
outbox: ctx.getOutboxUri(identifier),
|
|
102
|
+
manuallyApprovesFollowers: true,
|
|
103
|
+
published: time,
|
|
104
|
+
icon: new Image({
|
|
105
|
+
url: new URL("https://fedify.dev/logo.png"),
|
|
106
|
+
mediaType: "image/png"
|
|
107
|
+
}),
|
|
108
|
+
publicKey: keyPairs[0].cryptographicKey,
|
|
109
|
+
assertionMethods: keyPairs.map((pair) => pair.multikey),
|
|
110
|
+
url: ctx.getActorUri(identifier)
|
|
111
|
+
});
|
|
112
|
+
}).setKeyPairsDispatcher(async (_ctxData, identifier) => {
|
|
113
|
+
if (identifier === "i") {
|
|
114
|
+
if (actorKeyPairs == null) actorKeyPairs = [await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"), await generateCryptoKeyPair("Ed25519")];
|
|
115
|
+
return actorKeyPairs;
|
|
116
|
+
} else if (identifier === "ia") {
|
|
117
|
+
if (instanceActorKeyPairs == null) instanceActorKeyPairs = [await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"), await generateCryptoKeyPair("Ed25519")];
|
|
118
|
+
return instanceActorKeyPairs;
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
}).authorize(instanceActor);
|
|
122
|
+
federation.setInboxListeners("/{identifier}/inbox", "/inbox").setSharedKeyDispatcher((_) => ({ identifier: "ia" })).on(Activity, async (ctx, activity) => {
|
|
123
|
+
activities[ctx.data.activityIndex].activity = activity;
|
|
124
|
+
for await (const actor of activity.getActors()) if (actor.id != null) peers[actor.id.href] = actor;
|
|
125
|
+
for await (const actor of activity.getAttributions()) if (actor.id != null) peers[actor.id.href] = actor;
|
|
126
|
+
if (activity instanceof Follow) {
|
|
127
|
+
if (acceptFollows.length < 1) return;
|
|
128
|
+
const objectId = activity.objectId;
|
|
129
|
+
if (objectId == null) return;
|
|
130
|
+
const parsed = ctx.parseUri(objectId);
|
|
131
|
+
if (parsed?.type !== "actor" || parsed.identifier !== "i") return;
|
|
132
|
+
const { identifier } = parsed;
|
|
133
|
+
const follower = await activity.getActor();
|
|
134
|
+
if (!isActor(follower)) return;
|
|
135
|
+
const accepts = await matchesActor(follower, acceptFollows);
|
|
136
|
+
if (!accepts || activity.id == null) {
|
|
137
|
+
logger.debug("Does not accept follow from {actor}.", { actor: follower.id?.href });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
logger.debug("Accepting follow from {actor}.", { actor: follower.id?.href });
|
|
141
|
+
followers[activity.id.href] = follower;
|
|
142
|
+
await ctx.sendActivity({ identifier }, follower, new Accept({
|
|
143
|
+
id: new URL(`#accepts/${follower.id?.href}`, ctx.getActorUri("i")),
|
|
144
|
+
actor: ctx.getActorUri(identifier),
|
|
145
|
+
object: activity.id
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
federation.setFollowersDispatcher("/{identifier}/followers", (_ctx, identifier) => {
|
|
150
|
+
if (identifier !== "i") return null;
|
|
151
|
+
const items = [];
|
|
152
|
+
for (const follower of Object.values(followers)) {
|
|
153
|
+
if (follower.id == null) continue;
|
|
154
|
+
items.push(follower);
|
|
155
|
+
}
|
|
156
|
+
return { items };
|
|
157
|
+
}).setCounter((_ctx, identifier) => {
|
|
158
|
+
if (identifier !== "i") return null;
|
|
159
|
+
return Object.keys(followers).length;
|
|
160
|
+
}).authorize(authorize);
|
|
161
|
+
federation.setFollowingDispatcher("/{identifier}/following", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0).authorize(authorize);
|
|
162
|
+
federation.setOutboxDispatcher("/{identifier}/outbox", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0).authorize(authorize);
|
|
163
|
+
federation.setNodeInfoDispatcher("/nodeinfo/2.1", (_ctx) => {
|
|
164
|
+
return {
|
|
165
|
+
software: {
|
|
166
|
+
name: "fedify-cli",
|
|
167
|
+
version: deno_default.version,
|
|
168
|
+
repository: new URL("https://github.com/fedify-dev/fedify")
|
|
169
|
+
},
|
|
170
|
+
protocols: ["activitypub"],
|
|
171
|
+
usage: {
|
|
172
|
+
users: {
|
|
173
|
+
total: 1,
|
|
174
|
+
activeMonth: 1,
|
|
175
|
+
activeHalfyear: 1
|
|
176
|
+
},
|
|
177
|
+
localComments: 0,
|
|
178
|
+
localPosts: 0
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
const fetch = createFetchHandler(federation, {
|
|
40
183
|
actorName: command$1.actorName,
|
|
41
184
|
actorSummary: command$1.actorSummary
|
|
42
185
|
});
|
|
43
|
-
const sendDeleteToPeers = createSendDeleteToPeers({
|
|
186
|
+
const sendDeleteToPeers = createSendDeleteToPeers(federation, {
|
|
44
187
|
actorName: command$1.actorName,
|
|
45
188
|
actorSummary: command$1.actorSummary
|
|
46
189
|
});
|
|
47
|
-
if (command$1.debug) await configureLogging();
|
|
48
190
|
const spinner = ora({
|
|
49
191
|
text: "Spinning up an ephemeral ActivityPub server...",
|
|
50
192
|
discardStdin: false
|
|
51
193
|
}).start();
|
|
52
|
-
const server = await spawnTemporaryServer(fetch, {
|
|
194
|
+
const server = await spawnTemporaryServer(fetch, {
|
|
195
|
+
noTunnel: !command$1.tunnel,
|
|
196
|
+
...command$1.tunnel && { service: command$1.tunnelService }
|
|
197
|
+
});
|
|
53
198
|
spinner.succeed(`The ephemeral ActivityPub server is up and running: ${colors.green(server.url.href)}`);
|
|
54
199
|
process.on("SIGINT", () => {
|
|
55
200
|
spinner.stop();
|
|
@@ -94,61 +239,7 @@ async function runInbox(command$1) {
|
|
|
94
239
|
spinner.stop();
|
|
95
240
|
printServerInfo(fedCtx);
|
|
96
241
|
}
|
|
97
|
-
|
|
98
|
-
const federation = createFederation({
|
|
99
|
-
kv: new MemoryKvStore(),
|
|
100
|
-
documentLoaderFactory: () => {
|
|
101
|
-
return federationDocumentLoader;
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
const time = Temporal.Now.instant();
|
|
105
|
-
let actorKeyPairs = void 0;
|
|
106
|
-
federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
107
|
-
if (identifier !== "i") return null;
|
|
108
|
-
return new Application({
|
|
109
|
-
id: ctx.getActorUri(identifier),
|
|
110
|
-
preferredUsername: identifier,
|
|
111
|
-
name: ctx.data.actorName,
|
|
112
|
-
summary: ctx.data.actorSummary,
|
|
113
|
-
inbox: ctx.getInboxUri(identifier),
|
|
114
|
-
endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
|
|
115
|
-
followers: ctx.getFollowersUri(identifier),
|
|
116
|
-
following: ctx.getFollowingUri(identifier),
|
|
117
|
-
outbox: ctx.getOutboxUri(identifier),
|
|
118
|
-
manuallyApprovesFollowers: true,
|
|
119
|
-
published: time,
|
|
120
|
-
icon: new Image({
|
|
121
|
-
url: new URL("https://fedify.dev/logo.png"),
|
|
122
|
-
mediaType: "image/png"
|
|
123
|
-
}),
|
|
124
|
-
publicKey: (await ctx.getActorKeyPairs(identifier))[0].cryptographicKey,
|
|
125
|
-
assertionMethods: (await ctx.getActorKeyPairs(identifier)).map((pair) => pair.multikey),
|
|
126
|
-
url: ctx.getActorUri(identifier)
|
|
127
|
-
});
|
|
128
|
-
}).setKeyPairsDispatcher(async (_ctxData, identifier) => {
|
|
129
|
-
if (identifier !== "i") return [];
|
|
130
|
-
if (actorKeyPairs == null) actorKeyPairs = [await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"), await generateCryptoKeyPair("Ed25519")];
|
|
131
|
-
return actorKeyPairs;
|
|
132
|
-
});
|
|
133
|
-
const activities = [];
|
|
134
|
-
const acceptFollows = [];
|
|
135
|
-
async function acceptsFollowFrom(actor) {
|
|
136
|
-
const actorUri = actor.id;
|
|
137
|
-
let actorHandle = void 0;
|
|
138
|
-
if (actorUri == null) return false;
|
|
139
|
-
for (let uri of acceptFollows) {
|
|
140
|
-
if (uri === "*") return true;
|
|
141
|
-
if (uri.startsWith("http:") || uri.startsWith("https:")) {
|
|
142
|
-
uri = new URL(uri).href;
|
|
143
|
-
if (uri === actorUri.href) return true;
|
|
144
|
-
}
|
|
145
|
-
if (actorHandle == null) actorHandle = await getActorHandle(actor);
|
|
146
|
-
if (actorHandle === uri) return true;
|
|
147
|
-
}
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
const peers = {};
|
|
151
|
-
function createSendDeleteToPeers(actorOptions) {
|
|
242
|
+
function createSendDeleteToPeers(federation, actorOptions) {
|
|
152
243
|
return async function sendDeleteToPeers(server) {
|
|
153
244
|
const ctx = federation.createContext(new Request(server.url), {
|
|
154
245
|
activityIndex: -1,
|
|
@@ -168,67 +259,6 @@ function createSendDeleteToPeers(actorOptions) {
|
|
|
168
259
|
}
|
|
169
260
|
};
|
|
170
261
|
}
|
|
171
|
-
const followers = {};
|
|
172
|
-
federation.setInboxListeners("/{identifier}/inbox", "/inbox").setSharedKeyDispatcher((_) => ({ identifier: "i" })).on(Activity, async (ctx, activity) => {
|
|
173
|
-
activities[ctx.data.activityIndex].activity = activity;
|
|
174
|
-
for await (const actor of activity.getActors()) if (actor.id != null) peers[actor.id.href] = actor;
|
|
175
|
-
for await (const actor of activity.getAttributions()) if (actor.id != null) peers[actor.id.href] = actor;
|
|
176
|
-
if (activity instanceof Follow) {
|
|
177
|
-
if (acceptFollows.length < 1) return;
|
|
178
|
-
const objectId = activity.objectId;
|
|
179
|
-
if (objectId == null) return;
|
|
180
|
-
const parsed = ctx.parseUri(objectId);
|
|
181
|
-
if (parsed?.type !== "actor" || parsed.identifier !== "i") return;
|
|
182
|
-
const { identifier } = parsed;
|
|
183
|
-
const follower = await activity.getActor();
|
|
184
|
-
if (!isActor(follower)) return;
|
|
185
|
-
const accepts = await acceptsFollowFrom(follower);
|
|
186
|
-
if (!accepts || activity.id == null) {
|
|
187
|
-
logger.debug("Does not accept follow from {actor}.", { actor: follower.id?.href });
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
logger.debug("Accepting follow from {actor}.", { actor: follower.id?.href });
|
|
191
|
-
followers[activity.id.href] = follower;
|
|
192
|
-
await ctx.sendActivity({ identifier }, follower, new Accept({
|
|
193
|
-
id: new URL(`#accepts/${follower.id?.href}`, ctx.getActorUri("i")),
|
|
194
|
-
actor: ctx.getActorUri(identifier),
|
|
195
|
-
object: activity.id
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
federation.setFollowersDispatcher("/{identifier}/followers", (_ctx, identifier) => {
|
|
200
|
-
if (identifier !== "i") return null;
|
|
201
|
-
const items = [];
|
|
202
|
-
for (const follower of Object.values(followers)) {
|
|
203
|
-
if (follower.id == null) continue;
|
|
204
|
-
items.push(follower);
|
|
205
|
-
}
|
|
206
|
-
return { items };
|
|
207
|
-
}).setCounter((_ctx, identifier) => {
|
|
208
|
-
if (identifier !== "i") return null;
|
|
209
|
-
return Object.keys(followers).length;
|
|
210
|
-
});
|
|
211
|
-
federation.setFollowingDispatcher("/{identifier}/following", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0);
|
|
212
|
-
federation.setOutboxDispatcher("/{identifier}/outbox", (_ctx, _identifier) => null).setCounter((_ctx, _identifier) => 0);
|
|
213
|
-
federation.setNodeInfoDispatcher("/nodeinfo/2.1", (_ctx) => {
|
|
214
|
-
return {
|
|
215
|
-
software: {
|
|
216
|
-
name: "fedify-cli",
|
|
217
|
-
version: deno_default.version,
|
|
218
|
-
repository: new URL("https://github.com/fedify-dev/fedify")
|
|
219
|
-
},
|
|
220
|
-
protocols: ["activitypub"],
|
|
221
|
-
usage: {
|
|
222
|
-
users: {
|
|
223
|
-
total: 1,
|
|
224
|
-
activeMonth: 1,
|
|
225
|
-
activeHalfyear: 1
|
|
226
|
-
},
|
|
227
|
-
localComments: 0,
|
|
228
|
-
localPosts: 0
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
});
|
|
232
262
|
function printServerInfo(fedCtx) {
|
|
233
263
|
const table = new Table({
|
|
234
264
|
chars: tableStyle,
|
|
@@ -279,7 +309,7 @@ app.get("/r/:idx{[0-9]+}", (c) => {
|
|
|
279
309
|
tabPage: tab
|
|
280
310
|
}));
|
|
281
311
|
});
|
|
282
|
-
function createFetchHandler(actorOptions) {
|
|
312
|
+
function createFetchHandler(federation, actorOptions) {
|
|
283
313
|
return async function fetch(request) {
|
|
284
314
|
const timestamp = Temporal.Now.instant();
|
|
285
315
|
const idx = activities.length;
|
package/dist/init/mod.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { initCommand, runInit } from "@fedify/init";
|
|
5
|
+
|
|
6
|
+
export { initCommand, runInit };
|