@fedify/cli 2.0.0-dev.1761 → 2.0.0-dev.1795

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/cli",
3
- "version": "2.0.0-dev.1761+9e73442d",
3
+ "version": "2.0.0-dev.1795+b6806c18",
4
4
  "license": "MIT",
5
5
  "exports": "./src/mod.ts",
6
6
  "imports": {
package/dist/deno.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  //#region deno.json
5
5
  var name = "@fedify/cli";
6
- var version = "2.0.0-dev.1761+9e73442d";
6
+ var version = "2.0.0-dev.1795+b6806c18";
7
7
  var license = "MIT";
8
8
  var exports = "./src/mod.ts";
9
9
  var imports = {
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@
3
3
  import { Temporal } from "@js-temporal/polyfill";
4
4
 
5
5
  import { inboxCommand, runInbox } from "./inbox.js";
6
- import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.js";
7
- import { lookupCommand, runLookup } from "./lookup.js";
8
6
  import action_default from "./init/action/mod.js";
9
7
  import { initCommand } from "./init/command.js";
10
8
  import "./init/mod.js";
9
+ import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.js";
10
+ import { lookupCommand, runLookup } from "./lookup.js";
11
11
  import { runTunnel, tunnelCommand } from "./tunnel.js";
12
12
  import { runWebFinger } from "./webfinger/action.js";
13
13
  import { webFingerCommand } from "./webfinger/command.js";
@@ -27,7 +27,7 @@ async function main() {
27
27
  if (result.command === "webfinger") await runWebFinger(result);
28
28
  if (result.command === "inbox") runInbox(result);
29
29
  if (result.command === "nodeinfo") runNodeInfo(result);
30
- if (result.command === "tunnel") runTunnel(result);
30
+ if (result.command === "tunnel") await runTunnel(result);
31
31
  }
32
32
  await main();
33
33
 
package/dist/tunnel.js CHANGED
@@ -1,20 +1,54 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { debugOption } from "./globals.js";
5
- import { argument, command, constant, integer, merge, message, object } from "@optique/core";
4
+ import { configureLogging, debugOption } from "./globals.js";
5
+ import { argument, command, constant, integer, merge, message, object, option, optional } from "@optique/core";
6
+ import { print, printError } from "@optique/run";
7
+ import process from "node:process";
8
+ import ora from "ora";
9
+ import { openTunnel } from "@hongminhee/localtunnel";
10
+ import { choice as choice$1 } from "@optique/core/valueparser";
6
11
 
7
12
  //#region src/tunnel.ts
8
- const tunnelCommand = command("tunnel", merge(object({
9
- command: constant("tunnel"),
13
+ const tunnelCommand = command("tunnel", merge("Tunnel options", object({ command: constant("tunnel") }), object({
10
14
  port: argument(integer({
11
15
  metavar: "PORT",
12
16
  min: 0,
13
17
  max: 65535
14
- }))
15
- }), debugOption), { description: message`Expose a local HTTP server to the public internet using a secure tunnel.` });
16
- function runTunnel(command$1) {
17
- console.debug(command$1);
18
+ }), { description: message`The local port number to expose.` }),
19
+ service: optional(option("-s", "--service", choice$1([
20
+ "localhost.run",
21
+ "serveo.net",
22
+ "pinggy.io"
23
+ ]), { description: message`The localtunnel service to use.` }))
24
+ }), debugOption), { description: message`Expose a local HTTP server to the public internet using a secure tunnel.\nNote that the HTTP requests through the tunnel have X-Forwarded-* headers.` });
25
+ async function runTunnel(command$1, deps = {
26
+ openTunnel,
27
+ ora,
28
+ exit: process.exit
29
+ }) {
30
+ if (command$1.debug) await configureLogging();
31
+ const spinner = deps.ora({
32
+ text: "Creating a secure tunnel...",
33
+ discardStdin: false
34
+ }).start();
35
+ let tunnel;
36
+ try {
37
+ tunnel = await deps.openTunnel({
38
+ port: command$1.port,
39
+ service: command$1.service
40
+ });
41
+ } catch (error) {
42
+ if (command$1.debug) printError(message`${String(error)}`);
43
+ spinner.fail("Failed to create a secure tunnel.");
44
+ deps.exit(1);
45
+ }
46
+ spinner.succeed(`Your local server at ${command$1.port} is now publicly accessible:\n`);
47
+ print(message`${tunnel.url.href}`);
48
+ print(message`\nPress ^C to close the tunnel.`);
49
+ process.on("SIGINT", async () => {
50
+ await tunnel.close();
51
+ });
18
52
  }
19
53
 
20
54
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/cli",
3
- "version": "2.0.0-dev.1761+9e73442d",
3
+ "version": "2.0.0-dev.1795+b6806c18",
4
4
  "description": "CLI toolchain for Fedify and debugging ActivityPub",
5
5
  "keywords": [
6
6
  "fedify",
@@ -71,9 +71,9 @@
71
71
  "ora": "^8.2.0",
72
72
  "shiki": "^1.6.4",
73
73
  "srvx": "^0.8.7",
74
- "@fedify/fedify": "2.0.0-dev.1761+9e73442d",
75
- "@fedify/sqlite": "2.0.0-dev.1761+9e73442d",
76
- "@fedify/vocab-runtime": "2.0.0-dev.1761+9e73442d"
74
+ "@fedify/sqlite": "2.0.0-dev.1795+b6806c18",
75
+ "@fedify/fedify": "2.0.0-dev.1795+b6806c18",
76
+ "@fedify/vocab-runtime": "2.0.0-dev.1795+b6806c18"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/bun": "^1.2.23",
package/src/globals.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { message, object, option } from "@optique/core";
2
- import { configure, getConsoleSink } from "@logtape/logtape";
3
1
  import { getFileSink } from "@logtape/file";
4
- import { recordingSink } from "./log.ts";
2
+ import { configure, getConsoleSink } from "@logtape/logtape";
3
+ import { message, object, option } from "@optique/core";
5
4
  import { AsyncLocalStorage } from "node:async_hooks";
6
5
  import process from "node:process";
6
+ import { recordingSink } from "./log.ts";
7
7
 
8
8
  export const debugOption = object("Global options", {
9
9
  debug: option("-d", "--debug", {
package/src/mod.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  import { or } from "@optique/core";
3
3
  import { run } from "@optique/run";
4
4
  import { inboxCommand, runInbox } from "./inbox.tsx";
5
- import { lookupCommand, runLookup } from "./lookup.ts";
6
5
  import { initCommand, runInit } from "./init/mod.ts";
6
+ import { lookupCommand, runLookup } from "./lookup.ts";
7
7
  import { nodeInfoCommand, runNodeInfo } from "./nodeinfo.ts";
8
8
  import { runTunnel, tunnelCommand } from "./tunnel.ts";
9
9
  import { runWebFinger, webFingerCommand } from "./webfinger/mod.ts";
@@ -38,7 +38,7 @@ async function main() {
38
38
  runNodeInfo(result);
39
39
  }
40
40
  if (result.command === "tunnel") {
41
- runTunnel(result);
41
+ await runTunnel(result);
42
42
  }
43
43
  }
44
44
 
@@ -0,0 +1,157 @@
1
+ import type { Tunnel, TunnelOptions } from "@hongminhee/localtunnel";
2
+ import { run } from "@optique/run";
3
+ import { deepEqual, rejects } from "node:assert/strict";
4
+ import test from "node:test";
5
+ import type { Ora } from "ora";
6
+ import { runTunnel, tunnelCommand } from "./tunnel.ts";
7
+
8
+ test("tunnel command structure", () => {
9
+ const testCommandWithOptions = run(tunnelCommand, {
10
+ args: ["tunnel", "3001", "-s", "pinggy.io", "-d"],
11
+ });
12
+ const testCommandWithoutOptions = run(tunnelCommand, {
13
+ args: ["tunnel", "3000"],
14
+ });
15
+
16
+ deepEqual(testCommandWithOptions.command, "tunnel");
17
+ deepEqual(testCommandWithOptions.port, 3001);
18
+ deepEqual(testCommandWithOptions.service, "pinggy.io");
19
+ deepEqual(testCommandWithOptions.debug, true);
20
+
21
+ deepEqual(testCommandWithoutOptions.port, 3000);
22
+ deepEqual(testCommandWithoutOptions.service, undefined);
23
+ deepEqual(testCommandWithoutOptions.debug, false);
24
+ });
25
+
26
+ test("tunnel successfully creates and manages tunnel", async () => {
27
+ const mockCommand = {
28
+ command: "tunnel" as const,
29
+ port: 3001,
30
+ service: "pinggy.io" as const,
31
+ debug: true,
32
+ };
33
+
34
+ const mockTunnel: Tunnel = {
35
+ url: new URL("https://droar-218-152-125-59.a.free.pinggy.link/"),
36
+ localPort: 3001,
37
+ pid: 123,
38
+ close: () => Promise.resolve(),
39
+ };
40
+
41
+ let openTunnelCalled = false;
42
+ let openTunnelPort;
43
+ let openTunnelService;
44
+ let spinnerCalled = false;
45
+ let openTunnelSucceed = false;
46
+ let openTunnelFailed = false;
47
+ let spinnerMsg;
48
+
49
+ const mockDeps = {
50
+ openTunnel: (args: TunnelOptions) => {
51
+ openTunnelCalled = true;
52
+ openTunnelPort = args.port;
53
+ openTunnelService = args.service;
54
+ return Promise.resolve(mockTunnel);
55
+ },
56
+ ora: () =>
57
+ ({
58
+ start() {
59
+ spinnerCalled = true;
60
+ return this;
61
+ },
62
+ fail(msg: string) {
63
+ openTunnelFailed = true;
64
+ spinnerMsg = msg;
65
+ return this;
66
+ },
67
+ succeed(msg: string) {
68
+ openTunnelSucceed = true;
69
+ spinnerMsg = msg;
70
+ return this;
71
+ },
72
+ }) as unknown as Ora,
73
+ exit: (): never => {
74
+ throw new Error();
75
+ },
76
+ };
77
+
78
+ try {
79
+ await runTunnel(mockCommand, mockDeps);
80
+ } finally {
81
+ deepEqual(openTunnelCalled, true);
82
+ deepEqual(openTunnelPort, 3001);
83
+ deepEqual(openTunnelService, "pinggy.io");
84
+ deepEqual(openTunnelSucceed, true);
85
+ deepEqual(openTunnelFailed, false);
86
+ deepEqual(spinnerCalled, true);
87
+ deepEqual(
88
+ spinnerMsg,
89
+ `Your local server at ${mockTunnel.localPort} is now publicly accessible:\n`,
90
+ );
91
+ }
92
+ });
93
+
94
+ test("tunnel fails to create a secure tunnel and handles error", async () => {
95
+ const mockCommand = {
96
+ command: "tunnel" as const,
97
+ port: 3001,
98
+ service: undefined,
99
+ debug: false,
100
+ };
101
+
102
+ let openTunnelCalled = false;
103
+ let openTunnelPort;
104
+ let openTunnelService;
105
+ let spinnerCalled = false;
106
+ let openTunnelSucceed = false;
107
+ let openTunnelFailed = false;
108
+ let spinnerMsg;
109
+
110
+ const mockDeps = {
111
+ openTunnel: (args: TunnelOptions) => {
112
+ openTunnelCalled = true;
113
+ openTunnelPort = args.port;
114
+ openTunnelService = args.service;
115
+ return Promise.reject();
116
+ },
117
+ ora: () =>
118
+ ({
119
+ start() {
120
+ spinnerCalled = true;
121
+ return this;
122
+ },
123
+ fail(msg: string) {
124
+ openTunnelFailed = true;
125
+ spinnerMsg = msg;
126
+ return this;
127
+ },
128
+ succeed(msg: string) {
129
+ openTunnelSucceed = true;
130
+ spinnerMsg = msg;
131
+ return this;
132
+ },
133
+ }) as unknown as Ora,
134
+ exit: (): never => {
135
+ throw new Error("Process exit called");
136
+ },
137
+ };
138
+
139
+ try {
140
+ await rejects(
141
+ () => runTunnel(mockCommand, mockDeps),
142
+ Error,
143
+ "Process exit called",
144
+ );
145
+ } finally {
146
+ deepEqual(openTunnelCalled, true);
147
+ deepEqual(openTunnelPort, 3001);
148
+ deepEqual(openTunnelService, undefined);
149
+ deepEqual(openTunnelSucceed, false);
150
+ deepEqual(openTunnelFailed, true);
151
+ deepEqual(spinnerCalled, true);
152
+ deepEqual(
153
+ spinnerMsg,
154
+ "Failed to create a secure tunnel.",
155
+ );
156
+ }
157
+ });
package/src/tunnel.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { openTunnel, type Tunnel } from "@hongminhee/localtunnel";
1
2
  import {
2
3
  argument,
3
4
  command,
@@ -7,26 +8,83 @@ import {
7
8
  merge,
8
9
  message,
9
10
  object,
11
+ option,
12
+ optional,
10
13
  } from "@optique/core";
11
- import { debugOption } from "./globals.ts";
14
+ import { choice } from "@optique/core/valueparser";
15
+ import { print, printError } from "@optique/run";
16
+ import process from "node:process";
17
+ import ora from "ora";
18
+ import { configureLogging, debugOption } from "./globals.ts";
12
19
 
13
20
  export const tunnelCommand = command(
14
21
  "tunnel",
15
22
  merge(
23
+ "Tunnel options",
16
24
  object({
17
25
  command: constant("tunnel"),
18
- port: argument(integer({ metavar: "PORT", min: 0, max: 65_535 })),
26
+ }),
27
+ object({
28
+ port: argument(integer({ metavar: "PORT", min: 0, max: 65535 }), {
29
+ description: message`The local port number to expose.`,
30
+ }),
31
+ service: optional(
32
+ option(
33
+ "-s",
34
+ "--service",
35
+ choice(["localhost.run", "serveo.net", "pinggy.io"]),
36
+ {
37
+ description: message`The localtunnel service to use.`,
38
+ },
39
+ ),
40
+ ),
19
41
  }),
20
42
  debugOption,
21
43
  ),
22
44
  {
23
45
  description:
24
- message`Expose a local HTTP server to the public internet using a secure tunnel.`,
46
+ message`Expose a local HTTP server to the public internet using a secure tunnel.\nNote that the HTTP requests through the tunnel have X-Forwarded-* headers.`,
25
47
  },
26
48
  );
27
49
 
28
- export function runTunnel(
50
+ export async function runTunnel(
29
51
  command: InferValue<typeof tunnelCommand>,
52
+ deps: {
53
+ openTunnel: typeof openTunnel;
54
+ ora: typeof ora;
55
+ exit: typeof process.exit;
56
+ } = {
57
+ openTunnel,
58
+ ora,
59
+ exit: process.exit,
60
+ },
30
61
  ) {
31
- console.debug(command);
62
+ if (command.debug) {
63
+ await configureLogging();
64
+ }
65
+ const spinner = deps.ora({
66
+ text: "Creating a secure tunnel...",
67
+ discardStdin: false,
68
+ }).start();
69
+ let tunnel: Tunnel;
70
+ try {
71
+ tunnel = await deps.openTunnel({
72
+ port: command.port,
73
+ service: command.service,
74
+ });
75
+ } catch (error) {
76
+ if (command.debug) {
77
+ printError(message`${String(error)}`);
78
+ }
79
+ spinner.fail("Failed to create a secure tunnel.");
80
+ deps.exit(1);
81
+ }
82
+ spinner.succeed(
83
+ `Your local server at ${command.port} is now publicly accessible:\n`,
84
+ );
85
+ print(message`${tunnel.url.href}`);
86
+ print(message`\nPress ^C to close the tunnel.`);
87
+ process.on("SIGINT", async () => {
88
+ await tunnel.close();
89
+ });
32
90
  }