@fedify/cli 2.3.0-dev.1219 → 2.3.0-dev.1273
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bench/action.js +309 -0
- package/dist/bench/actor/documents.js +39 -0
- package/dist/bench/actor/fleet.js +39 -0
- package/dist/bench/actor/keys.js +35 -0
- package/dist/bench/command.js +42 -0
- package/dist/bench/discovery/discover.js +67 -0
- package/dist/bench/discovery/probe.js +50 -0
- package/dist/bench/load/arrival.js +27 -0
- package/dist/bench/load/clock.js +15 -0
- package/dist/bench/load/generator.js +112 -0
- package/dist/bench/metrics/aggregate.js +64 -0
- package/dist/bench/metrics/histogram.js +141 -0
- package/dist/bench/metrics/stats-client.js +154 -0
- package/dist/bench/mod.js +4 -0
- package/dist/bench/render/format.js +46 -0
- package/dist/bench/render/index.js +20 -0
- package/dist/bench/render/json.js +12 -0
- package/dist/bench/render/markdown.js +62 -0
- package/dist/bench/render/text.js +74 -0
- package/dist/bench/result/build.js +129 -0
- package/dist/bench/result/expect/assert.js +74 -0
- package/dist/bench/result/expect/evaluate.js +128 -0
- package/dist/bench/result/expect/metrics.js +34 -0
- package/dist/bench/result/schema.js +15 -0
- package/dist/bench/safety/gate.js +60 -0
- package/dist/bench/safety/tiers.js +97 -0
- package/dist/bench/scenario/coerce.js +24 -0
- package/dist/bench/scenario/errors.js +36 -0
- package/dist/bench/scenario/load.js +69 -0
- package/dist/bench/scenario/normalize.js +126 -0
- package/dist/bench/scenario/schema.js +358 -0
- package/dist/bench/scenario/units.js +56 -0
- package/dist/bench/scenario/validate.js +29 -0
- package/dist/bench/scenarios/inbox.js +155 -0
- package/dist/bench/scenarios/registry.js +21 -0
- package/dist/bench/scenarios/runner.js +76 -0
- package/dist/bench/scenarios/webfinger.js +44 -0
- package/dist/bench/server/synthetic.js +118 -0
- package/dist/bench/signing/activity-id.js +18 -0
- package/dist/bench/signing/pipeline.js +134 -0
- package/dist/bench/signing/signer.js +39 -0
- package/dist/bench/template/generate.js +90 -0
- package/dist/bench/template/helpers.js +19 -0
- package/dist/bench/template/template.js +132 -0
- package/dist/cache.js +1 -1
- package/dist/config.js +14 -2
- package/dist/deno.js +1 -1
- package/dist/generate-vocab/action.js +3 -3
- package/dist/generate-vocab/command.js +1 -1
- package/dist/imagerenderer.js +1 -1
- package/dist/inbox/view.js +1 -1
- package/dist/inbox.js +2 -2
- package/dist/lookup.js +37 -37
- package/dist/mod.js +3 -0
- package/dist/nodeinfo.js +5 -5
- package/dist/options.js +1 -1
- package/dist/relay.js +1 -1
- package/dist/runner.js +10 -8
- package/dist/tempserver.js +1 -1
- package/dist/tunnel.js +2 -2
- package/dist/utils.js +8 -4
- package/dist/webfinger/action.js +1 -1
- package/package.json +12 -10
|
@@ -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
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import { join } from "node:path";
|
|
5
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
6
|
//#region src/cache.ts
|
|
7
7
|
/**
|
|
8
8
|
* Returns the default cache directory path.
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { printError } from "@optique/run";
|
|
3
2
|
import { message } from "@optique/core";
|
|
3
|
+
import { printError } from "@optique/run";
|
|
4
4
|
import { createConfigContext } from "@optique/config";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { parse } from "smol-toml";
|
|
@@ -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,9 +1,9 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import { generateVocab } from "@fedify/vocab-tools";
|
|
3
|
-
import { message } from "@optique/core/message";
|
|
4
|
-
import { printError } from "@optique/run";
|
|
5
2
|
import { stat } from "node:fs/promises";
|
|
6
3
|
import process from "node:process";
|
|
4
|
+
import { printError } from "@optique/run";
|
|
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",
|
package/dist/imagerenderer.js
CHANGED
|
@@ -3,9 +3,9 @@ import { Jimp } from "./nodeinfo.js";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
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",
|
package/dist/inbox/view.js
CHANGED
|
@@ -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,11 +1,11 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
-
import { version } from "./deno.js";
|
|
3
2
|
import { getDocumentLoader } from "./docloader.js";
|
|
3
|
+
import { colors, matchesActor } from "./utils.js";
|
|
4
|
+
import { version } from "./deno.js";
|
|
4
5
|
import { ActivityEntryPage, ActivityListPage } from "./inbox/view.js";
|
|
5
6
|
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
11
|
import { Accept, Activity, Application, Delete, Endpoints, Follow, Image, PUBLIC_COLLECTION, isActor, lookupObject } from "@fedify/vocab";
|
package/dist/lookup.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { getContextLoader, getDocumentLoader as getDocumentLoader$1 } from "./docloader.js";
|
|
3
|
-
import {
|
|
4
|
-
import { spawnTemporaryServer } from "./tempserver.js";
|
|
5
|
-
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
3
|
+
import { colorEnabled, colors, describeError, formatObject } from "./utils.js";
|
|
6
4
|
import { configContext } from "./config.js";
|
|
7
5
|
import { createTunnelServiceOption, userAgentOption } from "./options.js";
|
|
6
|
+
import { configureLogging } from "./log.js";
|
|
7
|
+
import { spawnTemporaryServer } from "./tempserver.js";
|
|
8
8
|
import { renderImages } from "./imagerenderer.js";
|
|
9
|
-
import { url } from "@optique/core/message";
|
|
10
|
-
import { path, printError } from "@optique/run";
|
|
11
9
|
import process from "node:process";
|
|
12
|
-
import { argument, choice, command, constant, flag, float, integer, map, merge, message as message$1, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
13
10
|
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
11
|
+
import { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address } from "@fedify/vocab-runtime";
|
|
14
12
|
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
13
|
+
import { argument, choice, command, constant, flag, float, integer, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
14
|
+
import { path, printError } from "@optique/run";
|
|
15
15
|
import { getLogger } from "@logtape/logtape";
|
|
16
|
-
import ora from "ora";
|
|
17
|
-
import { UrlError, expandIPv6Address, isValidPublicIPv4Address, isValidPublicIPv6Address } from "@fedify/vocab-runtime";
|
|
18
16
|
import { bindConfig } from "@optique/config";
|
|
19
17
|
import { createWriteStream } from "node:fs";
|
|
18
|
+
import { url } from "@optique/core/message";
|
|
19
|
+
import ora from "ora";
|
|
20
20
|
import { isIP } from "node:net";
|
|
21
21
|
//#region src/lookup.ts
|
|
22
22
|
const logger = getLogger([
|
|
@@ -39,12 +39,12 @@ const recurseProperties = [
|
|
|
39
39
|
MISSKEY_QUOTE_IRI,
|
|
40
40
|
FEDIBIRD_QUOTE_IRI
|
|
41
41
|
];
|
|
42
|
-
const suppressErrorsOption = bindConfig(flag("-S", "--suppress-errors", { description: message
|
|
42
|
+
const suppressErrorsOption = bindConfig(flag("-S", "--suppress-errors", { description: message`Suppress partial errors during traversal or recursion.` }), {
|
|
43
43
|
context: configContext,
|
|
44
44
|
key: (config) => config.lookup?.suppressErrors ?? false,
|
|
45
45
|
default: false
|
|
46
46
|
});
|
|
47
|
-
const allowPrivateAddressOption = bindConfig(flag("-p", "--allow-private-address", { description: message
|
|
47
|
+
const allowPrivateAddressOption = bindConfig(flag("-p", "--allow-private-address", { description: message`Allow private IP addresses for URLs discovered \
|
|
48
48
|
during traversal or recursive object fetches. Recursive JSON-LD \
|
|
49
49
|
context URLs always remain blocked. URLs explicitly provided on the \
|
|
50
50
|
command line always allow private addresses.` }), {
|
|
@@ -53,11 +53,11 @@ command line always allow private addresses.` }), {
|
|
|
53
53
|
default: false
|
|
54
54
|
});
|
|
55
55
|
const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
56
|
-
authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message
|
|
56
|
+
authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }), () => true), {
|
|
57
57
|
context: configContext,
|
|
58
58
|
key: (config) => config.lookup?.authorizedFetch ? true : void 0
|
|
59
59
|
}),
|
|
60
|
-
firstKnock: bindConfig(option("--first-knock", choice(["draft-cavage-http-signatures-12", "rfc9421"]), { description: message
|
|
60
|
+
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.` }), {
|
|
61
61
|
context: configContext,
|
|
62
62
|
key: (config) => config.lookup?.firstKnock ?? "draft-cavage-http-signatures-12",
|
|
63
63
|
default: "draft-cavage-http-signatures-12"
|
|
@@ -70,21 +70,21 @@ const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
|
70
70
|
});
|
|
71
71
|
const lookupModeOption = withDefault(or(object("Recurse options", {
|
|
72
72
|
traverse: constant(false),
|
|
73
|
-
recurse: bindConfig(option("--recurse", choice(recurseProperties, { metavar: "PROPERTY" }), { description: message
|
|
73
|
+
recurse: bindConfig(option("--recurse", choice(recurseProperties, { metavar: "PROPERTY" }), { description: message`Recursively follow a relationship property.` }), {
|
|
74
74
|
context: configContext,
|
|
75
75
|
key: (config) => config.lookup?.recurse
|
|
76
76
|
}),
|
|
77
77
|
recurseDepth: bindConfig(option("--recurse-depth", integer({
|
|
78
78
|
min: 1,
|
|
79
79
|
metavar: "DEPTH"
|
|
80
|
-
}), { description: message
|
|
80
|
+
}), { description: message`Maximum recursion depth for ${optionNames(["--recurse"])}.` }), {
|
|
81
81
|
context: configContext,
|
|
82
82
|
key: (config) => config.lookup?.recurseDepth,
|
|
83
83
|
default: 20
|
|
84
84
|
}),
|
|
85
85
|
suppressErrors: suppressErrorsOption
|
|
86
86
|
}), object("Traverse options", {
|
|
87
|
-
traverse: bindConfig(flag("-t", "--traverse", { description: message
|
|
87
|
+
traverse: bindConfig(flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }), {
|
|
88
88
|
context: configContext,
|
|
89
89
|
key: (config) => config.lookup?.traverse ?? false,
|
|
90
90
|
default: false
|
|
@@ -103,22 +103,22 @@ const lookupCommand = command("lookup", merge(object({ command: constant("lookup
|
|
|
103
103
|
timeout: optional(bindConfig(option("-T", "--timeout", float({
|
|
104
104
|
min: 0,
|
|
105
105
|
metavar: "SECONDS"
|
|
106
|
-
}), { description: message
|
|
106
|
+
}), { description: message`Set timeout for network requests in seconds.` }), {
|
|
107
107
|
context: configContext,
|
|
108
108
|
key: (config) => config.lookup?.timeout
|
|
109
109
|
}))
|
|
110
|
-
})), object("Arguments", { urls: multiple(argument(string({ metavar: "URL_OR_HANDLE" }), { description: message
|
|
111
|
-
reverse: bindConfig(flag("--reverse", { description: message
|
|
110
|
+
})), 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", {
|
|
111
|
+
reverse: bindConfig(flag("--reverse", { description: message`Reverse the output order of fetched objects or items.` }), {
|
|
112
112
|
context: configContext,
|
|
113
113
|
key: (config) => config.lookup?.reverse ?? false,
|
|
114
114
|
default: false
|
|
115
115
|
}),
|
|
116
|
-
format: bindConfig(optional(or(map(flag("-r", "--raw", { description: message
|
|
116
|
+
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"))), {
|
|
117
117
|
context: configContext,
|
|
118
118
|
key: (config) => config.lookup?.defaultFormat ?? "default",
|
|
119
119
|
default: "default"
|
|
120
120
|
}),
|
|
121
|
-
separator: bindConfig(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message
|
|
121
|
+
separator: bindConfig(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message`Specify the separator between adjacent output objects or collection items.` }), {
|
|
122
122
|
context: configContext,
|
|
123
123
|
key: (config) => config.lookup?.separator ?? "----",
|
|
124
124
|
default: "----"
|
|
@@ -127,10 +127,10 @@ const lookupCommand = command("lookup", merge(object({ command: constant("lookup
|
|
|
127
127
|
metavar: "OUTPUT_PATH",
|
|
128
128
|
type: "file",
|
|
129
129
|
allowCreate: true
|
|
130
|
-
}), { description: message
|
|
130
|
+
}), { description: message`Specify the output file path.` }))
|
|
131
131
|
})), {
|
|
132
|
-
brief: message
|
|
133
|
-
description: message
|
|
132
|
+
brief: message`Look up Activity Streams objects.`,
|
|
133
|
+
description: message`Look up Activity Streams objects by URL or actor handle.
|
|
134
134
|
|
|
135
135
|
The arguments can be either URLs or actor handles (e.g., ${"@username@domain"}), and they can be multiple.`
|
|
136
136
|
});
|
|
@@ -282,10 +282,10 @@ function wrapDocumentLoaderWithTimeout(loader, timeoutSeconds) {
|
|
|
282
282
|
function handleTimeoutError(spinner, timeoutSeconds, url) {
|
|
283
283
|
const urlText = url ? ` for: ${colors.red(url)}` : "";
|
|
284
284
|
spinner.fail(`Request timed out after ${timeoutSeconds} seconds${urlText}.`);
|
|
285
|
-
printError(message
|
|
285
|
+
printError(message`Try increasing the timeout with ${optionNames(["-T", "--timeout"])} option or check network connectivity.`);
|
|
286
286
|
}
|
|
287
287
|
function isPrivateAddressError(error) {
|
|
288
|
-
const lowerMessage = (error
|
|
288
|
+
const lowerMessage = describeError(error).toLowerCase();
|
|
289
289
|
if (error instanceof UrlError) return lowerMessage.includes("invalid or private address") || lowerMessage.includes("localhost is not allowed");
|
|
290
290
|
return lowerMessage.includes("private address") || lowerMessage.includes("private ip") || lowerMessage.includes("localhost") || lowerMessage.includes("loopback");
|
|
291
291
|
}
|
|
@@ -308,7 +308,7 @@ function isPrivateAddressTarget(target) {
|
|
|
308
308
|
return getPrivateUrlCandidate(target) != null;
|
|
309
309
|
}
|
|
310
310
|
function getPrivateContextUrl(error) {
|
|
311
|
-
const errorMessage =
|
|
311
|
+
const errorMessage = describeError(error);
|
|
312
312
|
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl" || !errorMessage.includes("valid JSON-LD object")) return null;
|
|
313
313
|
const structuredError = error;
|
|
314
314
|
const structuredUrl = getPrivateUrlCandidate(structuredError.details?.url) ?? getPrivateUrlCandidate(structuredError.url);
|
|
@@ -318,10 +318,10 @@ function getPrivateContextUrl(error) {
|
|
|
318
318
|
return getPrivateUrlCandidate(match[1]);
|
|
319
319
|
}
|
|
320
320
|
function printRecursivePrivateAddressHint() {
|
|
321
|
-
printError(message
|
|
321
|
+
printError(message`The recursive target appears to be private or localhost. Try with ${optionNames(["-p", "--allow-private-address"])}, or use ${optionNames(["-S", "--suppress-errors"])} to skip blocked steps.`);
|
|
322
322
|
}
|
|
323
323
|
function printRecursivePrivateContextHint(privateContextUrl) {
|
|
324
|
-
printError(message
|
|
324
|
+
printError(message`Recursive JSON-LD context URL ${url(privateContextUrl)} is always blocked, even with ${optionNames(["-p", "--allow-private-address"])}. Use ${optionNames(["-S", "--suppress-errors"])} to skip blocked steps.`);
|
|
325
325
|
}
|
|
326
326
|
function getLookupFailureHint(error, options = {}) {
|
|
327
327
|
if (isPrivateAddressError(error)) return options.recursive ? "recursive-private-address" : "private-address";
|
|
@@ -338,13 +338,13 @@ function printLookupFailureHint(authLoader, error, options = {}) {
|
|
|
338
338
|
if (!shouldPrintLookupFailureHint(authLoader, hint)) return;
|
|
339
339
|
switch (hint) {
|
|
340
340
|
case "private-address":
|
|
341
|
-
printError(message
|
|
341
|
+
printError(message`The URL appears to be private or localhost. Try with ${optionNames(["-p", "--allow-private-address"])}.`);
|
|
342
342
|
return;
|
|
343
343
|
case "recursive-private-address":
|
|
344
344
|
printRecursivePrivateAddressHint();
|
|
345
345
|
return;
|
|
346
346
|
case "authorized-fetch":
|
|
347
|
-
printError(message
|
|
347
|
+
printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
350
|
}
|
|
@@ -418,7 +418,7 @@ async function runLookup(command, deps = {}) {
|
|
|
418
418
|
...deps
|
|
419
419
|
};
|
|
420
420
|
if (command.urls.length < 1) {
|
|
421
|
-
printError(message
|
|
421
|
+
printError(message`At least one URL or actor handle must be provided.`);
|
|
422
422
|
effectiveDeps.exit(1);
|
|
423
423
|
}
|
|
424
424
|
if (command.debug) await configureLogging();
|
|
@@ -559,7 +559,7 @@ async function runLookup(command, deps = {}) {
|
|
|
559
559
|
}
|
|
560
560
|
if (current == null) {
|
|
561
561
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
562
|
-
if (authLoader == null) printError(message
|
|
562
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
563
563
|
await finalizeAndExit(1);
|
|
564
564
|
return;
|
|
565
565
|
}
|
|
@@ -601,7 +601,7 @@ async function runLookup(command, deps = {}) {
|
|
|
601
601
|
else if (error instanceof RecursiveLookupError) {
|
|
602
602
|
spinner.fail(`Failed to recursively fetch object: ${colors.red(error.target)}.`);
|
|
603
603
|
if (!command.allowPrivateAddress && isPrivateAddressTarget(error.target)) printRecursivePrivateAddressHint();
|
|
604
|
-
else if (authLoader == null) printError(message
|
|
604
|
+
else if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
605
605
|
} else {
|
|
606
606
|
spinner.fail("Failed to recursively fetch object.");
|
|
607
607
|
const privateContextUrl = getPrivateContextUrl(error);
|
|
@@ -611,7 +611,7 @@ async function runLookup(command, deps = {}) {
|
|
|
611
611
|
return;
|
|
612
612
|
}
|
|
613
613
|
const hint = getLookupFailureHint(error, { recursive: true });
|
|
614
|
-
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message
|
|
614
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the ${optionNames(["-S", "--suppress-errors"])} option to suppress partial errors.`);
|
|
615
615
|
else printLookupFailureHint(authLoader, error, { recursive: true });
|
|
616
616
|
}
|
|
617
617
|
await finalizeAndExit(1);
|
|
@@ -685,7 +685,7 @@ async function runLookup(command, deps = {}) {
|
|
|
685
685
|
}
|
|
686
686
|
if (collection == null) {
|
|
687
687
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
688
|
-
if (authLoader == null) printError(message
|
|
688
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
689
689
|
await finalizeAndExit(1);
|
|
690
690
|
return;
|
|
691
691
|
}
|
|
@@ -747,7 +747,7 @@ async function runLookup(command, deps = {}) {
|
|
|
747
747
|
else {
|
|
748
748
|
spinner.fail(`Failed to complete the traversal for: ${colors.red(url)}.`);
|
|
749
749
|
const hint = getLookupFailureHint(error);
|
|
750
|
-
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message
|
|
750
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the ${optionNames(["-S", "--suppress-errors"])} option to suppress partial errors.`);
|
|
751
751
|
else printLookupFailureHint(authLoader, error);
|
|
752
752
|
}
|
|
753
753
|
await finalizeAndExit(1);
|
|
@@ -782,7 +782,7 @@ async function runLookup(command, deps = {}) {
|
|
|
782
782
|
const url = command.urls[i];
|
|
783
783
|
if (obj == null) {
|
|
784
784
|
spinner.fail(`Failed to fetch ${colors.red(url)}`);
|
|
785
|
-
if (authLoader == null) printError(message
|
|
785
|
+
if (authLoader == null) printError(message`It may be a private object. Try with ${optionNames(["-a", "--authorized-fetch"])}.`);
|
|
786
786
|
success = false;
|
|
787
787
|
} else {
|
|
788
788
|
spinner.succeed(`Fetched object: ${colors.green(url)}`);
|
package/dist/mod.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import "@js-temporal/polyfill";
|
|
3
|
+
import runBench from "./bench/action.js";
|
|
4
|
+
import "./bench/mod.js";
|
|
3
5
|
import runGenerateVocab from "./generate-vocab/action.js";
|
|
4
6
|
import "./generate-vocab/mod.js";
|
|
5
7
|
import { runInbox } from "./inbox.js";
|
|
@@ -23,6 +25,7 @@ async function main() {
|
|
|
23
25
|
else if (result.command === "tunnel") await runTunnel(result);
|
|
24
26
|
else if (result.command === "generate-vocab") await runGenerateVocab(result);
|
|
25
27
|
else if (result.command === "relay") await runRelay(result);
|
|
28
|
+
else if (result.command === "bench") await runBench(result);
|
|
26
29
|
}
|
|
27
30
|
await main();
|
|
28
31
|
//#endregion
|
package/dist/nodeinfo.js
CHANGED
|
@@ -2,15 +2,15 @@ import "@js-temporal/polyfill";
|
|
|
2
2
|
import { colors, formatObject } from "./utils.js";
|
|
3
3
|
import { configContext } from "./config.js";
|
|
4
4
|
import { userAgentOption } from "./options.js";
|
|
5
|
-
import { print, printError } from "@optique/run";
|
|
6
5
|
import process from "node:process";
|
|
7
|
-
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
8
6
|
import { getNodeInfo } from "@fedify/fedify";
|
|
9
|
-
import { getLogger } from "@logtape/logtape";
|
|
10
|
-
import ora from "ora";
|
|
11
7
|
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
12
|
-
import {
|
|
8
|
+
import { argument, command, constant, flag, group, merge, message, object, string, text } from "@optique/core";
|
|
9
|
+
import { print, printError } from "@optique/run";
|
|
10
|
+
import { getLogger } from "@logtape/logtape";
|
|
13
11
|
import os from "node:os";
|
|
12
|
+
import { bindConfig } from "@optique/config";
|
|
13
|
+
import ora from "ora";
|
|
14
14
|
import { createJimp } from "@jimp/core";
|
|
15
15
|
import webp from "@jimp/wasm-webp";
|
|
16
16
|
import { isICO, parseICO } from "icojs";
|
package/dist/options.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import { configContext } from "./config.js";
|
|
3
|
-
import { choice, constant, flag, map, merge, message, object, option, optional, or, string, withDefault } from "@optique/core";
|
|
4
3
|
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
4
|
+
import { choice, constant, flag, map, merge, message, object, option, optional, or, string, withDefault } from "@optique/core";
|
|
5
5
|
import { bindConfig } from "@optique/config";
|
|
6
6
|
//#region src/options.ts
|
|
7
7
|
/**
|