Commit 18894a4a authored by ChanHaeng Lee's avatar ChanHaeng Lee
Browse files

Merge remote-tracking branch 'upstream/main'

parents f815f0d9 7c78ce1a
Loading
Loading
Loading
Loading
+34 −2
Original line number Diff line number Diff line
@@ -10,9 +10,43 @@ To be released.

### @fedify/fedify

 -  Added inverse properties for collections to Vocabulary API.
    [[FEP-5711], [#373], [#381] by Jiwon Kwon]

     -  `new Collection()` constructor now accepts `likesOf` option.
     -  Added `Collection.likesOfId` property.
     -  Added `Collection.getLikesOf()` method.
     -  `new Collection()` constructor now accepts `sharesOf` option.
     -  Added `Collection.sharedOfId` property.
     -  Added `Collection.getSharedOf()` method.
     -  `new Collection()` constructor now accepts `repliesOf` option.
     -  Added `Collection.repliesOfId` property.
     -  Added `Collection.getRepliesOf()` method.
     -  `new Collection()` constructor now accepts `inboxOf` option.
     -  Added `Collection.inboxOfId` property.
     -  Added `Collection.getInboxOf()` method.
     -  `new Collection()` constructor now accepts `outboxOf` option.
     -  Added `Collection.outboxOfId` property.
     -  Added `Collection.getOutboxOf()` method.
     -  `new Collection()` constructor now accepts `followersOf` option.
     -  Added `Collection.followersOfId` property.
     -  Added `Collection.getFollowersOf()` method.
     -  `new Collection()` constructor now accepts `followingOf` option.
     -  Added `Collection.followingOfId` property.
     -  Added `Collection.getFollowingOf()` method.
     -  `new Collection()` constructor now accepts `likedOf` option.
     -  Added `Collection.likedOfId` property.
     -  Added `Collection.getLikedOf()` method.

 -  Changed how `parseSoftware()` function handles non-Semantic Versioning
    number strings on `tryBestEffort` mode.  [[#353], [#365] by Hyeonseo Kim]]

[FEP-5711]: https://w3id.org/fep/5711
[#353]: https://github.com/fedify-dev/fedify/issues/353
[#365]: https://github.com/fedify-dev/fedify/pull/365
[#373]: https://github.com/fedify-dev/fedify/issues/373
[#381]: https://github.com/fedify-dev/fedify/pull/381

### @fedify/cli

 -  Added `Next.js` option to `fedify init` command. This option allows users
@@ -37,8 +71,6 @@ To be released.

[Next.js]: https://nextjs.org/
[#313]: https://github.com/fedify-dev/fedify/issues/313
[#353]: https://github.com/fedify-dev/fedify/issues/353
[#365]: https://github.com/fedify-dev/fedify/pull/365


Version 1.8.5
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
    "@poppanator/http-constants": "npm:@poppanator/http-constants@^1.1.1",
    "@std/assert": "jsr:@std/assert@^1.0.13",
    "@std/fmt/colors": "jsr:@std/fmt@^0.224.0/colors",
    "@std/testing": "jsr:@std/testing@^1.0.8",
    "@std/dotenv": "jsr:@std/dotenv@^0.225.2",
    "@std/semver": "jsr:@std/semver@^1.0.5",
    "cli-highlight": "npm:cli-highlight@^2.1.11",
+172 −0
Original line number Diff line number Diff line
import type { Tunnel, TunnelOptions } from "@hongminhee/localtunnel";
import { assert, assertEquals, assertFalse, assertRejects } from "@std/assert";
import { assertSpyCall, stub } from "@std/testing/mock";
import type { Ora } from "ora";
import { command, tunnelAction } from "./tunnel.ts";

Deno.test("tunnel description", () => {
  // Test that the command is properly configured
  assert(
    command.getDescription().includes(
      "Expose a local HTTP server to the public internet using a secure tunnel.\n\n" +
        "Note that the HTTP requests through the tunnel have X-Forwarded-* headers.",
    ),
  );
});

Deno.test("tunnel command validates port argument", async () => {
  const exitStub = stub(Deno, "exit", () => {
    throw new Error("Process would exit");
  });

  try {
    await assertRejects(
      () => command.parse(["invalid-port"]),
      Error,
      "Process would exit",
    );
    assertSpyCall(exitStub, 0, { args: [2] });
  } finally {
    exitStub.restore();
  }
});

Deno.test("tunnel successfully creates and manages tunnel", async () => {
  // Track function calls
  let openTunnelCalled = false;
  let openTunnelArgs: TunnelOptions[] = [];
  let startCalled = false;
  let succeedCalled = false;
  let succeedArgs: string[] = [];
  let logArgs: string[] = [];
  let errorArgs: string[] = [];
  let addSignalListenerCalled = false;
  let exitCalled = false;

  // Create a mock tunnel object
  const mockTunnel = {
    url: new URL("https://abc123.localhost.run"),
    localPort: 3000,
    pid: 12345,
    close: () => Promise.resolve(),
  };

  // Create mock dependencies
  const mockDeps = {
    openTunnel: (args: TunnelOptions) => {
      openTunnelCalled = true;
      openTunnelArgs = [args];
      return Promise.resolve(mockTunnel as Tunnel);
    },
    ora: () => ({
      start() {
        startCalled = true;
        return this;
      },
      succeed(...args: string[]) {
        succeedCalled = true;
        succeedArgs = args;
        return this;
      },
      fail() {
        return this;
      },
    } as unknown as Ora),
    console: {
      log: (...args: string[]) => {
        logArgs = args;
      },
      error: (...args: string[]) => {
        errorArgs = args;
      },
    } as Console,
    addSignalListener: (() => {
      addSignalListenerCalled = true;
    }) as typeof Deno.addSignalListener,
    exit: (() => {
      exitCalled = true;
    }) as typeof Deno.exit,
  };

  await tunnelAction({ service: undefined }, 3000, mockDeps);

  // Verify all the expected interactions occurred
  assert(openTunnelCalled);
  assertEquals(openTunnelArgs, [{ port: 3000, service: undefined }]);
  assert(startCalled);
  assert(succeedCalled);
  assertEquals(succeedArgs, [
    "Your local server at 3000 is now publicly accessible:\n",
  ]);
  assertEquals(logArgs, ["https://abc123.localhost.run/"]);
  assertEquals(errorArgs, ["\nPress ^C to close the tunnel."]);
  assert(addSignalListenerCalled);
  assertFalse(exitCalled);
});

Deno.test("tunnel fails to create a secure tunnel and handles error", async () => {
  const exitStub = stub(Deno, "exit", () => {
    throw new Error("Process would exit");
  });

  // Track function calls
  let openTunnelCalled = false;
  let openTunnelArgs: TunnelOptions[] = [];
  let startCalled = false;
  let failCalled = false;
  let failArgs: string[] = [];
  let addSignalListenerCalled = false;

  const tunnelError = new Error("Failed to create a secure tunnel.");

  // Create mock dependencies that simulate failure
  const mockDeps = {
    openTunnel: (args: TunnelOptions) => {
      openTunnelCalled = true;
      openTunnelArgs = [args];
      return Promise.reject(tunnelError);
    },
    ora: () => ({
      start() {
        startCalled = true;
        return this;
      },
      succeed() {
        return this;
      },
      fail(...args: string[]) {
        failCalled = true;
        failArgs = args;
        return this;
      },
    } as unknown as Ora),
    console: {
      log: () => {},
      error: () => {},
    } as Console,
    addSignalListener: (() => {
      addSignalListenerCalled = true;
    }) as typeof Deno.addSignalListener,
    exit: (() => {
      throw new Error("Process would exit");
    }) as typeof Deno.exit,
  };

  try {
    await assertRejects(
      () => tunnelAction({ service: undefined }, 3000, mockDeps),
      Error,
      "Process would exit",
    );
  } finally {
    exitStub.restore();
  }

  // Verify error handling interactions
  assert(openTunnelCalled);
  assertEquals(openTunnelArgs, [{ port: 3000, service: undefined }]);
  assert(startCalled);
  assert(failCalled);
  assertEquals(failArgs, ["Failed to create a secure tunnel."]);
  assertFalse(addSignalListenerCalled);
});
+39 −21
Original line number Diff line number Diff line
@@ -4,32 +4,50 @@ import ora from "ora";

const service = new EnumType(["localhost.run", "serveo.net"]);

export const command = new Command()
  .type("service", service)
  .arguments("<port:integer>")
  .description(
    "Expose a local HTTP server to the public internet using a secure tunnel.\n\n" +
      "Note that the HTTP requests through the tunnel have X-Forwarded-* headers.",
  )
  .option("-s, --service <service:service>", "The localtunnel service to use.")
  .action(async (options, port: number) => {
    const spinner = ora({
export async function tunnelAction(
  options: { service?: "localhost.run" | "serveo.net" },
  port: number,
  deps: {
    openTunnel: typeof openTunnel;
    ora: typeof ora;
    console: typeof console;
    addSignalListener: typeof Deno.addSignalListener;
    exit: typeof Deno.exit;
  } = {
    openTunnel,
    ora,
    console,
    addSignalListener: Deno.addSignalListener,
    exit: Deno.exit,
  },
) {
  const spinner = deps.ora({
    text: "Creating a secure tunnel...",
    discardStdin: false,
  }).start();
  let tunnel: Tunnel;
  try {
      tunnel = await openTunnel({ port, service: options.service });
    tunnel = await deps.openTunnel({ port, service: options.service });
  } catch {
    spinner.fail("Failed to create a secure tunnel.");
      Deno.exit(1);
    deps.exit(1);
  }
  spinner.succeed(
    `Your local server at ${port} is now publicly accessible:\n`,
  );
    console.log(tunnel.url.href);
    console.error("\nPress ^C to close the tunnel.");
    Deno.addSignalListener("SIGINT", async () => {
  deps.console.log(tunnel.url.href);
  deps.console.error("\nPress ^C to close the tunnel.");
  deps.addSignalListener("SIGINT", async () => {
    await tunnel.close();
  });
  });
}

export const command = new Command()
  .type("service", service)
  .arguments("<port:integer>")
  .description(
    "Expose a local HTTP server to the public internet using a secure tunnel.\n\n" +
      "Note that the HTTP requests through the tunnel have X-Forwarded-* headers.",
  )
  .option("-s, --service <service:service>", "The localtunnel service to use.")
  .action(tunnelAction);
+2306 −154

File changed.

Preview size limit exceeded, changes collapsed.

Loading