@fedify/cli 2.3.0-dev.994 → 2.4.0-dev.1417

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 (83) hide show
  1. package/dist/bench/action.js +469 -0
  2. package/dist/bench/actor/documents.js +39 -0
  3. package/dist/bench/actor/fleet.js +39 -0
  4. package/dist/bench/actor/keys.js +35 -0
  5. package/dist/bench/command.js +72 -0
  6. package/dist/bench/compare/schema.js +16 -0
  7. package/dist/bench/compare.js +667 -0
  8. package/dist/bench/discovery/discover.js +67 -0
  9. package/dist/bench/discovery/probe.js +50 -0
  10. package/dist/bench/load/arrival.js +27 -0
  11. package/dist/bench/load/clock.js +33 -0
  12. package/dist/bench/load/generator.js +145 -0
  13. package/dist/bench/metrics/aggregate.js +64 -0
  14. package/dist/bench/metrics/histogram.js +141 -0
  15. package/dist/bench/metrics/stats-client.js +216 -0
  16. package/dist/bench/mod.js +11 -0
  17. package/dist/bench/render/format.js +46 -0
  18. package/dist/bench/render/index.js +20 -0
  19. package/dist/bench/render/json.js +12 -0
  20. package/dist/bench/render/markdown.js +63 -0
  21. package/dist/bench/render/text.js +75 -0
  22. package/dist/bench/result/build.js +252 -0
  23. package/dist/bench/result/expect/assert.js +74 -0
  24. package/dist/bench/result/expect/evaluate.js +128 -0
  25. package/dist/bench/result/expect/metrics.js +34 -0
  26. package/dist/bench/result/schema.js +365 -0
  27. package/dist/bench/safety/gate.js +62 -0
  28. package/dist/bench/safety/tiers.js +97 -0
  29. package/dist/bench/scenario/coerce.js +24 -0
  30. package/dist/bench/scenario/errors.js +36 -0
  31. package/dist/bench/scenario/load.js +69 -0
  32. package/dist/bench/scenario/normalize.js +125 -0
  33. package/dist/bench/scenario/schema.js +399 -0
  34. package/dist/bench/scenario/units.js +56 -0
  35. package/dist/bench/scenario/validate.js +29 -0
  36. package/dist/bench/scenarios/actor.js +38 -0
  37. package/dist/bench/scenarios/failure.js +363 -0
  38. package/dist/bench/scenarios/fanout.js +261 -0
  39. package/dist/bench/scenarios/inbox.js +147 -0
  40. package/dist/bench/scenarios/mixed.js +244 -0
  41. package/dist/bench/scenarios/object-discovery.js +211 -0
  42. package/dist/bench/scenarios/object.js +54 -0
  43. package/dist/bench/scenarios/read.js +108 -0
  44. package/dist/bench/scenarios/registry.js +39 -0
  45. package/dist/bench/scenarios/runner.js +96 -0
  46. package/dist/bench/scenarios/webfinger.js +44 -0
  47. package/dist/bench/server/synthetic.js +118 -0
  48. package/dist/bench/signing/activity-id.js +18 -0
  49. package/dist/bench/signing/pipeline.js +134 -0
  50. package/dist/bench/signing/signer.js +39 -0
  51. package/dist/bench/template/generate.js +90 -0
  52. package/dist/bench/template/helpers.js +19 -0
  53. package/dist/bench/template/template.js +132 -0
  54. package/dist/cache.js +2 -2
  55. package/dist/commands.js +110 -0
  56. package/dist/config.js +15 -3
  57. package/dist/deno.js +1 -1
  58. package/dist/docloader.js +1 -1
  59. package/dist/generate-vocab/action.js +3 -3
  60. package/dist/generate-vocab/command.js +6 -4
  61. package/dist/imagerenderer.js +3 -3
  62. package/dist/inbox/command.js +6 -4
  63. package/dist/inbox/view.js +1 -1
  64. package/dist/inbox.js +4 -4
  65. package/dist/log.js +2 -2
  66. package/dist/lookup/command.js +121 -0
  67. package/dist/lookup.js +27 -138
  68. package/dist/mod.js +2 -20
  69. package/dist/nodeinfo.js +53 -12
  70. package/dist/options.js +1 -1
  71. package/dist/relay/command.js +6 -4
  72. package/dist/relay.js +3 -3
  73. package/dist/runner.js +70 -45
  74. package/dist/tunnel.js +8 -6
  75. package/dist/utils.js +9 -4
  76. package/dist/webfinger/action.js +1 -1
  77. package/dist/webfinger/command.js +6 -4
  78. package/dist/webfinger/error.js +2 -0
  79. package/dist/webfinger/lib.js +1 -1
  80. package/package.json +28 -23
  81. package/dist/generate-vocab/mod.js +0 -4
  82. package/dist/init/mod.js +0 -3
  83. package/dist/webfinger/mod.js +0 -4
@@ -0,0 +1,90 @@
1
+ import "@js-temporal/polyfill";
2
+ //#region src/bench/template/generate.ts
3
+ /**
4
+ * Typed payload-generation directives for the scenario format.
5
+ *
6
+ * Rather than templating payload bodies as strings, the format uses typed
7
+ * directives such as `content: { generate: lorem, size: 2KB }`, which are
8
+ * JSON-Schema-validatable and produce deterministic output of a given byte
9
+ * size.
10
+ * @since 2.3.0
11
+ * @module
12
+ */
13
+ /**
14
+ * The largest payload {@link resolveGenerate} will produce (100 MiB). A
15
+ * generated payload is held in memory as a single string, so a much larger size
16
+ * would exhaust memory or overflow `String.repeat`; a realistic benchmark body
17
+ * is far smaller. (`parseSize` itself stays a plain parser with no limit.)
18
+ */
19
+ const MAX_PAYLOAD_SIZE = 100 * 1024 * 1024;
20
+ /** Multipliers for the size units accepted by {@link parseSize}. */
21
+ const SIZE_UNITS = {
22
+ b: 1,
23
+ kb: 1024,
24
+ kib: 1024,
25
+ mb: 1024 ** 2,
26
+ mib: 1024 ** 2,
27
+ gb: 1024 ** 3,
28
+ gib: 1024 ** 3
29
+ };
30
+ const SIZE_RE = /^\s*(\d+(?:\.\d+)?)\s*(b|kb|kib|mb|mib|gb|gib)?\s*$/i;
31
+ /**
32
+ * Parses a human-friendly byte size such as `"2KB"`, `"1.5MiB"`, or `512` into
33
+ * a number of bytes. Units are binary (`KB` = 1024 bytes); a bare number is
34
+ * interpreted as bytes.
35
+ * @param value A size string or a plain number of bytes.
36
+ * @returns The size in bytes, as a non-negative integer.
37
+ * @throws {RangeError} If the value cannot be parsed or is negative.
38
+ */
39
+ function parseSize(value) {
40
+ if (typeof value === "number") {
41
+ if (!Number.isFinite(value) || value < 0) throw new RangeError(`Invalid size: ${value}.`);
42
+ return ensureSafe(Math.floor(value), value);
43
+ }
44
+ const match = value.match(SIZE_RE);
45
+ if (match == null) throw new RangeError(`Invalid size: ${JSON.stringify(value)}.`);
46
+ const amount = Number.parseFloat(match[1]);
47
+ const unit = (match[2] ?? "b").toLowerCase();
48
+ return ensureSafe(Math.floor(amount * SIZE_UNITS[unit]), value);
49
+ }
50
+ function ensureSafe(bytes, original) {
51
+ if (!Number.isSafeInteger(bytes)) throw new RangeError(`Size out of range: ${JSON.stringify(original)}.`);
52
+ return bytes;
53
+ }
54
+ /**
55
+ * Determines whether a value is a {@link GenerateDirective} rather than a plain
56
+ * literal (such as a string content body).
57
+ * @param value The value to test.
58
+ * @returns `true` if the value is a generate directive.
59
+ */
60
+ function isGenerateDirective(value) {
61
+ return value != null && typeof value === "object" && !Array.isArray(value) && Object.hasOwn(value, "generate") && typeof value.generate === "string";
62
+ }
63
+ /** A fixed lorem ipsum corpus used by the `lorem` generator. */
64
+ const LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ";
65
+ /**
66
+ * Resolves a {@link GenerateDirective} into a deterministic payload string.
67
+ *
68
+ * The output is exactly the requested number of bytes (ASCII, so bytes equal
69
+ * characters) and is identical across calls for the same directive, which keeps
70
+ * benchmark payloads reproducible.
71
+ * @param directive The directive to resolve.
72
+ * @returns The generated payload string.
73
+ * @throws {RangeError} If the generator is unknown or the size is invalid.
74
+ */
75
+ function resolveGenerate(directive) {
76
+ const size = directive.size == null ? 0 : parseSize(directive.size);
77
+ if (size > MAX_PAYLOAD_SIZE) throw new RangeError(`Payload size ${JSON.stringify(directive.size)} exceeds the maximum of ${MAX_PAYLOAD_SIZE} bytes.`);
78
+ switch (directive.generate) {
79
+ case "lorem": return generateLorem(size);
80
+ default: throw new RangeError(`Unknown payload generator: ${JSON.stringify(directive.generate)}.`);
81
+ }
82
+ }
83
+ function generateLorem(size) {
84
+ if (size <= 0) return "";
85
+ let out = LOREM.repeat(Math.ceil(size / 335));
86
+ if (out.length > size) out = out.slice(0, size);
87
+ return out;
88
+ }
89
+ //#endregion
90
+ export { isGenerateDirective, resolveGenerate };
@@ -0,0 +1,19 @@
1
+ import "@js-temporal/polyfill";
2
+ //#region src/bench/template/helpers.ts
3
+ /**
4
+ * Returns a fresh registry of the default template helpers:
5
+ *
6
+ * - `uuid()` — a random UUID string.
7
+ * - `upper(value)` — the uppercase form of the argument.
8
+ * - `lower(value)` — the lowercase form of the argument.
9
+ * @returns A new record of helper functions.
10
+ */
11
+ function defaultHelpers() {
12
+ return {
13
+ uuid: () => crypto.randomUUID(),
14
+ upper: (value) => String(value).toUpperCase(),
15
+ lower: (value) => String(value).toLowerCase()
16
+ };
17
+ }
18
+ //#endregion
19
+ export { defaultHelpers };
@@ -0,0 +1,132 @@
1
+ import "@js-temporal/polyfill";
2
+ //#region src/bench/template/template.ts
3
+ /** An error raised while rendering a `${{ ... }}` template expression. */
4
+ var TemplateError = class extends Error {};
5
+ const EXPR_RE = /\$\{\{([\s\S]*?)\}\}/g;
6
+ const CALL_RE = /^([A-Za-z_]\w*)\s*\(([\s\S]*)\)$/;
7
+ const IDENT_RE = /^[A-Za-z_]\w*$/;
8
+ /** Property names that must never be resolved, to avoid prototype access. */
9
+ const FORBIDDEN = new Set([
10
+ "__proto__",
11
+ "prototype",
12
+ "constructor"
13
+ ]);
14
+ /** A guard against unbounded recursion on pathologically nested input. */
15
+ const MAX_DEPTH = 100;
16
+ /**
17
+ * Recursively renders every `${{ ... }}` expression in a value.
18
+ *
19
+ * When a string consists of a single expression, the raw evaluated value is
20
+ * returned (so `${{ count }}` can yield a number). When an expression is
21
+ * embedded in surrounding text, its result is stringified and interpolated.
22
+ * Objects and arrays are walked recursively; other scalars pass through.
23
+ * @typeParam T The value type.
24
+ * @param value The value to render.
25
+ * @param context The evaluation context.
26
+ * @returns The rendered value, of the same shape as the input.
27
+ */
28
+ function renderTemplates(value, context = {}) {
29
+ return renderValue(value, context);
30
+ }
31
+ function renderValue(value, ctx, depth = 0) {
32
+ if (depth > MAX_DEPTH) throw new TemplateError("Maximum template nesting depth exceeded.");
33
+ if (typeof value === "string") return renderString(value, ctx);
34
+ if (Array.isArray(value)) {
35
+ let out;
36
+ for (let i = 0; i < value.length; i++) {
37
+ const item = value[i];
38
+ const rendered = renderValue(item, ctx, depth + 1);
39
+ if (out == null && rendered !== item) out = value.slice(0, i);
40
+ if (out != null) out.push(rendered);
41
+ }
42
+ return out ?? value;
43
+ }
44
+ if (value != null && typeof value === "object") {
45
+ const entries = Object.entries(value);
46
+ let out;
47
+ for (let i = 0; i < entries.length; i++) {
48
+ const [key, item] = entries[i];
49
+ const rendered = renderValue(item, ctx, depth + 1);
50
+ if (out == null && rendered !== item) {
51
+ out = {};
52
+ for (let j = 0; j < i; j++) out[entries[j][0]] = entries[j][1];
53
+ }
54
+ if (out != null) out[key] = rendered;
55
+ }
56
+ return out ?? value;
57
+ }
58
+ return value;
59
+ }
60
+ function renderString(str, ctx) {
61
+ const matches = [...str.matchAll(EXPR_RE)];
62
+ if (str.split("${{").length - 1 !== matches.length) throw new TemplateError(`Unclosed \${{ }} expression: ${str}`);
63
+ if (matches.length === 0) return str;
64
+ const only = matches[0];
65
+ if (matches.length === 1 && str.slice(0, only.index).trim() === "" && str.slice(only.index + only[0].length).trim() === "") return evalExpr(only[1], ctx);
66
+ return str.replace(EXPR_RE, (_match, expr) => stringify(evalExpr(expr, ctx)));
67
+ }
68
+ function evalExpr(source, ctx) {
69
+ const expr = source.trim();
70
+ if (expr === "") throw new TemplateError("Empty ${{ }} expression.");
71
+ const call = expr.match(CALL_RE);
72
+ if (call != null) {
73
+ const name = call[1];
74
+ const helper = FORBIDDEN.has(name) || ctx.helpers == null || !Object.hasOwn(ctx.helpers, name) ? void 0 : ctx.helpers[name];
75
+ if (typeof helper !== "function") throw new TemplateError(`Unknown helper: ${name}.`);
76
+ return helper(...parseArgs(call[2], ctx));
77
+ }
78
+ return resolvePath(expr, ctx.values ?? {});
79
+ }
80
+ function parseArgs(source, ctx) {
81
+ const trimmed = source.trim();
82
+ if (trimmed === "") return [];
83
+ return splitTopLevel(trimmed).map((arg) => parseArg(arg.trim(), ctx));
84
+ }
85
+ function splitTopLevel(source) {
86
+ const parts = [];
87
+ let current = "";
88
+ let quote = null;
89
+ let escaped = false;
90
+ for (const char of source) if (escaped) {
91
+ current += char;
92
+ escaped = false;
93
+ } else if (char === "\\") {
94
+ current += char;
95
+ escaped = true;
96
+ } else if (quote != null) {
97
+ if (char === quote) quote = null;
98
+ current += char;
99
+ } else if (char === "'" || char === "\"") {
100
+ quote = char;
101
+ current += char;
102
+ } else if (char === ",") {
103
+ parts.push(current);
104
+ current = "";
105
+ } else current += char;
106
+ if (quote != null) throw new TemplateError("Unbalanced quote in helper arguments.");
107
+ parts.push(current);
108
+ return parts;
109
+ }
110
+ function parseArg(arg, ctx) {
111
+ const str = arg.match(/^'([\s\S]*)'$/) ?? arg.match(/^"([\s\S]*)"$/);
112
+ if (str != null) return str[1].replace(/\\(.)/g, "$1");
113
+ if (/^-?\d+(?:\.\d+)?$/.test(arg)) return Number(arg);
114
+ if (arg === "true") return true;
115
+ if (arg === "false") return false;
116
+ if (arg === "null") return null;
117
+ return resolvePath(arg, ctx.values ?? {});
118
+ }
119
+ function resolvePath(path, values) {
120
+ let current = values;
121
+ for (const part of path.split(".")) {
122
+ if (!IDENT_RE.test(part) || FORBIDDEN.has(part)) throw new TemplateError(`Invalid reference: ${path}.`);
123
+ if (current == null || typeof current !== "object" || !Object.hasOwn(current, part)) throw new TemplateError(`Unknown reference: ${path}.`);
124
+ current = current[part];
125
+ }
126
+ return current;
127
+ }
128
+ function stringify(value) {
129
+ return value == null ? "" : String(value);
130
+ }
131
+ //#endregion
132
+ export { renderTemplates };
package/dist/cache.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
- import { mkdir } from "node:fs/promises";
3
2
  import process from "node:process";
4
- import { join } from "node:path";
5
3
  import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { mkdir } from "node:fs/promises";
6
6
  //#region src/cache.ts
7
7
  /**
8
8
  * Returns the default cache directory path.
@@ -0,0 +1,110 @@
1
+ import "@js-temporal/polyfill";
2
+ import { benchMetadata, benchOptions } from "./bench/command.js";
3
+ import { generateVocabMetadata, generateVocabOptions } from "./generate-vocab/command.js";
4
+ import { inboxMetadata, inboxOptions } from "./inbox/command.js";
5
+ import { lookupMetadata, lookupOptions } from "./lookup/command.js";
6
+ import { nodeInfoMetadata, nodeInfoOptions, runNodeInfo } from "./nodeinfo.js";
7
+ import { relayMetadata, relayOptions } from "./relay/command.js";
8
+ import { runTunnel, tunnelMetadata, tunnelOptions } from "./tunnel.js";
9
+ import { webFingerMetadata, webFingerOptions } from "./webfinger/command.js";
10
+ import { constant, merge, message, object, optionNames } from "@optique/core";
11
+ import { initOptions, runInit } from "@fedify/init";
12
+ import { defineCommand } from "@optique/discover";
13
+ //#region src/commands.ts
14
+ function defineCliCommand(command) {
15
+ const { run, ...definition } = command;
16
+ return {
17
+ ...defineCommand({
18
+ ...definition,
19
+ handler: (_value) => {}
20
+ }),
21
+ run
22
+ };
23
+ }
24
+ const generatingCommands = [defineCliCommand({
25
+ path: ["init"],
26
+ parser: merge(initOptions, object({ command: constant("init") })),
27
+ metadata: {
28
+ brief: message`Initialize a new Fedify project directory.`,
29
+ description: message`Initialize a new Fedify project directory.
30
+
31
+ By default, it initializes the current directory. You can specify a different directory as an argument.
32
+
33
+ Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${optionNames(["-p", "--package-manager"])}, ${optionNames(["-k", "--kv-store"])}, and ${optionNames(["-m", "--message-queue"])}), it will prompt you to select the options interactively.`
34
+ },
35
+ run: runInit
36
+ }), defineCliCommand({
37
+ path: ["generate-vocab"],
38
+ parser: generateVocabOptions,
39
+ metadata: generateVocabMetadata,
40
+ run: async (value) => {
41
+ const { default: runGenerateVocab } = await import("./generate-vocab/action.js");
42
+ return await runGenerateVocab(value);
43
+ }
44
+ })];
45
+ const activityPubCommands = [
46
+ defineCliCommand({
47
+ path: ["webfinger"],
48
+ parser: webFingerOptions,
49
+ metadata: webFingerMetadata,
50
+ run: async (value) => {
51
+ const { default: runWebFinger } = await import("./webfinger/action.js");
52
+ return await runWebFinger(value);
53
+ }
54
+ }),
55
+ defineCliCommand({
56
+ path: ["lookup"],
57
+ parser: lookupOptions,
58
+ metadata: lookupMetadata,
59
+ run: async (value) => {
60
+ const { runLookup } = await import("./lookup.js");
61
+ return await runLookup(value);
62
+ }
63
+ }),
64
+ defineCliCommand({
65
+ path: ["inbox"],
66
+ parser: inboxOptions,
67
+ metadata: inboxMetadata,
68
+ run: async (value) => {
69
+ const { runInbox } = await import("./inbox.js");
70
+ return await runInbox(value);
71
+ }
72
+ }),
73
+ defineCliCommand({
74
+ path: ["nodeinfo"],
75
+ parser: nodeInfoOptions,
76
+ metadata: nodeInfoMetadata,
77
+ run: runNodeInfo
78
+ }),
79
+ defineCliCommand({
80
+ path: ["relay"],
81
+ parser: relayOptions,
82
+ metadata: relayMetadata,
83
+ run: async (value) => {
84
+ const { runRelay } = await import("./relay.js");
85
+ return await runRelay(value);
86
+ }
87
+ }),
88
+ defineCliCommand({
89
+ path: ["bench"],
90
+ parser: benchOptions,
91
+ metadata: benchMetadata,
92
+ run: async (value) => {
93
+ const { runBench } = await import("./bench/mod.js");
94
+ return await runBench(value);
95
+ }
96
+ })
97
+ ];
98
+ const networkCommands = [defineCliCommand({
99
+ path: ["tunnel"],
100
+ parser: tunnelOptions,
101
+ metadata: tunnelMetadata,
102
+ run: runTunnel
103
+ })];
104
+ [
105
+ ...generatingCommands,
106
+ ...activityPubCommands,
107
+ ...networkCommands
108
+ ];
109
+ //#endregion
110
+ export { activityPubCommands, generatingCommands, networkCommands };
package/dist/config.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
- import { printError } from "@optique/run";
3
2
  import { message } from "@optique/core";
4
- import { createConfigContext } from "@optique/config";
3
+ import { printError } from "@optique/run";
5
4
  import { readFileSync } from "node:fs";
6
5
  import { parse } from "smol-toml";
6
+ import { createConfigContext } from "@optique/config";
7
7
  import { array, boolean, check, forward, integer as integer$1, minValue, number, object as object$1, optional as optional$1, picklist, pipe, string as string$1 } from "valibot";
8
8
  //#region src/config.ts
9
9
  /**
@@ -76,6 +76,17 @@ const nodeinfoSchema = object$1({
76
76
  showMetadata: optional$1(boolean())
77
77
  });
78
78
  /**
79
+ * Schema for the bench command configuration.
80
+ *
81
+ * `allowUnsafeTarget` is intentionally absent: the unsafe-target override is a
82
+ * CLI-only, per-run acknowledgment, never a persisted default.
83
+ */
84
+ const benchSchema = object$1({ format: optional$1(picklist([
85
+ "text",
86
+ "json",
87
+ "markdown"
88
+ ])) });
89
+ /**
79
90
  * Config context for use with bindConfig().
80
91
  */
81
92
  const configContext = createConfigContext({ schema: object$1({
@@ -90,7 +101,8 @@ const configContext = createConfigContext({ schema: object$1({
90
101
  lookup: optional$1(lookupSchema),
91
102
  inbox: optional$1(inboxSchema),
92
103
  relay: optional$1(relaySchema),
93
- nodeinfo: optional$1(nodeinfoSchema)
104
+ nodeinfo: optional$1(nodeinfoSchema),
105
+ bench: optional$1(benchSchema)
94
106
  }) });
95
107
  /**
96
108
  * Try to load and parse a TOML config file.
package/dist/deno.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  //#region deno.json
3
- var version = "2.3.0-dev.994+9071ca0a";
3
+ var version = "2.4.0-dev.1417+43d75b89";
4
4
  //#endregion
5
5
  export { version };
package/dist/docloader.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "@js-temporal/polyfill";
2
- import { kvCache } from "@fedify/fedify";
3
2
  import { getDocumentLoader } from "@fedify/vocab-runtime";
3
+ import { kvCache } from "@fedify/fedify";
4
4
  import { getKvStore } from "#kv";
5
5
  //#region src/docloader.ts
6
6
  const documentLoaders = {};
@@ -1,9 +1,9 @@
1
1
  import "@js-temporal/polyfill";
2
- import { generateVocab } from "@fedify/vocab-tools";
3
- import { message } from "@optique/core/message";
2
+ import process from "node:process";
4
3
  import { printError } from "@optique/run";
5
4
  import { stat } from "node:fs/promises";
6
- import process from "node:process";
5
+ import { generateVocab } from "@fedify/vocab-tools";
6
+ import { message } from "@optique/core/message";
7
7
  //#region src/generate-vocab/action.ts
8
8
  async function runGenerateVocab({ schemaDir, generatedPath }) {
9
9
  if (!(await stat(schemaDir)).isDirectory()) {
@@ -1,6 +1,6 @@
1
1
  import "@js-temporal/polyfill";
2
- import { path } from "@optique/run";
3
2
  import { argument, command, constant, message, object, option, withDefault } from "@optique/core";
3
+ import { path } from "@optique/run";
4
4
  //#region src/generate-vocab/command.ts
5
5
  const schemaDir = withDefault(option("-i", "--input", path({
6
6
  metavar: "DIR",
@@ -12,10 +12,12 @@ const generatedPath = argument(path({
12
12
  type: "file",
13
13
  allowCreate: true
14
14
  }), { description: message`Path to output the generated vocabulary classes. Should end with ${".ts"} suffix.` });
15
- const generateVocabCommand = command("generate-vocab", object("Generation options", {
15
+ const generateVocabOptions = object("Generation options", {
16
16
  command: constant("generate-vocab"),
17
17
  schemaDir,
18
18
  generatedPath
19
- }), { description: message`Generate vocabulary classes from schema files.` });
19
+ });
20
+ const generateVocabMetadata = { description: message`Generate vocabulary classes from schema files.` };
21
+ command("generate-vocab", generateVocabOptions, generateVocabMetadata);
20
22
  //#endregion
21
- export { generateVocabCommand as default };
23
+ export { generateVocabMetadata, generateVocabOptions };
@@ -1,11 +1,11 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import { Jimp } from "./nodeinfo.js";
3
- import fs from "node:fs/promises";
4
3
  import process from "node:process";
5
- import { validatePublicUrl } from "@fedify/vocab-runtime";
4
+ import os from "node:os";
6
5
  import path from "node:path";
6
+ import { validatePublicUrl } from "@fedify/vocab-runtime";
7
+ import fs from "node:fs/promises";
7
8
  import { encodeBase64 } from "byte-encodings/base64";
8
- import os from "node:os";
9
9
  //#region src/imagerenderer.ts
10
10
  const KITTY_IDENTIFIERS = [
11
11
  "kitty",
@@ -6,7 +6,7 @@ import { bindConfig } from "@optique/config";
6
6
  //#region src/inbox/command.ts
7
7
  const DEFAULT_EPHEMERAL_INBOX_NAME = "Fedify Ephemeral Inbox";
8
8
  const DEFAULT_EPHEMERAL_INBOX_SUMMARY = "An ephemeral ActivityPub inbox for testing purposes.";
9
- const inboxCommand = command("inbox", merge(object("Inbox options", {
9
+ const inboxOptions = merge(object("Inbox options", {
10
10
  command: constant("inbox"),
11
11
  follow: bindConfig(multiple(option("-f", "--follow", string({ metavar: "URI" }), { description: message`Follow the given actor. The argument can be either an actor URI or a handle. Can be specified multiple times.` })), {
12
12
  context: configContext,
@@ -33,9 +33,11 @@ const inboxCommand = command("inbox", merge(object("Inbox options", {
33
33
  key: (config) => config.inbox?.authorizedFetch ?? false,
34
34
  default: false
35
35
  })
36
- }), group("Tunnel options", createTunnelOption("inbox"))), {
36
+ }), group("Tunnel options", createTunnelOption("inbox")));
37
+ const inboxMetadata = {
37
38
  brief: message`Run an ephemeral ActivityPub inbox server.`,
38
39
  description: message`Spins up an ephemeral server that serves the ActivityPub inbox with a one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`
39
- });
40
+ };
41
+ command("inbox", inboxOptions, inboxMetadata);
40
42
  //#endregion
41
- export { inboxCommand };
43
+ export { inboxMetadata, inboxOptions };
@@ -1,9 +1,9 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { renderActivity, renderRawActivity, renderRequest, renderResponse } from "./rendercode.js";
3
+ import util from "node:util";
3
4
  import { getStatusText } from "@poppanator/http-constants";
4
5
  import { Fragment } from "hono/jsx";
5
6
  import { getSingletonHighlighter } from "shiki";
6
- import util from "node:util";
7
7
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
8
8
  //#region src/inbox/view.tsx
9
9
  const Layout = (props) => {
package/dist/inbox.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
+ import { colors, matchesActor } from "./utils.js";
3
+ import { configureLogging, recordingSink } from "./log.js";
2
4
  import { version } from "./deno.js";
3
5
  import { getDocumentLoader } from "./docloader.js";
4
6
  import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
5
- import { configureLogging, recordingSink } from "./log.js";
6
7
  import { tableStyle } from "./table.js";
7
8
  import { spawnTemporaryServer } from "./tempserver.js";
8
- import { colors, matchesActor } from "./utils.js";
9
9
  import process from "node:process";
10
10
  import { MemoryKvStore, createFederation, generateCryptoKeyPair } from "@fedify/fedify";
11
- import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
12
11
  import { getLogger } from "@logtape/logtape";
12
+ import ora from "ora";
13
+ import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
13
14
  import Table from "cli-table3";
14
15
  import { Hono } from "hono";
15
- import ora from "ora";
16
16
  import { jsx } from "hono/jsx/jsx-runtime";
17
17
  //#region src/inbox.tsx
18
18
  /** @jsx react-jsx */
package/dist/log.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
- import { mkdir } from "node:fs/promises";
3
2
  import process from "node:process";
3
+ import { dirname } from "node:path";
4
4
  import { configure, getConsoleSink } from "@logtape/logtape";
5
+ import { mkdir } from "node:fs/promises";
5
6
  import { getFileSink } from "@logtape/file";
6
7
  import { AsyncLocalStorage } from "node:async_hooks";
7
- import { dirname } from "node:path";
8
8
  //#region src/log.ts
9
9
  function getRecordingSink() {
10
10
  let records = [];
@@ -0,0 +1,121 @@
1
+ import "@js-temporal/polyfill";
2
+ import { configContext } from "../config.js";
3
+ import { createTunnelServiceOption, userAgentOption } from "../options.js";
4
+ import { argument, choice, command, constant, flag, float, integer, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
5
+ import { path } from "@optique/run";
6
+ import { bindConfig } from "@optique/config";
7
+ //#region src/lookup/command.ts
8
+ const IN_REPLY_TO_IRI = "https://www.w3.org/ns/activitystreams#inReplyTo";
9
+ const QUOTE_IRI = "https://w3id.org/fep/044f#quote";
10
+ const QUOTE_URL_IRI = "https://www.w3.org/ns/activitystreams#quoteUrl";
11
+ const MISSKEY_QUOTE_IRI = "https://misskey-hub.net/ns#_misskey_quote";
12
+ const FEDIBIRD_QUOTE_IRI = "http://fedibird.com/ns#quoteUri";
13
+ const recurseProperties = [
14
+ "replyTarget",
15
+ "quote",
16
+ "quoteUrl",
17
+ IN_REPLY_TO_IRI,
18
+ QUOTE_IRI,
19
+ QUOTE_URL_IRI,
20
+ MISSKEY_QUOTE_IRI,
21
+ FEDIBIRD_QUOTE_IRI
22
+ ];
23
+ const suppressErrorsOption = bindConfig(flag("-S", "--suppress-errors", { description: message`Suppress partial errors during traversal or recursion.` }), {
24
+ context: configContext,
25
+ key: (config) => config.lookup?.suppressErrors ?? false,
26
+ default: false
27
+ });
28
+ const allowPrivateAddressOption = bindConfig(flag("-p", "--allow-private-address", { description: message`Allow private IP addresses for URLs discovered \
29
+ during traversal or recursive object fetches. Recursive JSON-LD \
30
+ context URLs always remain blocked. URLs explicitly provided on the \
31
+ command line always allow private addresses.` }), {
32
+ context: configContext,
33
+ key: (config) => config.lookup?.allowPrivateAddress ?? false,
34
+ default: false
35
+ });
36
+ const authorizedFetchOption = withDefault(object("Authorized fetch options", {
37
+ authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }), () => true), {
38
+ context: configContext,
39
+ key: (config) => config.lookup?.authorizedFetch ? true : void 0
40
+ }),
41
+ 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.` }), {
42
+ context: configContext,
43
+ key: (config) => config.lookup?.firstKnock ?? "draft-cavage-http-signatures-12",
44
+ default: "draft-cavage-http-signatures-12"
45
+ }),
46
+ tunnelService: optional(createTunnelServiceOption())
47
+ }), {
48
+ authorizedFetch: false,
49
+ firstKnock: void 0,
50
+ tunnelService: void 0
51
+ });
52
+ const lookupModeOption = withDefault(or(object("Recurse options", {
53
+ traverse: constant(false),
54
+ recurse: bindConfig(option("--recurse", choice(recurseProperties, { metavar: "PROPERTY" }), { description: message`Recursively follow a relationship property.` }), {
55
+ context: configContext,
56
+ key: (config) => config.lookup?.recurse
57
+ }),
58
+ recurseDepth: bindConfig(option("--recurse-depth", integer({
59
+ min: 1,
60
+ metavar: "DEPTH"
61
+ }), { description: message`Maximum recursion depth for ${optionNames(["--recurse"])}.` }), {
62
+ context: configContext,
63
+ key: (config) => config.lookup?.recurseDepth,
64
+ default: 20
65
+ }),
66
+ suppressErrors: suppressErrorsOption
67
+ }), object("Traverse options", {
68
+ traverse: bindConfig(flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }), {
69
+ context: configContext,
70
+ key: (config) => config.lookup?.traverse ?? false,
71
+ default: false
72
+ }),
73
+ recurse: constant(void 0),
74
+ recurseDepth: constant(void 0),
75
+ suppressErrors: suppressErrorsOption
76
+ })), {
77
+ traverse: false,
78
+ recurse: void 0,
79
+ recurseDepth: void 0,
80
+ suppressErrors: false
81
+ });
82
+ const lookupOptions = merge(object({ command: constant("lookup") }), lookupModeOption, authorizedFetchOption, merge("Network options", userAgentOption, object({
83
+ allowPrivateAddress: allowPrivateAddressOption,
84
+ timeout: optional(bindConfig(option("-T", "--timeout", float({
85
+ min: 0,
86
+ metavar: "SECONDS"
87
+ }), { description: message`Set timeout for network requests in seconds.` }), {
88
+ context: configContext,
89
+ key: (config) => config.lookup?.timeout
90
+ }))
91
+ })), 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", {
92
+ reverse: bindConfig(flag("--reverse", { description: message`Reverse the output order of fetched objects or items.` }), {
93
+ context: configContext,
94
+ key: (config) => config.lookup?.reverse ?? false,
95
+ default: false
96
+ }),
97
+ 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"))), {
98
+ context: configContext,
99
+ key: (config) => config.lookup?.defaultFormat ?? "default",
100
+ default: "default"
101
+ }),
102
+ separator: bindConfig(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message`Specify the separator between adjacent output objects or collection items.` }), {
103
+ context: configContext,
104
+ key: (config) => config.lookup?.separator ?? "----",
105
+ default: "----"
106
+ }),
107
+ output: optional(option("-o", "--output", path({
108
+ metavar: "OUTPUT_PATH",
109
+ type: "file",
110
+ allowCreate: true
111
+ }), { description: message`Specify the output file path.` }))
112
+ }));
113
+ const lookupMetadata = {
114
+ brief: message`Look up Activity Streams objects.`,
115
+ description: message`Look up Activity Streams objects by URL or actor handle.
116
+
117
+ The arguments can be either URLs or actor handles (e.g., ${"@username@domain"}), and they can be multiple.`
118
+ };
119
+ command("lookup", lookupOptions, lookupMetadata);
120
+ //#endregion
121
+ export { FEDIBIRD_QUOTE_IRI, IN_REPLY_TO_IRI, MISSKEY_QUOTE_IRI, QUOTE_IRI, QUOTE_URL_IRI, authorizedFetchOption, lookupMetadata, lookupOptions };