Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Handle app URL translation into seed BaseUrl in one place
Rūdolfs Ošiņš committed 2 years ago
commit 3f7a58c4896e778757ed047fdae7d90414342735
parent bc25367ecbdccf08ee3fd079c3ba777384b0847f
18 files changed +132 -123
modified httpd-client/index.ts
@@ -102,39 +102,33 @@ const nodeStatsSchema = object({

export class HttpdClient {
  #fetcher: Fetcher;
-
  #baseUrl: BaseUrl;

+
  public baseUrl: BaseUrl;
  public project: project.Client;
  public session: session.Client;

  public constructor(baseUrl: BaseUrl) {
-
    this.#baseUrl = baseUrl;
-
    this.#fetcher = new Fetcher(this.#baseUrl);
+
    this.baseUrl = baseUrl;
+
    this.#fetcher = new Fetcher(this.baseUrl);

    this.project = new project.Client(this.#fetcher);
    this.session = new session.Client(this.#fetcher);
  }

  public changePort(port: number): void {
-
    this.#baseUrl.port = port;
+
    this.baseUrl.port = port;
  }

  public get url(): string {
-
    return `${this.#baseUrl.scheme}://${this.#baseUrl.hostname}:${
-
      this.#baseUrl.port
-
    }`;
-
  }
-

-
  public get hostAndPort(): string {
-
    return `${this.#baseUrl.hostname}:${this.#baseUrl.port}`;
+
    return `${this.baseUrl.scheme}://${this.baseUrl.hostname}:${this.baseUrl.port}`;
  }

  public get hostname(): string {
-
    return this.#baseUrl.hostname;
+
    return this.baseUrl.hostname;
  }

  public get port(): string {
-
    return this.#baseUrl.port.toString();
+
    return this.baseUrl.port.toString();
  }

  public async getNodeInfo(options?: RequestOptions): Promise<NodeInfo> {
modified src/App/Header/Connect.svelte
@@ -120,7 +120,7 @@
          route={{
            resource: "seeds",
            params: {
-
              hostAndPort: httpd.api.hostAndPort,
+
              baseUrl: httpd.api.baseUrl,
              projectPageIndex: 0,
            },
          }}>
@@ -153,7 +153,7 @@
          route={{
            resource: "seeds",
            params: {
-
              hostAndPort: httpd.api.hostAndPort,
+
              baseUrl: httpd.api.baseUrl,
              projectPageIndex: 0,
            },
          }}>
modified src/App/Header/Search.svelte
@@ -6,7 +6,7 @@
  import * as modal from "@app/lib/modal";
  import * as router from "@app/lib/router";
  import { searchPlaceholder } from "@app/lib/shared";
-
  import { getHostAndPort, unreachable } from "@app/lib/utils";
+
  import { unreachable } from "@app/lib/utils";

  import Icon from "@app/components/Icon.svelte";
  import SearchResultsModal from "@app/App/Header/SearchResultsModal.svelte";
@@ -54,7 +54,7 @@
            view: { resource: "tree" },
            id: project.id,
            peer: undefined,
-
            hostAndPort: getHostAndPort(baseUrl),
+
            baseUrl,
            hash: undefined,
            search: undefined,
          },
modified src/App/Header/SearchResultsModal.svelte
@@ -2,7 +2,7 @@
  import type { ProjectBaseUrl } from "@app/lib/search";

  import * as modal from "@app/lib/modal";
-
  import { formatRepositoryId, getHostAndPort } from "@app/lib/utils";
+
  import { formatRepositoryId } from "@app/lib/utils";

  import Link from "@app/components/Link.svelte";
  import Modal from "@app/components/Modal.svelte";
@@ -40,7 +40,7 @@
                resource: "projects",
                params: {
                  view: { resource: "tree" },
-
                  hostAndPort: getHostAndPort(result.baseUrl),
+
                  baseUrl: result.baseUrl,
                  id: result.project.id,
                },
              }}>
modified src/lib/router.ts
@@ -1,14 +1,16 @@
+
import type { BaseUrl } from "@httpd-client";
import type { LoadedRoute, Route } from "@app/lib/router/definitions";

import { get, writable } from "svelte/store";

import * as mutexExecutor from "@app/lib/mutexExecutor";
-
import { loadRoute } from "@app/lib/router/definitions";
-
import { unreachable } from "@app/lib/utils";
+
import * as utils from "@app/lib/utils";
+
import { config } from "@app/lib/config";
import {
  createProjectRoute,
  resolveProjectRoute,
} from "@app/views/projects/router";
+
import { loadRoute } from "@app/lib/router/definitions";

// Only used by Safari.
const DOCUMENT_TITLE = "Radicle Interface";
@@ -118,6 +120,34 @@ export async function replace(newRoute: Route): Promise<void> {
  await navigate("replace", newRoute);
}

+
function extractBaseUrl(hostAndPort: string): BaseUrl {
+
  if (
+
    hostAndPort === "radicle.local" ||
+
    hostAndPort === "radicle.local:8080" ||
+
    hostAndPort === "0.0.0.0" ||
+
    hostAndPort === "0.0.0.0:8080" ||
+
    hostAndPort === "127.0.0.1" ||
+
    hostAndPort === "127.0.0.1:8080"
+
  ) {
+
    return { hostname: "127.0.0.1", port: 8080, scheme: "http" };
+
  } else if (hostAndPort.includes(":")) {
+
    const [hostname, port] = hostAndPort.split(":");
+
    return {
+
      hostname,
+
      port: Number(port),
+
      scheme: utils.isLocal(hostname)
+
        ? "http"
+
        : config.seeds.defaultHttpdScheme,
+
    };
+
  } else {
+
    return {
+
      hostname: hostAndPort,
+
      port: config.seeds.defaultHttpdPort,
+
      scheme: config.seeds.defaultHttpdScheme,
+
    };
+
  }
+
}
+

function pathToRoute(url: URL): Route | null {
  const segments = url.pathname.substring(1).split("/");

@@ -126,6 +156,7 @@ function pathToRoute(url: URL): Route | null {
    case "seeds": {
      const hostAndPort = segments.shift();
      if (hostAndPort) {
+
        const baseUrl = extractBaseUrl(hostAndPort);
        const id = segments.shift();
        if (id) {
          // Allows project paths with or without trailing slash
@@ -139,17 +170,17 @@ function pathToRoute(url: URL): Route | null {
                view: { resource: "tree" },
                id,
                peer: undefined,
-
                hostAndPort,
+
                baseUrl,
              },
            };
          }
-
          const params = resolveProjectRoute(url, hostAndPort, id, segments);
+
          const params = resolveProjectRoute(url, baseUrl, id, segments);
          if (params) {
            return {
              resource: "projects",
              params: {
                ...params,
-
                hostAndPort,
+
                baseUrl,
                id,
              },
            };
@@ -158,7 +189,7 @@ function pathToRoute(url: URL): Route | null {
        }
        return {
          resource: "seeds",
-
          params: { hostAndPort, projectPageIndex: 0 },
+
          params: { baseUrl, projectPageIndex: 0 },
        };
      }
      return null;
@@ -186,17 +217,27 @@ function pathToRoute(url: URL): Route | null {
  }
}

+
function seedPath(baseUrl: BaseUrl) {
+
  const port = baseUrl.port ?? config.seeds.defaultHttpdPort;
+

+
  if (port === config.seeds.defaultHttpdPort) {
+
    return `/seeds/${baseUrl.hostname}`;
+
  } else {
+
    return `/seeds/${baseUrl.hostname}:${port}`;
+
  }
+
}
+

export function routeToPath(route: Route) {
  if (route.resource === "home") {
    return "/";
  } else if (route.resource === "session") {
    return `/session?id=${route.params.id}&sig=${route.params.signature}&pk=${route.params.publicKey}`;
  } else if (route.resource === "seeds") {
-
    return `/seeds/${route.params.hostAndPort}`;
+
    return seedPath(route.params.baseUrl);
  } else if (route.resource === "loadError") {
    return "";
  } else if (route.resource === "projects") {
-
    const hostAndPortPrefix = `/seeds/${route.params.hostAndPort}`;
+
    const seed = seedPath(route.params.baseUrl);
    const content = `/${route.params.view.resource}`;

    let peer = "";
@@ -230,38 +271,38 @@ export function routeToPath(route: Route) {

    if (route.params.view.resource === "tree") {
      if (suffix) {
-
        return `${hostAndPortPrefix}/${route.params.id}${peer}/tree${suffix}`;
+
        return `${seed}/${route.params.id}${peer}/tree${suffix}`;
      }
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}`;
+
      return `${seed}/${route.params.id}${peer}`;
    } else if (route.params.view.resource === "commits") {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/commits${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/commits${suffix}`;
    } else if (route.params.view.resource === "history") {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/history${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/history${suffix}`;
    } else if (
      route.params.view.resource === "issues" &&
      route.params.view.params?.view.resource === "new"
    ) {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/issues/new${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/issues/new${suffix}`;
    } else if (route.params.view.resource === "issues") {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/issues${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/issues${suffix}`;
    } else if (route.params.view.resource === "issue") {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/issues/${route.params.view.params.issue}`;
+
      return `${seed}/${route.params.id}${peer}/issues/${route.params.view.params.issue}`;
    } else if (route.params.view.resource === "patches") {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/patches${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/patches${suffix}`;
    } else if (route.params.view.resource === "patch") {
      if (route.params.view.params.revision) {
-
        return `${hostAndPortPrefix}/${route.params.id}${peer}/patches/${route.params.view.params.patch}/${route.params.view.params.revision}${suffix}`;
+
        return `${seed}/${route.params.id}${peer}/patches/${route.params.view.params.patch}/${route.params.view.params.revision}${suffix}`;
      }
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}/patches/${route.params.view.params.patch}${suffix}`;
+
      return `${seed}/${route.params.id}${peer}/patches/${route.params.view.params.patch}${suffix}`;
    } else {
-
      return `${hostAndPortPrefix}/${route.params.id}${peer}${content}`;
+
      return `${seed}/${route.params.id}${peer}${content}`;
    }
  } else if (route.resource === "booting") {
    return "";
  } else if (route.resource === "notFound") {
    return route.params.url;
  } else {
-
    unreachable(route);
+
    utils.unreachable(route);
  }
}

modified src/lib/utils.ts
@@ -5,7 +5,6 @@ import bs58 from "bs58";
import twemojiModule from "twemoji";

import { base } from "@app/lib/router";
-
import { config } from "@app/lib/config";

export async function toClipboard(text: string): Promise<void> {
  await navigator.clipboard.writeText(text);
@@ -244,38 +243,6 @@ export function twemoji(
  });
}

-
export function extractBaseUrl(hostAndPort: string): BaseUrl {
-
  if (
-
    hostAndPort === "radicle.local" ||
-
    hostAndPort === "radicle.local:8080" ||
-
    hostAndPort === "0.0.0.0" ||
-
    hostAndPort === "0.0.0.0:8080" ||
-
    hostAndPort === "127.0.0.1" ||
-
    hostAndPort === "127.0.0.1:8080"
-
  ) {
-
    return { hostname: "127.0.0.1", port: 8080, scheme: "http" };
-
  } else if (hostAndPort.includes(":")) {
-
    const [hostname, port] = hostAndPort.split(":");
-
    return {
-
      hostname,
-
      port: Number(port),
-
      scheme: isLocal(hostname) ? "http" : config.seeds.defaultHttpdScheme,
-
    };
-
  } else {
-
    return {
-
      hostname: hostAndPort,
-
      port: config.seeds.defaultHttpdPort,
-
      scheme: config.seeds.defaultHttpdScheme,
-
    };
-
  }
-
}
-

-
export function getHostAndPort(baseUrl: BaseUrl): string {
-
  return baseUrl.port === config.seeds.defaultHttpdPort
-
    ? baseUrl.hostname
-
    : `${baseUrl.hostname}:${baseUrl.port}`;
-
}
-

export function createAddRemoveArrays(
  currentArray: string[],
  newArray: string[],
modified src/views/home/Index.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import type { ProjectBaseUrlActivity } from "./router";

-
  import { getHostAndPort, twemoji } from "@app/lib/utils";
+
  import { twemoji } from "@app/lib/utils";

  import Link from "@app/components/Link.svelte";
  import ProjectCard from "@app/components/ProjectCard.svelte";
@@ -78,7 +78,7 @@
              params: {
                view: { resource: "tree" },
                id: project.id,
-
                hostAndPort: getHostAndPort(baseUrl),
+
                baseUrl,
                peer: undefined,
                revision: undefined,
              },
modified src/views/projects/Header.svelte
@@ -2,7 +2,7 @@
  import type { BaseUrl } from "@httpd-client";
  import type { ProjectLoadedView } from "@app/views/projects/router";

-
  import { getHostAndPort, isLocal } from "@app/lib/utils";
+
  import { isLocal } from "@app/lib/utils";
  import { pluralize } from "@app/lib/pluralize";

  import CloneButton from "@app/views/projects/CloneButton.svelte";
@@ -107,7 +107,7 @@
    route={{
      resource: "seeds",
      params: {
-
        hostAndPort: getHostAndPort(baseUrl),
+
        baseUrl,
        projectPageIndex: 0,
      },
    }}>
modified src/views/projects/Issue.svelte
@@ -141,7 +141,7 @@
        resource: "projects",
        params: {
          id: projectId,
-
          hostAndPort: utils.getHostAndPort(baseUrl),
+
          baseUrl,
          view: {
            resource: "issue",
            params: { issue: issue.id },
modified src/views/projects/Issue/New.svelte
@@ -1,7 +1,6 @@
<script lang="ts">
  import type { BaseUrl } from "@httpd-client";

-
  import * as httpd from "@app/lib/httpd";
  import * as modal from "@app/lib/modal";
  import * as router from "@app/lib/router";
  import * as utils from "@app/lib/utils";
@@ -56,7 +55,7 @@
        resource: "projects",
        params: {
          id: projectId,
-
          hostAndPort: httpd.api.hostAndPort,
+
          baseUrl,
          view: {
            resource: "issue",
            params: { issue: result.id },
modified src/views/projects/View.svelte
@@ -1,8 +1,7 @@
<script lang="ts">
-
  import type { Project } from "@httpd-client";
+
  import type { BaseUrl, Project } from "@httpd-client";
  import type { ProjectLoadedView } from "@app/views/projects/router";

-
  import * as utils from "@app/lib/utils";
  import { unreachable } from "@app/lib/utils";

  import SourceBrowsingHeader from "./SourceBrowsingHeader.svelte";
@@ -18,18 +17,16 @@
  import Patches from "./Patches.svelte";
  import ProjectMeta from "./ProjectMeta.svelte";

-
  export let hostAndPort: string;
  export let id: string;
  export let project: Project;
  export let view: ProjectLoadedView;
+
  export let baseUrl: BaseUrl;

  export let hash: string | undefined = undefined;
  export let path: string | undefined = undefined;
  export let peer: string | undefined = undefined;
  export let revision: string | undefined = undefined;
  export let search: string | undefined = undefined;
-

-
  $: baseUrl = utils.extractBaseUrl(hostAndPort);
</script>

<style>
modified src/views/projects/router.ts
@@ -1,5 +1,6 @@
import type { LoadError } from "@app/lib/router/definitions";
import type {
+
  BaseUrl,
  Commit,
  CommitHeader,
  Issue,
@@ -13,7 +14,6 @@ import { get } from "svelte/store";

import { HttpdClient } from "@httpd-client";
import { activeRouteStore, push, replace, routeToPath } from "@app/lib/router";
-
import { extractBaseUrl } from "@app/lib/utils";

export const COMMITS_PER_PAGE = 30;

@@ -28,14 +28,8 @@ export interface ProjectLoadedRoute {
}

export interface ProjectsParams {
+
  baseUrl: BaseUrl;
  id: string;
-
  hash?: string;
-
  hostAndPort: string;
-
  path?: string;
-
  peer?: string;
-
  revision?: string;
-
  route?: string;
-
  search?: string;
  view:
    | { resource: "tree" }
    | { resource: "commits" }
@@ -54,10 +48,17 @@ export interface ProjectsParams {
        };
      }
    | { resource: "patch"; params: { patch: string; revision?: string } };
+

+
  hash?: string;
+
  path?: string;
+
  peer?: string;
+
  revision?: string;
+
  route?: string;
+
  search?: string;
}

export interface ProjectLoadedParams {
-
  hostAndPort: string;
+
  baseUrl: BaseUrl;
  id: string;
  project: Project;
  view: ProjectLoadedView;
@@ -148,8 +149,7 @@ export function parseRevisionToOid(
export async function loadProjectRoute(
  params: ProjectsParams,
): Promise<ProjectLoadedRoute | LoadError> {
-
  const baseUrl = extractBaseUrl(params.hostAndPort);
-
  const api = new HttpdClient(baseUrl);
+
  const api = new HttpdClient(params.baseUrl);
  try {
    if (
      params.view.resource === "tree" ||
@@ -429,7 +429,7 @@ export async function updateProjectRoute(

export function resolveProjectRoute(
  url: URL,
-
  hostAndPort: string,
+
  baseUrl: BaseUrl,
  id: string,
  segments: string[],
): ProjectsParams | null {
@@ -444,8 +444,8 @@ export function resolveProjectRoute(
    const hash = url.href.match(/#{1}[^#.]+$/)?.pop();
    return {
      view: { resource: "tree" },
+
      baseUrl,
      id,
-
      hostAndPort,
      peer,
      path: undefined,
      revision: undefined,
@@ -456,8 +456,8 @@ export function resolveProjectRoute(
  } else if (content === "history") {
    return {
      view: { resource: "history" },
+
      baseUrl,
      id,
-
      hostAndPort,
      peer,
      path: undefined,
      revision: undefined,
@@ -467,8 +467,8 @@ export function resolveProjectRoute(
  } else if (content === "commits") {
    return {
      view: { resource: "commits" },
+
      baseUrl,
      id,
-
      hostAndPort,
      peer,
      path: undefined,
      revision: undefined,
@@ -480,8 +480,8 @@ export function resolveProjectRoute(
    if (issueOrAction === "new") {
      return {
        view: { resource: "issues", params: { view: { resource: "new" } } },
+
        baseUrl,
        id,
-
        hostAndPort,
        peer,
        search: sanitizeQueryString(url.search),
        path: undefined,
@@ -490,8 +490,8 @@ export function resolveProjectRoute(
    } else if (issueOrAction) {
      return {
        view: { resource: "issue", params: { issue: issueOrAction } },
+
        baseUrl,
        id,
-
        hostAndPort,
        peer,
        path: undefined,
        revision: undefined,
@@ -500,8 +500,8 @@ export function resolveProjectRoute(
    } else {
      return {
        view: { resource: "issues" },
+
        baseUrl,
        id,
-
        hostAndPort,
        peer,
        search: sanitizeQueryString(url.search),
        path: undefined,
@@ -514,8 +514,8 @@ export function resolveProjectRoute(
    if (patch) {
      return {
        view: { resource: "patch", params: { patch, revision } },
+
        baseUrl,
        id,
-
        hostAndPort,
        peer,
        path: undefined,
        revision: undefined,
@@ -524,8 +524,8 @@ export function resolveProjectRoute(
    } else {
      return {
        view: { resource: "patches" },
+
        baseUrl,
        id,
-
        hostAndPort,
        peer,
        search: sanitizeQueryString(url.search),
        path: undefined,
modified src/views/seeds/View.svelte
@@ -3,7 +3,7 @@
  import type { ProjectActivity } from "@app/views/seeds/router";

  import { config } from "@app/lib/config";
-
  import { getHostAndPort, isLocal, truncateId } from "@app/lib/utils";
+
  import { isLocal, truncateId } from "@app/lib/utils";
  import { loadProjects } from "@app/views/seeds/router";

  import Button from "@app/components/Button.svelte";
@@ -131,7 +131,7 @@
              params: {
                view: { resource: "tree" },
                id: project.id,
-
                hostAndPort: getHostAndPort(baseUrl),
+
                baseUrl,
                revision: undefined,
                hash: undefined,
                search: undefined,
modified src/views/seeds/router.ts
@@ -3,11 +3,10 @@ import type { LoadError } from "@app/lib/router/definitions";
import type { WeeklyActivity } from "@app/lib/commit";

import { HttpdClient } from "@httpd-client";
-
import { extractBaseUrl } from "@app/lib/utils";
import { loadProjectActivity } from "@app/lib/commit";

interface SeedsRouteParams {
-
  hostAndPort: string;
+
  baseUrl: BaseUrl;
  projectPageIndex: number;
}

@@ -68,19 +67,18 @@ export async function loadProjects(
export async function loadSeedRoute(
  params: SeedsRouteParams,
): Promise<SeedsLoadedRoute | LoadError> {
-
  const baseUrl = extractBaseUrl(params.hostAndPort);
-
  const api = new HttpdClient(baseUrl);
+
  const api = new HttpdClient(params.baseUrl);
  try {
    const projectPageIndex = 0;
    const [nodeInfo, { projects, total }] = await Promise.all([
      api.getNodeInfo(),
-
      loadProjects(projectPageIndex, baseUrl),
+
      loadProjects(projectPageIndex, params.baseUrl),
    ]);
    return {
      resource: "seeds",
      params: {
        projectPageIndex: projectPageIndex + 1,
-
        baseUrl,
+
        baseUrl: params.baseUrl,
        nid: nodeInfo.node.id,
        version: nodeInfo.version,
        projects: projects,
@@ -91,7 +89,7 @@ export async function loadSeedRoute(
    return {
      resource: "loadError",
      params: {
-
        title: params.hostAndPort,
+
        title: `${params.baseUrl.hostname}:${params.baseUrl.port}`,
        errorMessage: "Not able to query this seed.",
        stackTrace: error.stack,
      },
modified src/views/session/Index.svelte
@@ -21,7 +21,7 @@
      void router.push({
        resource: "seeds",
        params: {
-
          hostAndPort: httpd.api.hostAndPort,
+
          baseUrl: httpd.api.baseUrl,
          projectPageIndex: 0,
        },
      });
modified tests/e2e/hashRouter.spec.ts
@@ -31,7 +31,7 @@ test("navigation between seed and project pages", async ({ page }) => {
  await project.click();
  await expect(page).toHaveURL(`/#${sourceBrowsingUrl}`);

-
  await expectBackAndForwardNavigationWorks("/#/seeds/radicle.local", page);
+
  await expectBackAndForwardNavigationWorks("/#/seeds/127.0.0.1", page);
  await expectUrlPersistsReload(page);

  await page.locator('role=link[name="radicle.local"]').click();
modified tests/e2e/historyRouter.spec.ts
@@ -31,7 +31,7 @@ test("navigation between seed and project pages", async ({ page }) => {
  await project.click();
  await expect(page).toHaveURL(sourceBrowsingUrl);

-
  await expectBackAndForwardNavigationWorks("/seeds/radicle.local", page);
+
  await expectBackAndForwardNavigationWorks("/seeds/127.0.0.1", page);
  await expectUrlPersistsReload(page);

  await page.locator('role=link[name="radicle.local"]').click();
modified tests/unit/router.test.ts
@@ -10,7 +10,7 @@ describe("routeToPath", () => {
    {
      input: {
        resource: "seeds",
-
        params: { hostAndPort: "willow.radicle.garden" },
+
        params: { baseUrl: { hostname: "willow.radicle.garden" } },
      },
      output: "/seeds/willow.radicle.garden",
      description: "Seed View Route",
@@ -20,7 +20,7 @@ describe("routeToPath", () => {
        resource: "projects",
        params: {
          view: { resource: "tree" },
-
          hostAndPort: "willow.radicle.garden",
+
          baseUrl: { hostname: "willow.radicle.garden" },
          id: "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT",
        },
      },
@@ -54,7 +54,14 @@ describe("pathToRoute", () => {
      input: new URL("/seeds/willow.radicle.garden", dummyUrl),
      output: {
        resource: "seeds",
-
        params: { hostAndPort: "willow.radicle.garden", projectPageIndex: 0 },
+
        params: {
+
          baseUrl: {
+
            hostname: "willow.radicle.garden",
+
            scheme: "http",
+
            port: 8080,
+
          },
+
          projectPageIndex: 0,
+
        },
      },
      description: "Seed View Route",
    },
@@ -67,8 +74,11 @@ describe("pathToRoute", () => {
        resource: "projects",
        params: {
          view: { resource: "tree" },
-
          hostAndPort: "willow.radicle.garden",
-
          profile: undefined,
+
          baseUrl: {
+
            hostname: "willow.radicle.garden",
+
            scheme: "http",
+
            port: 8080,
+
          },
          peer: undefined,
          id: "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT",
        },
@@ -92,8 +102,11 @@ describe("pathToRoute", () => {
        resource: "projects",
        params: {
          view: { resource: "tree" },
-
          hostAndPort: "willow.radicle.garden",
-
          profile: undefined,
+
          baseUrl: {
+
            hostname: "willow.radicle.garden",
+
            scheme: "http",
+
            port: 8080,
+
          },
          peer: undefined,
          id: "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT",
        },