@fedify/cli 2.0.0-pr.479.1922 → 2.0.0

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.
Files changed (160) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -3
  3. package/dist/cache.js +17 -3
  4. package/dist/config.js +105 -0
  5. package/dist/deno.js +18 -8
  6. package/dist/generate-vocab/action.js +1 -1
  7. package/dist/imagerenderer.js +1 -1
  8. package/dist/inbox/rendercode.js +11 -21
  9. package/dist/inbox.js +162 -132
  10. package/dist/init/mod.js +3 -3
  11. package/dist/log.js +35 -1
  12. package/dist/lookup.js +55 -23
  13. package/dist/mod.js +95 -18
  14. package/dist/nodeinfo.js +39 -22
  15. package/dist/options.js +84 -0
  16. package/dist/relay.js +136 -0
  17. package/dist/tempserver.js +15 -8
  18. package/dist/tunnel.js +6 -10
  19. package/dist/utils.js +19 -108
  20. package/dist/webfinger/action.js +1 -1
  21. package/dist/webfinger/command.js +17 -9
  22. package/dist/webfinger/lib.js +3 -3
  23. package/package.json +50 -28
  24. package/deno.json +0 -71
  25. package/dist/globals.js +0 -49
  26. package/dist/init/action/configs.js +0 -91
  27. package/dist/init/action/const.js +0 -10
  28. package/dist/init/action/deps.js +0 -50
  29. package/dist/init/action/dir.js +0 -16
  30. package/dist/init/action/env.js +0 -13
  31. package/dist/init/action/install.js +0 -20
  32. package/dist/init/action/mod.js +0 -39
  33. package/dist/init/action/notice.js +0 -55
  34. package/dist/init/action/patch.js +0 -147
  35. package/dist/init/action/precommand.js +0 -28
  36. package/dist/init/action/recommend.js +0 -24
  37. package/dist/init/action/set.js +0 -31
  38. package/dist/init/action/templates.js +0 -58
  39. package/dist/init/action/utils.js +0 -50
  40. package/dist/init/ask/dir.js +0 -82
  41. package/dist/init/ask/kv.js +0 -44
  42. package/dist/init/ask/mod.js +0 -16
  43. package/dist/init/ask/mq.js +0 -46
  44. package/dist/init/ask/pm.js +0 -49
  45. package/dist/init/ask/wf.js +0 -29
  46. package/dist/init/command.js +0 -50
  47. package/dist/init/const.js +0 -31
  48. package/dist/init/json/biome.js +0 -24
  49. package/dist/init/json/kv.js +0 -53
  50. package/dist/init/json/mq.js +0 -72
  51. package/dist/init/json/pm.js +0 -44
  52. package/dist/init/json/rt.js +0 -39
  53. package/dist/init/json/vscode-settings-for-deno.js +0 -53
  54. package/dist/init/json/vscode-settings.js +0 -49
  55. package/dist/init/lib.js +0 -136
  56. package/dist/init/templates/defaults/federation.ts.tpl +0 -23
  57. package/dist/init/templates/defaults/logging.ts.tpl +0 -23
  58. package/dist/init/templates/express/app.ts.tpl +0 -16
  59. package/dist/init/templates/express/index.ts.tpl +0 -6
  60. package/dist/init/templates/hono/app.tsx.tpl +0 -14
  61. package/dist/init/templates/hono/index/bun.ts.tpl +0 -10
  62. package/dist/init/templates/hono/index/deno.ts.tpl +0 -13
  63. package/dist/init/templates/hono/index/node.ts.tpl +0 -14
  64. package/dist/init/templates/next/middleware.ts.tpl +0 -45
  65. package/dist/init/templates/nitro/.env.test.tpl +0 -1
  66. package/dist/init/templates/nitro/nitro.config.ts.tpl +0 -14
  67. package/dist/init/templates/nitro/server/error.ts.tpl +0 -3
  68. package/dist/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
  69. package/dist/init/test/action.js +0 -17
  70. package/dist/init/test/create.js +0 -100
  71. package/dist/init/test/fill.js +0 -32
  72. package/dist/init/test/lookup.js +0 -190
  73. package/dist/init/test/run.js +0 -25
  74. package/dist/init/test/utils.js +0 -17
  75. package/dist/init/webframeworks.js +0 -136
  76. package/scripts/pack.ts +0 -71
  77. package/src/cache.ts +0 -17
  78. package/src/docloader.ts +0 -67
  79. package/src/generate-vocab/action.ts +0 -17
  80. package/src/generate-vocab/command.ts +0 -44
  81. package/src/generate-vocab/mod.ts +0 -2
  82. package/src/globals.ts +0 -43
  83. package/src/imagerenderer.ts +0 -149
  84. package/src/inbox/entry.ts +0 -10
  85. package/src/inbox/rendercode.ts +0 -68
  86. package/src/inbox/view.tsx +0 -598
  87. package/src/inbox.tsx +0 -536
  88. package/src/init/action/configs.ts +0 -133
  89. package/src/init/action/const.ts +0 -9
  90. package/src/init/action/deps.ts +0 -161
  91. package/src/init/action/dir.ts +0 -11
  92. package/src/init/action/env.ts +0 -14
  93. package/src/init/action/install.ts +0 -24
  94. package/src/init/action/mod.ts +0 -66
  95. package/src/init/action/notice.ts +0 -103
  96. package/src/init/action/patch.ts +0 -233
  97. package/src/init/action/precommand.ts +0 -29
  98. package/src/init/action/recommend.ts +0 -38
  99. package/src/init/action/set.ts +0 -65
  100. package/src/init/action/templates.ts +0 -96
  101. package/src/init/action/utils.ts +0 -64
  102. package/src/init/ask/dir.ts +0 -98
  103. package/src/init/ask/kv.ts +0 -82
  104. package/src/init/ask/mod.ts +0 -23
  105. package/src/init/ask/mq.ts +0 -86
  106. package/src/init/ask/pm.ts +0 -58
  107. package/src/init/ask/wf.ts +0 -27
  108. package/src/init/command.ts +0 -135
  109. package/src/init/const.ts +0 -4
  110. package/src/init/json/biome.json +0 -17
  111. package/src/init/json/kv.json +0 -39
  112. package/src/init/json/mq.json +0 -95
  113. package/src/init/json/pm.json +0 -47
  114. package/src/init/json/rt.json +0 -42
  115. package/src/init/json/vscode-settings-for-deno.json +0 -43
  116. package/src/init/json/vscode-settings.json +0 -41
  117. package/src/init/lib.ts +0 -223
  118. package/src/init/mod.ts +0 -3
  119. package/src/init/templates/defaults/federation.ts.tpl +0 -23
  120. package/src/init/templates/defaults/logging.ts.tpl +0 -23
  121. package/src/init/templates/express/app.ts.tpl +0 -16
  122. package/src/init/templates/express/index.ts.tpl +0 -6
  123. package/src/init/templates/hono/app.tsx.tpl +0 -14
  124. package/src/init/templates/hono/index/bun.ts.tpl +0 -10
  125. package/src/init/templates/hono/index/deno.ts.tpl +0 -13
  126. package/src/init/templates/hono/index/node.ts.tpl +0 -14
  127. package/src/init/templates/next/middleware.ts.tpl +0 -45
  128. package/src/init/templates/nitro/.env.test.tpl +0 -1
  129. package/src/init/templates/nitro/nitro.config.ts.tpl +0 -14
  130. package/src/init/templates/nitro/server/error.ts.tpl +0 -3
  131. package/src/init/templates/nitro/server/middleware/federation.ts.tpl +0 -8
  132. package/src/init/test/action.ts +0 -28
  133. package/src/init/test/create.ts +0 -137
  134. package/src/init/test/fill.ts +0 -67
  135. package/src/init/test/lookup.ts +0 -254
  136. package/src/init/test/run.ts +0 -39
  137. package/src/init/test/types.ts +0 -27
  138. package/src/init/test/utils.ts +0 -21
  139. package/src/init/types.ts +0 -89
  140. package/src/init/webframeworks.ts +0 -168
  141. package/src/kv.bun.ts +0 -12
  142. package/src/kv.node.ts +0 -11
  143. package/src/log.ts +0 -64
  144. package/src/lookup.test.ts +0 -182
  145. package/src/lookup.ts +0 -563
  146. package/src/mod.ts +0 -62
  147. package/src/nodeinfo.test.ts +0 -229
  148. package/src/nodeinfo.ts +0 -454
  149. package/src/table.ts +0 -17
  150. package/src/tempserver.ts +0 -87
  151. package/src/tunnel.test.ts +0 -157
  152. package/src/tunnel.ts +0 -94
  153. package/src/utils.ts +0 -254
  154. package/src/webfinger/action.ts +0 -50
  155. package/src/webfinger/command.ts +0 -64
  156. package/src/webfinger/error.ts +0 -47
  157. package/src/webfinger/lib.ts +0 -37
  158. package/src/webfinger/mod.test.ts +0 -79
  159. package/src/webfinger/mod.ts +0 -2
  160. package/tsdown.config.ts +0 -35
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright 2024–2025 Hong Minhee
3
+ Copyright 2024–2026 Hong Minhee
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
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
- [npm]: https://www.npmjs.com/package/@fedify/cli
15
+ [JSR]: https://jsr.io/@fedify/cli
17
16
  [npm badge]: https://img.shields.io/npm/v/@fedify/cli?logo=npm
18
- [GitHub Releases]: https://github.com/fedify-dev/fedify/releases
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 envPaths from "env-paths";
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
- const paths = envPaths("fedify", { suffix: "" });
9
- const DEFAULT_CACHE_DIR = paths.cache;
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.0-pr.479.1922+564a1890";
6
+ var version = "2.0.0";
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/fedify codegen",
40
+ "codegen": "deno task -f @fedify/vocab compile",
38
41
  "check": {
39
- "command": "deno task codegen && deno fmt --check && deno lint && deno check src/**/*.ts",
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 }) {
@@ -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
@@ -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
- async function renderRequest(request) {
8
+ function renderRequest(request) {
9
9
  request = request.clone();
10
10
  const url = new URL(request.url);
11
- let code = `${request.method} ${url.pathname + url.search}\n`;
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
- async function renderResponse(response) {
13
+ function renderResponse(response) {
23
14
  response = response.clone();
24
- let code = `${response.status} ${response.statusText === "" ? getStatusText(response.status) : response.statusText}\n`;
25
- for (const [key, value] of response.headers.entries()) code += `${capitalize(key)}: ${value}\n`;
26
- let body;
27
- try {
28
- body = await response.text();
29
- } catch (_) {
30
- body = "[Failed to decode body; it may be binary.]";
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 { Accept, Activity, Application, Delete, Endpoints, Follow, Image, MemoryKvStore, PUBLIC_COLLECTION, createFederation, generateCryptoKeyPair, getActorHandle, isActor, lookupObject } from "@fedify/fedify";
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: optional(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.` }))),
30
- acceptFollow: optional(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.` }))),
31
- noTunnel: option("-T", "--no-tunnel", { description: message`Do not tunnel the ephemeral ActivityPub server to the public Internet.` }),
32
- actorName: withDefault(option("--actor-name", string({ metavar: "NAME" }), { description: message`Customize the actor display name.` }), "Fedify Ephemeral Inbox"),
33
- actorSummary: withDefault(option("--actor-summary", string({ metavar: "SUMMARY" }), { description: message`Customize the actor description.` }), "An ephemeral ActivityPub inbox for testing purposes.")
34
- }), debugOption), {
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
- const fetch = createFetchHandler({
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, { noTunnel: command$1.noTunnel });
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
- const federationDocumentLoader = await getDocumentLoader();
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 action_default from "./action/mod.js";
5
- import { initCommand, testInitCommand } from "./command.js";
6
- import action_default$1 from "./test/action.js";
4
+ import { initCommand, runInit } from "@fedify/init";
5
+
6
+ export { initCommand, runInit };