@fedify/cli 1.8.11 → 2.0.0-dev.1757
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/deno.json +72 -0
- package/dist/cache.js +17 -0
- package/dist/deno.js +72 -0
- package/dist/docloader.js +52 -0
- package/dist/globals.js +49 -0
- package/dist/imagerenderer.js +105 -0
- package/dist/inbox/rendercode.js +57 -0
- package/dist/inbox/view.js +508 -0
- package/dist/inbox.js +315 -0
- package/dist/init/action/configs.js +81 -0
- package/dist/init/action/deps.js +52 -0
- package/dist/init/action/dir.js +16 -0
- package/dist/init/action/env.js +13 -0
- package/dist/init/action/install.js +22 -0
- package/dist/init/action/mod.js +39 -0
- package/dist/init/action/notice.js +62 -0
- package/dist/init/action/patch.js +141 -0
- package/dist/init/action/precommand.js +23 -0
- package/dist/init/action/recommend.js +24 -0
- package/dist/init/action/set.js +31 -0
- package/dist/init/action/templates.js +57 -0
- package/dist/init/action/utils.js +50 -0
- package/dist/init/ask/dir.js +82 -0
- package/dist/init/ask/kv.js +33 -0
- package/dist/init/ask/mod.js +16 -0
- package/dist/init/ask/mq.js +33 -0
- package/dist/init/ask/pm.js +49 -0
- package/dist/init/ask/wf.js +29 -0
- package/dist/init/command.js +25 -0
- package/dist/init/const.js +31 -0
- package/dist/init/json/biome.js +24 -0
- package/dist/init/json/kv.js +53 -0
- package/dist/init/json/mq.js +72 -0
- package/dist/init/json/pm.js +44 -0
- package/dist/init/json/rt.js +39 -0
- package/dist/init/json/vscode-settings-for-deno.js +53 -0
- package/dist/init/json/vscode-settings.js +49 -0
- package/dist/init/lib.js +129 -0
- package/dist/init/mod.js +5 -0
- package/dist/init/webframeworks.js +133 -0
- package/dist/kv.bun.js +17 -0
- package/dist/kv.node.js +17 -0
- package/dist/log.js +52 -0
- package/dist/lookup.js +287 -0
- package/dist/mod.js +34 -0
- package/dist/nodeinfo.js +261 -0
- package/dist/table.js +24 -0
- package/dist/tempserver.js +71 -0
- package/dist/tunnel.js +21 -0
- package/dist/utils.js +67 -0
- package/dist/webfinger/action.js +44 -0
- package/dist/webfinger/command.js +20 -0
- package/dist/webfinger/error.js +47 -0
- package/dist/webfinger/lib.js +45 -0
- package/dist/webfinger/mod.js +5 -0
- package/package.json +64 -24
- package/scripts/pack.ts +64 -0
- package/src/cache.ts +17 -0
- package/src/docloader.ts +67 -0
- package/src/globals.ts +43 -0
- package/src/imagerenderer.ts +149 -0
- package/src/inbox/entry.ts +10 -0
- package/src/inbox/rendercode.ts +68 -0
- package/src/inbox/view.tsx +598 -0
- package/src/inbox.tsx +535 -0
- package/src/init/action/configs.ts +88 -0
- package/src/init/action/deps.ts +93 -0
- package/src/init/action/dir.ts +11 -0
- package/src/init/action/env.ts +14 -0
- package/src/init/action/install.ts +59 -0
- package/src/init/action/mod.ts +66 -0
- package/src/init/action/notice.ts +101 -0
- package/src/init/action/patch.ts +212 -0
- package/src/init/action/precommand.ts +22 -0
- package/src/init/action/recommend.ts +38 -0
- package/src/init/action/set.ts +78 -0
- package/src/init/action/templates.ts +95 -0
- package/src/init/action/utils.ts +64 -0
- package/src/init/ask/dir.ts +98 -0
- package/src/init/ask/kv.ts +39 -0
- package/src/init/ask/mod.ts +23 -0
- package/src/init/ask/mq.ts +37 -0
- package/src/init/ask/pm.ts +58 -0
- package/src/init/ask/wf.ts +27 -0
- package/src/init/command.ts +64 -0
- package/src/init/const.ts +4 -0
- package/src/init/json/biome.json +17 -0
- package/src/init/json/kv.json +39 -0
- package/src/init/json/mq.json +95 -0
- package/src/init/json/pm.json +47 -0
- package/src/init/json/rt.json +42 -0
- package/src/init/json/vscode-settings-for-deno.json +43 -0
- package/src/init/json/vscode-settings.json +41 -0
- package/src/init/lib.ts +220 -0
- package/src/init/mod.ts +2 -0
- package/src/init/templates/defaults/federation.ts.tpl +23 -0
- package/src/init/templates/defaults/logging.ts.tpl +23 -0
- package/src/init/templates/express/app.ts.tpl +16 -0
- package/src/init/templates/express/index.ts.tpl +6 -0
- package/src/init/templates/hono/app.tsx.tpl +14 -0
- package/src/init/templates/hono/index/bun.ts.tpl +10 -0
- package/src/init/templates/hono/index/deno.ts.tpl +13 -0
- package/src/init/templates/hono/index/node.ts.tpl +14 -0
- package/src/init/templates/next/middleware.ts.tpl +45 -0
- package/src/init/templates/nitro/nitro.config.ts.tpl +5 -0
- package/src/init/templates/nitro/server/error.ts.tpl +3 -0
- package/src/init/templates/nitro/server/middleware/federation.ts.tpl +8 -0
- package/src/init/types.ts +88 -0
- package/src/init/webframeworks.ts +151 -0
- package/src/kv.bun.ts +12 -0
- package/src/kv.node.ts +11 -0
- package/src/log.ts +64 -0
- package/src/lookup.test.ts +182 -0
- package/src/lookup.ts +558 -0
- package/src/mod.ts +45 -0
- package/src/nodeinfo.test.ts +229 -0
- package/src/nodeinfo.ts +447 -0
- package/src/table.ts +17 -0
- package/src/tempserver.ts +87 -0
- package/src/tunnel.ts +32 -0
- package/src/utils.ts +136 -0
- package/src/webfinger/action.ts +50 -0
- package/src/webfinger/command.ts +59 -0
- package/src/webfinger/error.ts +47 -0
- package/src/webfinger/lib.ts +37 -0
- package/src/webfinger/mod.test.ts +79 -0
- package/src/webfinger/mod.ts +2 -0
- package/tsdown.config.ts +24 -0
- package/src/install.mjs +0 -189
- package/src/run.mjs +0 -22
package/dist/lookup.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { getContextLoader, getDocumentLoader } from "./docloader.js";
|
|
5
|
+
import { configureLogging, debugOption } from "./globals.js";
|
|
6
|
+
import { spawnTemporaryServer } from "./tempserver.js";
|
|
7
|
+
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
8
|
+
import { renderImages } from "./imagerenderer.js";
|
|
9
|
+
import { argument, choice, command, constant, flag, float, map, merge, message, multiple, object, option, optional, or, string, withDefault } from "@optique/core";
|
|
10
|
+
import { path, print, printError } from "@optique/run";
|
|
11
|
+
import { Application, Collection, CryptographicKey, Object as Object$1, generateCryptoKeyPair, getAuthenticatedDocumentLoader, lookupObject, respondWithObject, traverseCollection } from "@fedify/fedify";
|
|
12
|
+
import { getLogger } from "@logtape/logtape";
|
|
13
|
+
import process from "node:process";
|
|
14
|
+
import ora from "ora";
|
|
15
|
+
import { createWriteStream } from "node:fs";
|
|
16
|
+
|
|
17
|
+
//#region src/lookup.ts
|
|
18
|
+
const logger = getLogger([
|
|
19
|
+
"fedify",
|
|
20
|
+
"cli",
|
|
21
|
+
"lookup"
|
|
22
|
+
]);
|
|
23
|
+
const authorizedFetchOption = withDefault(object({
|
|
24
|
+
authorizedFetch: flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }),
|
|
25
|
+
firstKnock: withDefault(option("--first-knock", choice(["draft-cavage-http-signatures-12", "rfc9421"]), { description: message`The first-knock spec for -a/--authorized-fetch. It is used for the double-knocking technique.` }), "draft-cavage-http-signatures-12")
|
|
26
|
+
}), { authorizedFetch: false });
|
|
27
|
+
const traverseOption = withDefault(object({
|
|
28
|
+
traverse: flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }),
|
|
29
|
+
suppressErrors: option("-S", "--suppress-errors", { description: message`Suppress partial errors while traversing the collection.` })
|
|
30
|
+
}), { traverse: false });
|
|
31
|
+
const lookupCommand = command("lookup", merge("Looking up options", object({ command: constant("lookup") }), traverseOption, authorizedFetchOption, debugOption, object({
|
|
32
|
+
urls: multiple(argument(string({ metavar: "URL_OR_HANDLE" }), { description: message`One or more URLs or handles to look up.` }), { min: 1 }),
|
|
33
|
+
format: withDefault(or(map(option("-r", "--raw", { description: message`Print the fetched JSON-LD document as is.` }), () => "raw"), map(option("-C", "--compact", { description: message`Compact the fetched JSON-LD document.` }), () => "compact"), map(option("-e", "--expand", { description: message`Expand the fetched JSON-LD document.` }), () => "expand")), "default"),
|
|
34
|
+
userAgent: optional(option("-u", "--user-agent", string({ metavar: "USER_AGENT" }), { description: message`The custom User-Agent header value.` })),
|
|
35
|
+
separator: withDefault(option("-s", "--separator", string({ metavar: "SEPARATOR" }), { description: message`Specify the separator between adjacent output objects or collection items.` }), "----"),
|
|
36
|
+
output: optional(option("-o", "--output", path({
|
|
37
|
+
metavar: "OUTPUT_PATH",
|
|
38
|
+
type: "file",
|
|
39
|
+
allowCreate: true
|
|
40
|
+
}), { description: message`Specify the output file path.` })),
|
|
41
|
+
timeout: optional(option("-T", "--timeout", float({
|
|
42
|
+
min: 0,
|
|
43
|
+
metavar: "SECONDS"
|
|
44
|
+
}), { description: message`Set timeout for network requests in seconds.` }))
|
|
45
|
+
})), { description: message`Lookup an Activity Streams object by URL or the actor handle. The argument can be either a URL or an actor handle (e.g., @username@domain), and it can be multiple.` });
|
|
46
|
+
var TimeoutError = class extends Error {
|
|
47
|
+
name = "TimeoutError";
|
|
48
|
+
constructor(message$1) {
|
|
49
|
+
super(message$1);
|
|
50
|
+
this.name = "TimeoutError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function findAllImages(obj) {
|
|
54
|
+
const result = [];
|
|
55
|
+
const icon = await obj.getIcon();
|
|
56
|
+
const image = await obj.getImage();
|
|
57
|
+
if (icon && icon.url instanceof URL) result.push(icon.url);
|
|
58
|
+
if (image && image.url instanceof URL) result.push(image.url);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
async function writeObjectToStream(object$1, outputPath, format, contextLoader) {
|
|
62
|
+
const stream = outputPath ? createWriteStream(outputPath) : process.stdout;
|
|
63
|
+
let content;
|
|
64
|
+
let json = true;
|
|
65
|
+
let imageUrls = [];
|
|
66
|
+
if (format) if (format === "raw") content = await object$1.toJsonLd({ contextLoader });
|
|
67
|
+
else if (format === "compact") content = await object$1.toJsonLd({
|
|
68
|
+
format: "compact",
|
|
69
|
+
contextLoader
|
|
70
|
+
});
|
|
71
|
+
else if (format === "expand") content = await object$1.toJsonLd({
|
|
72
|
+
format: "expand",
|
|
73
|
+
contextLoader
|
|
74
|
+
});
|
|
75
|
+
else {
|
|
76
|
+
content = object$1;
|
|
77
|
+
json = false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
content = object$1;
|
|
81
|
+
json = false;
|
|
82
|
+
}
|
|
83
|
+
const enableColors = colorEnabled && outputPath === void 0;
|
|
84
|
+
content = formatObject(content, enableColors, json);
|
|
85
|
+
const encoder = new TextEncoder();
|
|
86
|
+
const bytes = encoder.encode(content + "\n");
|
|
87
|
+
stream.write(bytes);
|
|
88
|
+
if (object$1 instanceof Object$1) imageUrls = await findAllImages(object$1);
|
|
89
|
+
if (!outputPath && imageUrls.length > 0) await renderImages(imageUrls);
|
|
90
|
+
}
|
|
91
|
+
const signalTimers = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
function createTimeoutSignal(timeoutSeconds) {
|
|
93
|
+
if (timeoutSeconds == null) return void 0;
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timerId = setTimeout(() => {
|
|
96
|
+
controller.abort(new TimeoutError(`Request timed out after ${timeoutSeconds} seconds`));
|
|
97
|
+
}, timeoutSeconds * 1e3);
|
|
98
|
+
signalTimers.set(controller.signal, timerId);
|
|
99
|
+
return controller.signal;
|
|
100
|
+
}
|
|
101
|
+
function clearTimeoutSignal(signal) {
|
|
102
|
+
if (!signal) return;
|
|
103
|
+
const timerId = signalTimers.get(signal);
|
|
104
|
+
if (timerId !== void 0) {
|
|
105
|
+
clearTimeout(timerId);
|
|
106
|
+
signalTimers.delete(signal);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function wrapDocumentLoaderWithTimeout(loader, timeoutSeconds) {
|
|
110
|
+
if (timeoutSeconds == null) return loader;
|
|
111
|
+
return (url, options) => {
|
|
112
|
+
const signal = createTimeoutSignal(timeoutSeconds);
|
|
113
|
+
return loader(url, {
|
|
114
|
+
...options,
|
|
115
|
+
signal
|
|
116
|
+
}).finally(() => clearTimeoutSignal(signal));
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function handleTimeoutError(spinner, timeoutSeconds, url) {
|
|
120
|
+
const urlText = url ? ` for: ${colors.red(url)}` : "";
|
|
121
|
+
spinner.fail(`Request timed out after ${timeoutSeconds} seconds${urlText}.`);
|
|
122
|
+
printError(message`Try increasing the timeout with -T/--timeout option or check network connectivity.`);
|
|
123
|
+
}
|
|
124
|
+
async function runLookup(command$1) {
|
|
125
|
+
if (command$1.urls.length < 1) {
|
|
126
|
+
printError(message`At least one URL or actor handle must be provided.`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (command$1.debug) await configureLogging();
|
|
130
|
+
const spinner = ora({
|
|
131
|
+
text: `Looking up the ${command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`,
|
|
132
|
+
discardStdin: false
|
|
133
|
+
}).start();
|
|
134
|
+
let server = void 0;
|
|
135
|
+
const baseDocumentLoader = await getDocumentLoader({ userAgent: command$1.userAgent });
|
|
136
|
+
const documentLoader = wrapDocumentLoaderWithTimeout(baseDocumentLoader, command$1.timeout);
|
|
137
|
+
const baseContextLoader = await getContextLoader({ userAgent: command$1.userAgent });
|
|
138
|
+
const contextLoader = wrapDocumentLoaderWithTimeout(baseContextLoader, command$1.timeout);
|
|
139
|
+
let authLoader = void 0;
|
|
140
|
+
if (command$1.authorizedFetch) {
|
|
141
|
+
spinner.text = "Generating a one-time key pair...";
|
|
142
|
+
const key = await generateCryptoKeyPair();
|
|
143
|
+
spinner.text = "Spinning up a temporary ActivityPub server...";
|
|
144
|
+
server = await spawnTemporaryServer((req) => {
|
|
145
|
+
const serverUrl = server?.url ?? new URL("http://localhost/");
|
|
146
|
+
if (new URL(req.url).pathname == "/.well-known/webfinger") {
|
|
147
|
+
const jrd = {
|
|
148
|
+
subject: `acct:${serverUrl.hostname}@${serverUrl.hostname}`,
|
|
149
|
+
aliases: [serverUrl.href],
|
|
150
|
+
links: [{
|
|
151
|
+
rel: "self",
|
|
152
|
+
href: serverUrl.href,
|
|
153
|
+
type: "application/activity+json"
|
|
154
|
+
}]
|
|
155
|
+
};
|
|
156
|
+
return new Response(JSON.stringify(jrd), { headers: { "Content-Type": "application/jrd+json" } });
|
|
157
|
+
}
|
|
158
|
+
return respondWithObject(new Application({
|
|
159
|
+
id: serverUrl,
|
|
160
|
+
preferredUsername: serverUrl?.hostname,
|
|
161
|
+
publicKey: new CryptographicKey({
|
|
162
|
+
id: new URL("#main-key", serverUrl),
|
|
163
|
+
owner: serverUrl,
|
|
164
|
+
publicKey: key.publicKey
|
|
165
|
+
}),
|
|
166
|
+
manuallyApprovesFollowers: true,
|
|
167
|
+
inbox: new URL("/inbox", serverUrl),
|
|
168
|
+
outbox: new URL("/outbox", serverUrl)
|
|
169
|
+
}), { contextLoader });
|
|
170
|
+
});
|
|
171
|
+
const baseAuthLoader = getAuthenticatedDocumentLoader({
|
|
172
|
+
keyId: new URL("#main-key", server.url),
|
|
173
|
+
privateKey: key.privateKey
|
|
174
|
+
}, { specDeterminer: {
|
|
175
|
+
determineSpec() {
|
|
176
|
+
return command$1.firstKnock;
|
|
177
|
+
},
|
|
178
|
+
rememberSpec() {}
|
|
179
|
+
} });
|
|
180
|
+
authLoader = wrapDocumentLoaderWithTimeout(baseAuthLoader, command$1.timeout);
|
|
181
|
+
}
|
|
182
|
+
spinner.text = `Looking up the ${command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`;
|
|
183
|
+
if (command$1.traverse) {
|
|
184
|
+
let totalItems = 0;
|
|
185
|
+
for (let urlIndex = 0; urlIndex < command$1.urls.length; urlIndex++) {
|
|
186
|
+
const url = command$1.urls[urlIndex];
|
|
187
|
+
if (urlIndex > 0) spinner.text = `Looking up collection ${urlIndex + 1}/${command$1.urls.length}...`;
|
|
188
|
+
let collection;
|
|
189
|
+
try {
|
|
190
|
+
collection = await lookupObject(url, {
|
|
191
|
+
documentLoader: authLoader ?? documentLoader,
|
|
192
|
+
contextLoader,
|
|
193
|
+
userAgent: command$1.userAgent
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
197
|
+
else {
|
|
198
|
+
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
199
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
200
|
+
}
|
|
201
|
+
await server?.close();
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
if (collection == null) {
|
|
205
|
+
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
206
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
207
|
+
await server?.close();
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
if (!(collection instanceof Collection)) {
|
|
211
|
+
spinner.fail(`Not a collection: ${colors.red(url)}. The -t/--traverse option requires a collection.`);
|
|
212
|
+
await server?.close();
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
spinner.succeed(`Fetched collection: ${colors.green(url)}.`);
|
|
216
|
+
try {
|
|
217
|
+
let collectionItems = 0;
|
|
218
|
+
for await (const item of traverseCollection(collection, {
|
|
219
|
+
documentLoader: authLoader ?? documentLoader,
|
|
220
|
+
contextLoader,
|
|
221
|
+
suppressError: command$1.suppressErrors
|
|
222
|
+
})) {
|
|
223
|
+
if (!command$1.output && (totalItems > 0 || collectionItems > 0)) print(message`${command$1.separator}`);
|
|
224
|
+
await writeObjectToStream(item, command$1.output, command$1.format, contextLoader);
|
|
225
|
+
collectionItems++;
|
|
226
|
+
totalItems++;
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
logger.error("Failed to complete the traversal for {url}: {error}", {
|
|
230
|
+
url,
|
|
231
|
+
error
|
|
232
|
+
});
|
|
233
|
+
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
234
|
+
else {
|
|
235
|
+
spinner.fail(`Failed to complete the traversal for: ${colors.red(url)}.`);
|
|
236
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
237
|
+
else printError(message`Use the -S/--suppress-errors option to suppress partial errors.`);
|
|
238
|
+
}
|
|
239
|
+
await server?.close();
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
spinner.succeed("Successfully fetched all items in the collection.");
|
|
244
|
+
await server?.close();
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
const promises = [];
|
|
248
|
+
for (const url of command$1.urls) promises.push(lookupObject(url, {
|
|
249
|
+
documentLoader: authLoader ?? documentLoader,
|
|
250
|
+
contextLoader,
|
|
251
|
+
userAgent: command$1.userAgent
|
|
252
|
+
}).catch((error) => {
|
|
253
|
+
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
254
|
+
throw error;
|
|
255
|
+
}));
|
|
256
|
+
let objects;
|
|
257
|
+
try {
|
|
258
|
+
objects = await Promise.all(promises);
|
|
259
|
+
} catch (_error) {
|
|
260
|
+
await server?.close();
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
spinner.stop();
|
|
264
|
+
let success = true;
|
|
265
|
+
let i = 0;
|
|
266
|
+
for (const obj of objects) {
|
|
267
|
+
const url = command$1.urls[i];
|
|
268
|
+
if (i > 0) print(message`${command$1.separator}`);
|
|
269
|
+
i++;
|
|
270
|
+
if (obj == null) {
|
|
271
|
+
spinner.fail(`Failed to fetch ${colors.red(url)}`);
|
|
272
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
273
|
+
success = false;
|
|
274
|
+
} else {
|
|
275
|
+
spinner.succeed(`Fetched object: ${colors.green(url)}`);
|
|
276
|
+
await writeObjectToStream(obj, command$1.output, command$1.format, contextLoader);
|
|
277
|
+
if (i < command$1.urls.length - 1) print(message`${command$1.separator}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (success) spinner.succeed(command$1.urls.length > 1 ? "Successfully fetched all objects." : "Successfully fetched the object.");
|
|
281
|
+
await server?.close();
|
|
282
|
+
if (!success) process.exit(1);
|
|
283
|
+
if (success && command$1.output) spinner.succeed(`Successfully wrote output to ${colors.green(command$1.output)}.`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
export { lookupCommand, runLookup };
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
4
|
+
|
|
5
|
+
import { inboxCommand, runInbox } from "./inbox.js";
|
|
6
|
+
import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.js";
|
|
7
|
+
import { lookupCommand, runLookup } from "./lookup.js";
|
|
8
|
+
import action_default from "./init/action/mod.js";
|
|
9
|
+
import { initCommand } from "./init/command.js";
|
|
10
|
+
import "./init/mod.js";
|
|
11
|
+
import { runTunnel, tunnelCommand } from "./tunnel.js";
|
|
12
|
+
import { runWebFinger } from "./webfinger/action.js";
|
|
13
|
+
import { webFingerCommand } from "./webfinger/command.js";
|
|
14
|
+
import "./webfinger/mod.js";
|
|
15
|
+
import { or } from "@optique/core";
|
|
16
|
+
import { run } from "@optique/run";
|
|
17
|
+
|
|
18
|
+
//#region src/mod.ts
|
|
19
|
+
const command$1 = or(initCommand, webFingerCommand, lookupCommand, inboxCommand, nodeInfoCommand, tunnelCommand);
|
|
20
|
+
async function main() {
|
|
21
|
+
const result = run(command$1, {
|
|
22
|
+
programName: "fedify",
|
|
23
|
+
help: "both"
|
|
24
|
+
});
|
|
25
|
+
if (result.command === "init") await action_default(result);
|
|
26
|
+
if (result.command === "lookup") await runLookup(result);
|
|
27
|
+
if (result.command === "webfinger") await runWebFinger(result);
|
|
28
|
+
if (result.command === "inbox") runInbox(result);
|
|
29
|
+
if (result.command === "nodeinfo") runNodeInfo(result);
|
|
30
|
+
if (result.command === "tunnel") runTunnel(result);
|
|
31
|
+
}
|
|
32
|
+
await main();
|
|
33
|
+
|
|
34
|
+
//#endregion
|
package/dist/nodeinfo.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { debugOption } from "./globals.js";
|
|
5
|
+
import { colors, formatObject } from "./utils.js";
|
|
6
|
+
import { argument, command, constant, flag, merge, message, object, option, optional, or, string } from "@optique/core";
|
|
7
|
+
import { print, printError } from "@optique/run";
|
|
8
|
+
import { getNodeInfo } from "@fedify/fedify";
|
|
9
|
+
import { getLogger } from "@logtape/logtape";
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import { getUserAgent } from "@fedify/vocab-runtime";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import { createJimp } from "@jimp/core";
|
|
15
|
+
import webp from "@jimp/wasm-webp";
|
|
16
|
+
import { isICO, parseICO } from "icojs";
|
|
17
|
+
import { defaultFormats, defaultPlugins, intToRGBA } from "jimp";
|
|
18
|
+
|
|
19
|
+
//#region src/nodeinfo.ts
|
|
20
|
+
const logger = getLogger([
|
|
21
|
+
"fedify",
|
|
22
|
+
"cli",
|
|
23
|
+
"nodeinfo"
|
|
24
|
+
]);
|
|
25
|
+
const Jimp = createJimp({
|
|
26
|
+
formats: [...defaultFormats, webp],
|
|
27
|
+
plugins: defaultPlugins
|
|
28
|
+
});
|
|
29
|
+
const nodeInfoOption = optional(or(object({ raw: flag("-r", "--raw", { description: message`Show NodeInfo document in the raw JSON format` }) }), object({
|
|
30
|
+
bestEffort: optional(flag("-b", "--best-effort", { description: message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.` })),
|
|
31
|
+
noFavicon: optional(flag("--no-favicon", { description: message`Disable fetching the favicon of the instance` })),
|
|
32
|
+
metadata: optional(flag("-m", "--metadata", { description: message`show the extra metadata of the NodeInfo, i.e., the metadata field of the document.` }))
|
|
33
|
+
})));
|
|
34
|
+
const userAgentOption = optional(object({ userAgent: option("-u", "--user-agent", string()) }));
|
|
35
|
+
const nodeInfoCommand = command("nodeinfo", merge(object({
|
|
36
|
+
command: constant("nodeinfo"),
|
|
37
|
+
host: argument(string({ metavar: "hostname or URL" }), { description: message`Bare hostname or a full URL of the instance` })
|
|
38
|
+
}), debugOption, nodeInfoOption, userAgentOption), { description: message`Get information about a remote node using the NodeInfo protocol. The argument is the hostname of the remote node, or the URL of the remote node.` });
|
|
39
|
+
async function runNodeInfo(command$1) {
|
|
40
|
+
const spinner = ora({
|
|
41
|
+
text: "Fetching a NodeInfo document...",
|
|
42
|
+
discardStdin: false
|
|
43
|
+
}).start();
|
|
44
|
+
const url = new URL(URL.canParse(command$1.host) ? command$1.host : `https://${command$1.host}`);
|
|
45
|
+
if ("raw" in command$1 && command$1.raw) {
|
|
46
|
+
const nodeInfo$1 = await getNodeInfo(url, {
|
|
47
|
+
parse: "none",
|
|
48
|
+
userAgent: command$1.userAgent
|
|
49
|
+
});
|
|
50
|
+
if (nodeInfo$1 === void 0) {
|
|
51
|
+
spinner.fail("No NodeInfo document found.");
|
|
52
|
+
printError(message`No NodeInfo document found.`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
spinner.succeed("NodeInfo document fetched.");
|
|
56
|
+
console.log(formatObject(nodeInfo$1, void 0, true));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const nodeInfo = await getNodeInfo(url, {
|
|
60
|
+
parse: "bestEffort" in command$1 && command$1.bestEffort ? "best-effort" : "strict",
|
|
61
|
+
userAgent: command$1.userAgent
|
|
62
|
+
});
|
|
63
|
+
logger.debug("NodeInfo document: {nodeInfo}", { nodeInfo });
|
|
64
|
+
if (nodeInfo == void 0) {
|
|
65
|
+
spinner.fail("No NodeInfo document found or it is invalid.");
|
|
66
|
+
printError(message`No NodeInfo document found or it is invalid.`);
|
|
67
|
+
if (!("bestEffort" in command$1 && command$1.bestEffort)) printError(message`Use the -b/--best-effort option to try to parse the document anyway.`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
let layout;
|
|
71
|
+
let defaultWidth = 0;
|
|
72
|
+
if (!("noFavicon" in command$1 && command$1.noFavicon)) {
|
|
73
|
+
spinner.text = "Fetching the favicon...";
|
|
74
|
+
try {
|
|
75
|
+
const faviconUrl = await getFaviconUrl(url, command$1.userAgent);
|
|
76
|
+
const response = await fetch(faviconUrl, { headers: { "User-Agent": command$1.userAgent == null ? getUserAgent() : command$1.userAgent } });
|
|
77
|
+
if (response.ok) {
|
|
78
|
+
const contentType = response.headers.get("Content-Type");
|
|
79
|
+
let buffer = await response.arrayBuffer();
|
|
80
|
+
if (contentType === "image/vnd.microsoft.icon" || contentType === "image/x-icon" || isICO(buffer)) {
|
|
81
|
+
const images = await parseICO(buffer);
|
|
82
|
+
if (images.length < 1) throw new Error("No images found in the ICO file.");
|
|
83
|
+
buffer = images[0].buffer;
|
|
84
|
+
}
|
|
85
|
+
const image = await Jimp.read(buffer);
|
|
86
|
+
const colorSupport = checkTerminalColorSupport();
|
|
87
|
+
layout = getAsciiArt(image, DEFAULT_IMAGE_WIDTH, colorSupport, colors).split("\n").map((line) => ` ${line} `);
|
|
88
|
+
defaultWidth = 41;
|
|
89
|
+
} else {
|
|
90
|
+
logger.error("Failed to fetch the favicon: {status} {statusText}", {
|
|
91
|
+
status: response.status,
|
|
92
|
+
statusText: response.statusText
|
|
93
|
+
});
|
|
94
|
+
layout = [""];
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.error("Failed to fetch or render the favicon: {error}", { error });
|
|
98
|
+
layout = [""];
|
|
99
|
+
}
|
|
100
|
+
} else layout = [""];
|
|
101
|
+
spinner.succeed("NodeInfo document fetched.");
|
|
102
|
+
print(message``);
|
|
103
|
+
let i = 0;
|
|
104
|
+
const next = () => {
|
|
105
|
+
i++;
|
|
106
|
+
if (i >= layout.length) layout.push(" ".repeat(defaultWidth));
|
|
107
|
+
return i;
|
|
108
|
+
};
|
|
109
|
+
layout[i] += colors.bold(url.host);
|
|
110
|
+
layout[next()] += colors.dim("=".repeat(url.host.length));
|
|
111
|
+
layout[next()] += colors.bold(colors.dim("Software:"));
|
|
112
|
+
layout[next()] += ` ${nodeInfo.software.name} v${nodeInfo.software.version}`;
|
|
113
|
+
if (nodeInfo.software.homepage != null) layout[next()] += ` ${nodeInfo.software.homepage.href}`;
|
|
114
|
+
if (nodeInfo.software.repository != null) layout[next()] += " " + colors.dim(nodeInfo.software.repository.href);
|
|
115
|
+
if (nodeInfo.protocols.length > 0) {
|
|
116
|
+
layout[next()] += colors.bold(colors.dim("Protocols:"));
|
|
117
|
+
for (const protocol of nodeInfo.protocols) layout[next()] += ` ${protocol}`;
|
|
118
|
+
}
|
|
119
|
+
if (nodeInfo.services?.inbound?.length ?? false) {
|
|
120
|
+
layout[next()] += colors.bold(colors.dim("Inbound services:"));
|
|
121
|
+
for (const service of nodeInfo.services?.inbound ?? []) layout[next()] += ` ${service}`;
|
|
122
|
+
}
|
|
123
|
+
if (nodeInfo.services?.outbound?.length ?? false) {
|
|
124
|
+
layout[next()] += colors.bold(colors.dim("Outbound services:"));
|
|
125
|
+
for (const service of nodeInfo.services?.outbound ?? []) layout[next()] += ` ${service}`;
|
|
126
|
+
}
|
|
127
|
+
if (nodeInfo.usage?.users != null && (nodeInfo.usage.users.total != null || nodeInfo.usage.users.activeHalfyear != null || nodeInfo.usage.users.activeMonth != null)) {
|
|
128
|
+
layout[next()] += colors.bold(colors.dim("Users:"));
|
|
129
|
+
if (nodeInfo.usage.users.total != null) layout[next()] += ` ${nodeInfo.usage.users.total.toLocaleString("en-US")} ` + colors.dim("(total)");
|
|
130
|
+
if (nodeInfo.usage.users.activeHalfyear != null) layout[next()] += ` ${nodeInfo.usage.users.activeHalfyear.toLocaleString("en-US")} ` + colors.dim("(active half year)");
|
|
131
|
+
if (nodeInfo.usage.users.activeMonth != null) layout[next()] += ` ${nodeInfo.usage.users.activeMonth.toLocaleString("en-US")} ` + colors.dim("(active month)");
|
|
132
|
+
}
|
|
133
|
+
if (nodeInfo.usage?.localPosts != null) {
|
|
134
|
+
layout[next()] += colors.bold(colors.dim("Local posts: "));
|
|
135
|
+
layout[next()] += " " + nodeInfo.usage.localPosts.toLocaleString("en-US");
|
|
136
|
+
}
|
|
137
|
+
if (nodeInfo.usage?.localComments != null) {
|
|
138
|
+
layout[next()] += colors.bold(colors.dim("Local comments:"));
|
|
139
|
+
layout[next()] += " " + nodeInfo.usage.localComments.toLocaleString("en-US");
|
|
140
|
+
}
|
|
141
|
+
if (nodeInfo.openRegistrations != null) {
|
|
142
|
+
layout[next()] += colors.bold(colors.dim("Open registrations:"));
|
|
143
|
+
layout[next()] += " " + (nodeInfo.openRegistrations ? "Yes" : "No");
|
|
144
|
+
}
|
|
145
|
+
if ("metadata" in command$1 && command$1.metadata && nodeInfo.metadata != null && Object.keys(nodeInfo.metadata).length > 0) {
|
|
146
|
+
layout[next()] += colors.bold(colors.dim("Metadata:"));
|
|
147
|
+
for (const [key, value] of Object.entries(nodeInfo.metadata)) layout[next()] += ` ${colors.dim(key + ":")} ${indent(typeof value === "string" ? value : formatObject(value), defaultWidth + 4 + key.length)}`;
|
|
148
|
+
}
|
|
149
|
+
console.log(layout.join("\n"));
|
|
150
|
+
}
|
|
151
|
+
function indent(text, depth) {
|
|
152
|
+
return text.replace(/\n/g, "\n" + " ".repeat(depth));
|
|
153
|
+
}
|
|
154
|
+
const LINK_REGEXP = /<link((?:\s+(?:[-a-z]+)=(?:"[^"]*"|'[^']*'|[^\s]+))*)\s*\/?>/gi;
|
|
155
|
+
const LINK_ATTRS_REGEXP = /(?:\s+([-a-z]+)=("[^"]*"|'[^']*'|[^\s]+))/gi;
|
|
156
|
+
async function getFaviconUrl(url, userAgent) {
|
|
157
|
+
const response = await fetch(url, { headers: { "User-Agent": userAgent == null ? getUserAgent() : userAgent } });
|
|
158
|
+
const text = await response.text();
|
|
159
|
+
for (const match of text.matchAll(LINK_REGEXP)) {
|
|
160
|
+
const attrs = {};
|
|
161
|
+
for (const attrMatch of match[1].matchAll(LINK_ATTRS_REGEXP)) {
|
|
162
|
+
const [, key, value] = attrMatch;
|
|
163
|
+
attrs[key] = value.startsWith("\"") || value.startsWith("'") ? value.slice(1, -1) : value;
|
|
164
|
+
}
|
|
165
|
+
const rel = attrs.rel?.toLowerCase()?.trim()?.split(/\s+/) ?? [];
|
|
166
|
+
if (!rel.includes("icon") && !rel.includes("apple-touch-icon")) continue;
|
|
167
|
+
if ("sizes" in attrs && attrs.sizes.match(/\d+x\d+/)) {
|
|
168
|
+
const [w, h] = attrs.sizes.split("x").map((v) => Number.parseInt(v));
|
|
169
|
+
if (w < 38 || h < 19) continue;
|
|
170
|
+
}
|
|
171
|
+
if ("href" in attrs) {
|
|
172
|
+
if (attrs.href.endsWith(".svg")) continue;
|
|
173
|
+
return new URL(attrs.href, response.url);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return new URL("/favicon.ico", response.url);
|
|
177
|
+
}
|
|
178
|
+
function checkTerminalColorSupport() {
|
|
179
|
+
const noColor = process.env.NO_COLOR;
|
|
180
|
+
if (noColor != null && noColor !== "") return "none";
|
|
181
|
+
const colorTerm = process.env.COLORTERM;
|
|
182
|
+
if (colorTerm != null && (colorTerm.includes("24bit") || colorTerm.includes("truecolor"))) return "truecolor";
|
|
183
|
+
const term = process.env.TERM;
|
|
184
|
+
if (term != null && (term.includes("256color") || term.includes("xterm") || term === "screen" || term === "tmux")) return "256color";
|
|
185
|
+
if (term != null && term !== "dumb") return "256color";
|
|
186
|
+
const isWindows = os.platform() === "win32";
|
|
187
|
+
const isWT = process.env.WT_SESSION;
|
|
188
|
+
if (isWindows && isWT != null && isWT !== "") return "truecolor";
|
|
189
|
+
return "none";
|
|
190
|
+
}
|
|
191
|
+
const DEFAULT_IMAGE_WIDTH = 38;
|
|
192
|
+
const ASCII_CHARS = "█▓▒░@#B8&WM%*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ";
|
|
193
|
+
const CUBE_VALUES = [
|
|
194
|
+
0,
|
|
195
|
+
95,
|
|
196
|
+
135,
|
|
197
|
+
175,
|
|
198
|
+
215,
|
|
199
|
+
255
|
|
200
|
+
];
|
|
201
|
+
const findClosestIndex = (value) => {
|
|
202
|
+
let minDiff = Infinity;
|
|
203
|
+
let closestIndex = 0;
|
|
204
|
+
for (let idx = 0; idx < CUBE_VALUES.length; idx++) {
|
|
205
|
+
const diff = Math.abs(value - CUBE_VALUES[idx]);
|
|
206
|
+
if (diff < minDiff) {
|
|
207
|
+
minDiff = diff;
|
|
208
|
+
closestIndex = idx;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return closestIndex;
|
|
212
|
+
};
|
|
213
|
+
function rgbTo256Color(r, g, b) {
|
|
214
|
+
const gray = Math.round((r + g + b) / 3);
|
|
215
|
+
const isGrayscale = Math.abs(r - gray) <= 5 && Math.abs(g - gray) <= 5 && Math.abs(b - gray) <= 5;
|
|
216
|
+
if (isGrayscale) {
|
|
217
|
+
const isExactCubeValue = CUBE_VALUES.includes(r) && r === g && g === b;
|
|
218
|
+
if (!isExactCubeValue) {
|
|
219
|
+
if (gray < 8) return 232;
|
|
220
|
+
if (gray > 238) return 255;
|
|
221
|
+
const grayIndex = Math.round((gray - 8) / 10);
|
|
222
|
+
return Math.max(232, Math.min(255, 232 + grayIndex));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const r6 = findClosestIndex(r);
|
|
226
|
+
const g6 = findClosestIndex(g);
|
|
227
|
+
const b6 = findClosestIndex(b);
|
|
228
|
+
return 16 + 36 * r6 + 6 * g6 + b6;
|
|
229
|
+
}
|
|
230
|
+
function getAsciiArt(image, width = DEFAULT_IMAGE_WIDTH, colorSupport, colors$1) {
|
|
231
|
+
const ratio = image.width / image.height;
|
|
232
|
+
const height = Math.round(width / ratio * .5);
|
|
233
|
+
image.resize({
|
|
234
|
+
w: width,
|
|
235
|
+
h: height
|
|
236
|
+
});
|
|
237
|
+
let art = "";
|
|
238
|
+
for (let y = 0; y < height; y++) {
|
|
239
|
+
for (let x = 0; x < width; x++) {
|
|
240
|
+
const pixel = image.getPixelColor(x, y);
|
|
241
|
+
const color = intToRGBA(pixel);
|
|
242
|
+
if (color.a < 1) {
|
|
243
|
+
art += " ";
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const brightness = (color.r + color.g + color.b) / 3;
|
|
247
|
+
const charIndex = Math.round(brightness / 255 * 72);
|
|
248
|
+
const char = ASCII_CHARS[charIndex];
|
|
249
|
+
if (colorSupport === "truecolor") art += colors$1.rgb(color.r, color.g, color.b)(char);
|
|
250
|
+
else if (colorSupport === "256color") {
|
|
251
|
+
const colorIndex = rgbTo256Color(color.r, color.g, color.b);
|
|
252
|
+
art += colors$1.ansi256(colorIndex)(char);
|
|
253
|
+
} else art += char;
|
|
254
|
+
}
|
|
255
|
+
if (y < height - 1) art += "\n";
|
|
256
|
+
}
|
|
257
|
+
return art;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
export { Jimp, nodeInfoCommand, runNodeInfo };
|
package/dist/table.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
//#region src/table.ts
|
|
5
|
+
const tableStyle = {
|
|
6
|
+
top: "─",
|
|
7
|
+
topMid: "┬",
|
|
8
|
+
topLeft: "╭",
|
|
9
|
+
topRight: "╮",
|
|
10
|
+
bottom: "─",
|
|
11
|
+
bottomMid: "┴",
|
|
12
|
+
bottomLeft: "╰",
|
|
13
|
+
bottomRight: "╯",
|
|
14
|
+
left: "│",
|
|
15
|
+
leftMid: "├",
|
|
16
|
+
mid: "─",
|
|
17
|
+
midMid: "┼",
|
|
18
|
+
right: "│",
|
|
19
|
+
rightMid: "┤",
|
|
20
|
+
middle: "│"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { tableStyle };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
3
|
+
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import { openTunnel } from "@hongminhee/localtunnel";
|
|
6
|
+
import { serve } from "srvx";
|
|
7
|
+
|
|
8
|
+
//#region src/tempserver.ts
|
|
9
|
+
const logger = getLogger([
|
|
10
|
+
"fedify",
|
|
11
|
+
"cli",
|
|
12
|
+
"tempserver"
|
|
13
|
+
]);
|
|
14
|
+
async function spawnTemporaryServer(fetch, options = {}) {
|
|
15
|
+
if (options.noTunnel) {
|
|
16
|
+
const server$1 = serve({
|
|
17
|
+
port: 0,
|
|
18
|
+
hostname: "::",
|
|
19
|
+
fetch
|
|
20
|
+
});
|
|
21
|
+
await server$1.ready();
|
|
22
|
+
const url$1 = new URL(server$1.url);
|
|
23
|
+
const port$1 = url$1.port;
|
|
24
|
+
logger.debug("Temporary server is listening on port {port}.", { port: port$1 });
|
|
25
|
+
return {
|
|
26
|
+
url: new URL(`http://localhost:${port$1}`),
|
|
27
|
+
async close() {
|
|
28
|
+
await server$1.close();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const server = serve({
|
|
33
|
+
fetch: (request) => {
|
|
34
|
+
const url$1 = new URL(request.url);
|
|
35
|
+
url$1.protocol = "https:";
|
|
36
|
+
request = new Request(url$1, {
|
|
37
|
+
method: request.method,
|
|
38
|
+
headers: request.headers,
|
|
39
|
+
body: request.method === "GET" || request.method === "HEAD" ? null : request.body,
|
|
40
|
+
referrer: request.referrer,
|
|
41
|
+
referrerPolicy: request.referrerPolicy,
|
|
42
|
+
mode: request.mode,
|
|
43
|
+
credentials: request.credentials,
|
|
44
|
+
cache: request.cache,
|
|
45
|
+
redirect: request.redirect,
|
|
46
|
+
integrity: request.integrity,
|
|
47
|
+
keepalive: request.keepalive,
|
|
48
|
+
signal: request.signal
|
|
49
|
+
});
|
|
50
|
+
return new Response();
|
|
51
|
+
},
|
|
52
|
+
port: 0,
|
|
53
|
+
hostname: "::"
|
|
54
|
+
});
|
|
55
|
+
await server.ready();
|
|
56
|
+
const url = new URL(server.url);
|
|
57
|
+
const port = url.port;
|
|
58
|
+
logger.debug("Temporary server is listening on port {port}.", { port });
|
|
59
|
+
const tun = await openTunnel({ port: parseInt(port) });
|
|
60
|
+
logger.debug("Temporary server is tunneled to {url}.", { url: tun.url.href });
|
|
61
|
+
return {
|
|
62
|
+
url: tun.url,
|
|
63
|
+
async close() {
|
|
64
|
+
await server.close();
|
|
65
|
+
await tun.close();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { spawnTemporaryServer };
|